效果
安装库
安装两个库,分别用来读xml和csv,如果有luarocks,执行下列命令
luarocks install xml2lua
luarocks install ftcsv
manoelcampos/xml2lua (github.com)
整体结构 tilemap.lua
引入要用的两个库,创建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
返回map
跟tileset
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 引擎加载时候加载了图块集与地图。
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)
完整代码 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