效果

[Lua][Love] "图块集与地图" 加载显示功能 TileMap-LMLPHP

安装库

安装两个库,分别用来读xml和csv,如果有luarocks,执行下列命令

luarocks install xml2lua
luarocks install ftcsv

manoelcampos/xml2lua (github.com)

FourierTransformer/ftcsv


整体结构 tilemap.lua

[Lua][Love] "图块集与地图" 加载显示功能 TileMap-LMLPHP

引入要用的两个库,创建xml解析器,读取xml解析到的数据是从handler.root里读的,而不是parser

local xml2lua = require 'xml2lua'
local handler = require 'xmlhandler.tree'
local ftcsv = require 'ftcsv'
local parser = xml2lua.parser(handler)
local map = {}
local tileset = {}

love.load执行两个回调函数加载图块集跟地图

function map:load(...)
  map:loadTMX(love.filesystem.getWorkingDirectory() .. "/map/map.tmx")
end

function tileset:load(...)
  tileset:loadTSX(love.filesystem.getWorkingDirectory() .. "/map/tilemap_packed.tsx")
  tileset:loadTile()
end

在游戏窗口绘制各个图块,图层从map列表获得,图块从tileset列表获得

function map:draw()
  for _, layer in ipairs(map) do
    for i = 1, map.height, 1 do
      for k = 1, map.width, 1 do
        if layer[i][k] ~= "0" then
          love.graphics.draw(
            tileset.image,
            tileset[tonumber(layer[i][k])],
            tileset.tilewidth * (k - 1),
            tileset.tileheight * (i - 1)
          )
        end
      end
    end
  end
end

加载文件用,找不到文件就报错

function openFile(filename)
  local f = io.open(filename, 'r')
  if f then
    return f
  else
    error("no file exists", 2)
  end
end

加载地图文件 tmx,先用xml解析器解析,然后用ftcsv解析各个图层保存到map

tmx各个图层csv的最后一行末尾没有 , 符号会导致 ftcsv 出错,手动添加一个 , 符号

function map:loadTMX(filename)
  local f = openFile(filename)
  local tmx = f:read('a')
  parser:parse(tmx)
  local attr = handler.root.map._attr
  map.width = tonumber(attr.width)
  map.height = tonumber(attr.height)
  for _, value in pairs(handler.root.map.layer) do
    map[#map + 1] = ftcsv.parse(value.data[1] .. ',', ",", { headers = false, loadFromString = true })
  end
end

加载 TSX 图块集的信息,包括图块大小,图块数量,列数,行数需要自己算,记得转换成 number 类型

function tileset:loadTSX(filename)
  local f = openFile(filename)
  local tsx = f:read('a')
  parser:parse(tsx)
  local attr = handler.root.tileset._attr
  local image = handler.root.tileset.image._attr
  tileset.tilewidth = tonumber(attr.tilewidth)
  tileset.tileheight = tonumber(attr.tileheight)
  tileset.tilecount = tonumber(attr.tilecount)
  tileset.columns = tonumber(attr.columns)
  tileset.rows = tileset.tilecount / tileset.columns
  tileset.source = image.source
  tileset.width = tonumber(image.width)
  tileset.width = tonumber(image.height)
end

加载 TSX 图块集信息后,已经拿到图块数量,图块大小,图片集文件地址等信息。通过这些信息,我们使用加载图片集文件,然后使用 newQuad 将图片集各个部分,即图块,保存到tileset列表中

function tileset:loadTile()
  self.source = string.gsub(self.source, "%.%.", "")
  self.image = love.graphics.newImage(self.source)
  for i = 0, tileset.rows - 1, 1 do
    for j = 0, tileset.columns - 1, 1 do
      tileset[#tileset + 1] = love.graphics.newQuad(
        j * tileset.tilewidth, i * tileset.tileheight,
        tileset.tilewidth, tileset.tileheight, self.image
      )
    end
  end
end

返回maptileset

return {
  map = map,
  tileset = tileset
}

鼠标位置显示坐标 mouse.lua

根据鼠标在屏幕的x、y坐标绘制方块和文字

local mouse = {}

mouse.pos_x = 0
mouse.pos_y = 0
mouse.size = 32

function mouse:update(t)
  mouse.pos_x, mouse.pos_y = love.mouse.getPosition()
end

function mouse:draw()
  local x = math.floor(self.pos_x / self.size)
  local y = math.floor(self.pos_y / self.size)
  love.graphics.rectangle("fill", x * self.size, y * self.size, self.size, self.size)
  love.graphics.print(
    string.format("(%d,%d)", x + 1, y + 1),
    x * self.size + self.size,
    y * self.size + math.floor(self.size / 2), 0, 2,
    2)
end

return mouse

游戏循环 main.lua

引入我们写的模块

local tilemap = require 'tilemap'
local map = tilemap.map
local tileset = tilemap.tileset
local mouse = require 'mouse'
require 'debugger'

原图块像素只有 16px,游戏画面需要放大几倍,如果按默认过滤模式,会出现“流血现象”(下图所示),全部改成nearest,单纯的像素放大。love 引擎加载时候加载了图块集与地图。

[Lua][Love] "图块集与地图" 加载显示功能 TileMap-LMLPHP

love.load = function()
  love.graphics.setDefaultFilter("nearest", "nearest")
  tileset:load()
  map:load()
  player = love.graphics.newImage("assets/player.png")
end

更新鼠标位置

love.update = function(t)
  mouse:update(t)
end

先保存原视角状态,修改视角整体放大两倍,绘制地图,之后还原视角,再画玩家(32*32)跟鼠标。

love.draw = function()
  love.graphics.push()
  love.graphics.scale(2)
  map:draw()
  love.graphics.pop()
  love.graphics.draw(player, 16 * 16, 192)
  mouse:draw()
end

自己创建 TSX、TMX

用的一款工具 Tiled Map Editor by Thorbjørn (itch.io)

[Lua][Love] "图块集与地图" 加载显示功能 TileMap-LMLPHP

完整代码 tilemap.lua

local xml2lua = require 'xml2lua'
local handler = require 'xmlhandler.tree'
local ftcsv = require 'ftcsv'
local parser = xml2lua.parser(handler)
local map = {}
local tileset = {}

function map:load(...)
  map:loadTMX(love.filesystem.getWorkingDirectory() .. "/map/map.tmx")
end

function tileset:load(...)
  tileset:loadTSX(love.filesystem.getWorkingDirectory() .. "/map/tilemap_packed.tsx")
  tileset:loadTile()
end

function map:draw()
  for _, layer in ipairs(map) do
    for i = 1, map.height, 1 do
      for k = 1, map.width, 1 do
        if layer[i][k] ~= "0" then
          love.graphics.draw(
            tileset.image,
            tileset[tonumber(layer[i][k])],
            tileset.tilewidth * (k - 1),
            tileset.tileheight * (i - 1)
          )
        end
      end
    end
  end
end

function openFile(filename)
  local f = io.open(filename, 'r')
  if f then
    return f
  else
    error("no file exists", 2)
  end
end

function map:loadTMX(filename)
  local f = openFile(filename)
  local tmx = f:read('a')
  parser:parse(tmx)
  local attr = handler.root.map._attr
  map.width = tonumber(attr.width)
  map.height = tonumber(attr.height)
  for _, value in pairs(handler.root.map.layer) do
    map[#map + 1] = ftcsv.parse(value.data[1] .. ',', ",", { headers = false, loadFromString = true })
  end
end

function tileset:loadTSX(filename)
  local f = openFile(filename)
  local tsx = f:read('a')
  parser:parse(tsx)
  local attr = handler.root.tileset._attr
  local image = handler.root.tileset.image._attr
  tileset.tilewidth = tonumber(attr.tilewidth)
  tileset.tileheight = tonumber(attr.tileheight)
  tileset.tilecount = tonumber(attr.tilecount)
  tileset.columns = tonumber(attr.columns)
  tileset.rows = tileset.tilecount / tileset.columns
  tileset.source = image.source
  tileset.width = tonumber(image.width)
  tileset.width = tonumber(image.height)
end

function tileset:loadTile()
  self.source = string.gsub(self.source, "%.%.", "")
  self.image = love.graphics.newImage(self.source)
  for i = 0, tileset.rows - 1, 1 do
    for j = 0, tileset.columns - 1, 1 do
      tileset[#tileset + 1] = love.graphics.newQuad(
        j * tileset.tilewidth, i * tileset.tileheight,
        tileset.tilewidth, tileset.tileheight, self.image
      )
    end
  end
end

return {
  map = map,
  tileset = tileset
}

完整代码 main.lua

local tilemap = require 'tilemap'
local map = tilemap.map
local tileset = tilemap.tileset
local mouse = require 'mouse'
require 'debugger'


love.load = function()
  love.graphics.setDefaultFilter("nearest", "nearest")
  tileset:load()
  map:load()
  player = love.graphics.newImage("assets/player.png")
end

love.update = function(t)
  mouse:update(t)
end

love.draw = function()
  love.graphics.push()
  love.graphics.scale(2)
  map:draw()
  love.graphics.pop()
  love.graphics.draw(player, 16 * 16, 192)
  mouse:draw()
end

完整项目

https://linxiaoxu.oss-cn-hangzhou.aliyuncs.com/static/files/2023/tinytown.zip

08-25 03:29