1483 lines
37 KiB
Lua
1483 lines
37 KiB
Lua
|
--- Simple and fast Tiled map loader and renderer.
|
||
|
-- @module sti
|
||
|
-- @author Landon Manning
|
||
|
-- @copyright 2016
|
||
|
-- @license MIT/X11
|
||
|
|
||
|
local STI = {
|
||
|
_LICENSE = "MIT/X11",
|
||
|
_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
|
||
|
_VERSION = "0.18.2.1",
|
||
|
_DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework.",
|
||
|
cache = {}
|
||
|
}
|
||
|
STI.__index = STI
|
||
|
|
||
|
local cwd = (...):gsub('%.init$', '') .. "."
|
||
|
local utils = require(cwd .. "utils")
|
||
|
local ceil = math.ceil
|
||
|
local floor = math.floor
|
||
|
local lg = require(cwd .. "graphics")
|
||
|
local Map = {}
|
||
|
Map.__index = Map
|
||
|
|
||
|
local function new(map, plugins, ox, oy)
|
||
|
local dir = ""
|
||
|
|
||
|
if type(map) == "table" then
|
||
|
map = setmetatable(map, Map)
|
||
|
else
|
||
|
-- Check for valid map type
|
||
|
local ext = map:sub(-4, -1)
|
||
|
assert(ext == ".lua", string.format(
|
||
|
"Invalid file type: %s. File must be of type: lua.",
|
||
|
ext
|
||
|
))
|
||
|
|
||
|
-- Get directory of map
|
||
|
dir = map:reverse():find("[/\\]") or ""
|
||
|
if dir ~= "" then
|
||
|
dir = map:sub(1, 1 + (#map - dir))
|
||
|
end
|
||
|
|
||
|
-- Load map
|
||
|
map = setmetatable(assert(love.filesystem.load(map))(), Map)
|
||
|
end
|
||
|
|
||
|
map:init(dir, plugins, ox, oy)
|
||
|
|
||
|
return map
|
||
|
end
|
||
|
|
||
|
--- Instance a new map.
|
||
|
-- @param map Path to the map file or the map table itself
|
||
|
-- @param plugins A list of plugins to load
|
||
|
-- @param ox Offset of map on the X axis (in pixels)
|
||
|
-- @param oy Offset of map on the Y axis (in pixels)
|
||
|
-- @return table The loaded Map
|
||
|
function STI.__call(_, map, plugins, ox, oy)
|
||
|
return new(map, plugins, ox, oy)
|
||
|
end
|
||
|
|
||
|
--- Flush image cache.
|
||
|
function STI:flush()
|
||
|
self.cache = {}
|
||
|
end
|
||
|
|
||
|
--- Map object
|
||
|
|
||
|
--- Instance a new map
|
||
|
-- @param path Path to the map file
|
||
|
-- @param plugins A list of plugins to load
|
||
|
-- @param ox Offset of map on the X axis (in pixels)
|
||
|
-- @param oy Offset of map on the Y axis (in pixels)
|
||
|
function Map:init(path, plugins, ox, oy)
|
||
|
if type(plugins) == "table" then
|
||
|
self:loadPlugins(plugins)
|
||
|
end
|
||
|
|
||
|
self:resize()
|
||
|
self.objects = {}
|
||
|
self.tiles = {}
|
||
|
self.tileInstances = {}
|
||
|
self.drawRange = {
|
||
|
sx = 1,
|
||
|
sy = 1,
|
||
|
ex = self.width,
|
||
|
ey = self.height,
|
||
|
}
|
||
|
self.offsetx = ox or 0
|
||
|
self.offsety = oy or 0
|
||
|
|
||
|
self.freeBatchSprites = {}
|
||
|
setmetatable(self.freeBatchSprites, { __mode = 'k' })
|
||
|
|
||
|
-- Set tiles, images
|
||
|
local gid = 1
|
||
|
for i, tileset in ipairs(self.tilesets) do
|
||
|
assert(tileset.image, "STI does not support Tile Collections.\nYou need to create a Texture Atlas.")
|
||
|
|
||
|
-- Cache images
|
||
|
if lg.isCreated then
|
||
|
local formatted_path = utils.format_path(path .. tileset.image)
|
||
|
|
||
|
if not STI.cache[formatted_path] then
|
||
|
utils.fix_transparent_color(tileset, formatted_path)
|
||
|
utils.cache_image(STI, formatted_path, tileset.image)
|
||
|
else
|
||
|
tileset.image = STI.cache[formatted_path]
|
||
|
end
|
||
|
end
|
||
|
|
||
|
gid = self:setTiles(i, tileset, gid)
|
||
|
end
|
||
|
|
||
|
-- Set layers
|
||
|
for _, layer in ipairs(self.layers) do
|
||
|
self:setLayer(layer, path)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Load plugins
|
||
|
-- @param plugins A list of plugins to load
|
||
|
function Map:loadPlugins(plugins)
|
||
|
for _, plugin in ipairs(plugins) do
|
||
|
local pluginModulePath = cwd .. 'plugins.' .. plugin
|
||
|
local ok, pluginModule = pcall(require, pluginModulePath)
|
||
|
if ok then
|
||
|
for k, func in pairs(pluginModule) do
|
||
|
if not self[k] then
|
||
|
self[k] = func
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Create Tiles
|
||
|
-- @param index Index of the Tileset
|
||
|
-- @param tileset Tileset data
|
||
|
-- @param gid First Global ID in Tileset
|
||
|
-- @return number Next Tileset's first Global ID
|
||
|
function Map:setTiles(index, tileset, gid)
|
||
|
local quad = lg.newQuad
|
||
|
local imageW = tileset.imagewidth
|
||
|
local imageH = tileset.imageheight
|
||
|
local tileW = tileset.tilewidth
|
||
|
local tileH = tileset.tileheight
|
||
|
local margin = tileset.margin
|
||
|
local spacing = tileset.spacing
|
||
|
local w = utils.get_tiles(imageW, tileW, margin, spacing)
|
||
|
local h = utils.get_tiles(imageH, tileH, margin, spacing)
|
||
|
|
||
|
for y = 1, h do
|
||
|
for x = 1, w do
|
||
|
local id = gid - tileset.firstgid
|
||
|
local quadX = (x - 1) * tileW + margin + (x - 1) * spacing
|
||
|
local quadY = (y - 1) * tileH + margin + (y - 1) * spacing
|
||
|
local properties, terrain, animation, objectGroup
|
||
|
|
||
|
for _, tile in pairs(tileset.tiles) do
|
||
|
if tile.id == id then
|
||
|
properties = tile.properties
|
||
|
animation = tile.animation
|
||
|
objectGroup = tile.objectGroup
|
||
|
|
||
|
if tile.terrain then
|
||
|
terrain = {}
|
||
|
|
||
|
for i = 1, #tile.terrain do
|
||
|
terrain[i] = tileset.terrains[tile.terrain[i] + 1]
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local tile = {
|
||
|
id = id,
|
||
|
gid = gid,
|
||
|
tileset = index,
|
||
|
quad = quad(
|
||
|
quadX, quadY,
|
||
|
tileW, tileH,
|
||
|
imageW, imageH
|
||
|
),
|
||
|
properties = properties or {},
|
||
|
terrain = terrain,
|
||
|
animation = animation,
|
||
|
objectGroup = objectGroup,
|
||
|
frame = 1,
|
||
|
time = 0,
|
||
|
width = tileW,
|
||
|
height = tileH,
|
||
|
sx = 1,
|
||
|
sy = 1,
|
||
|
r = 0,
|
||
|
offset = tileset.tileoffset,
|
||
|
}
|
||
|
|
||
|
self.tiles[gid] = tile
|
||
|
gid = gid + 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return gid
|
||
|
end
|
||
|
|
||
|
--- Create Layers
|
||
|
-- @param layer Layer data
|
||
|
-- @param path (Optional) Path to an Image Layer's image
|
||
|
function Map:setLayer(layer, path)
|
||
|
if layer.encoding then
|
||
|
if layer.encoding == "base64" then
|
||
|
assert(require "ffi", "Compressed maps require LuaJIT FFI.\nPlease Switch your interperator to LuaJIT or your Tile Layer Format to \"CSV\".")
|
||
|
local fd = love.filesystem.newFileData(layer.data, "data", "base64"):getString()
|
||
|
|
||
|
if not layer.compression then
|
||
|
layer.data = utils.get_decompressed_data(fd)
|
||
|
else
|
||
|
assert(love.math.decompress, "zlib and gzip compression require LOVE 0.10.0+.\nPlease set your Tile Layer Format to \"Base64 (uncompressed)\" or \"CSV\".")
|
||
|
|
||
|
if layer.compression == "zlib" then
|
||
|
local data = love.math.decompress(fd, "zlib")
|
||
|
layer.data = utils.get_decompressed_data(data)
|
||
|
end
|
||
|
|
||
|
if layer.compression == "gzip" then
|
||
|
local data = love.math.decompress(fd, "gzip")
|
||
|
layer.data = utils.get_decompressed_data(data)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
layer.x = (layer.x or 0) + layer.offsetx + self.offsetx
|
||
|
layer.y = (layer.y or 0) + layer.offsety + self.offsety
|
||
|
layer.update = function() end
|
||
|
|
||
|
if layer.type == "tilelayer" then
|
||
|
self:setTileData(layer)
|
||
|
self:setSpriteBatches(layer)
|
||
|
layer.draw = function() self:drawTileLayer(layer) end
|
||
|
elseif layer.type == "objectgroup" then
|
||
|
self:setObjectData(layer)
|
||
|
self:setObjectCoordinates(layer)
|
||
|
self:setObjectSpriteBatches(layer)
|
||
|
layer.draw = function() self:drawObjectLayer(layer) end
|
||
|
elseif layer.type == "imagelayer" then
|
||
|
layer.draw = function() self:drawImageLayer(layer) end
|
||
|
|
||
|
if layer.image ~= "" then
|
||
|
local formatted_path = utils.format_path(path .. layer.image)
|
||
|
if not STI.cache[formatted_path] then
|
||
|
utils.cache_image(STI, formatted_path)
|
||
|
end
|
||
|
|
||
|
layer.image = STI.cache[formatted_path]
|
||
|
layer.width = layer.image:getWidth()
|
||
|
layer.height = layer.image:getHeight()
|
||
|
end
|
||
|
end
|
||
|
|
||
|
self.layers[layer.name] = layer
|
||
|
end
|
||
|
|
||
|
--- Add Tiles to Tile Layer
|
||
|
-- @param layer The Tile Layer
|
||
|
function Map:setTileData(layer)
|
||
|
local i = 1
|
||
|
local map = {}
|
||
|
|
||
|
for y = 1, layer.height do
|
||
|
map[y] = {}
|
||
|
for x = 1, layer.width do
|
||
|
local gid = layer.data[i]
|
||
|
|
||
|
if gid > 0 then
|
||
|
map[y][x] = self.tiles[gid] or self:setFlippedGID(gid)
|
||
|
end
|
||
|
|
||
|
i = i + 1
|
||
|
end
|
||
|
end
|
||
|
|
||
|
layer.data = map
|
||
|
end
|
||
|
|
||
|
--- Add Objects to Layer
|
||
|
-- @param layer The Object Layer
|
||
|
function Map:setObjectData(layer)
|
||
|
for _, object in ipairs(layer.objects) do
|
||
|
object.layer = layer
|
||
|
self.objects[object.id] = object
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Correct position and orientation of Objects in an Object Layer
|
||
|
-- @param layer The Object Layer
|
||
|
function Map:setObjectCoordinates(layer)
|
||
|
for _, object in ipairs(layer.objects) do
|
||
|
local x = layer.x + object.x
|
||
|
local y = layer.y + object.y
|
||
|
local w = object.width
|
||
|
local h = object.height
|
||
|
local r = object.rotation
|
||
|
local cos = math.cos(math.rad(r))
|
||
|
local sin = math.sin(math.rad(r))
|
||
|
|
||
|
if object.shape == "rectangle" and not object.gid then
|
||
|
object.rectangle = {}
|
||
|
|
||
|
local vertices = {
|
||
|
{ x=x, y=y },
|
||
|
{ x=x + w, y=y },
|
||
|
{ x=x + w, y=y + h },
|
||
|
{ x=x, y=y + h },
|
||
|
}
|
||
|
|
||
|
for _, vertex in ipairs(vertices) do
|
||
|
vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
|
||
|
table.insert(object.rectangle, { x = vertex.x, y = vertex.y })
|
||
|
end
|
||
|
elseif object.shape == "ellipse" then
|
||
|
object.ellipse = {}
|
||
|
local vertices = utils.convert_ellipse_to_polygon(x, y, w, h)
|
||
|
|
||
|
for _, vertex in ipairs(vertices) do
|
||
|
vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
|
||
|
table.insert(object.ellipse, { x = vertex.x, y = vertex.y })
|
||
|
end
|
||
|
elseif object.shape == "polygon" then
|
||
|
for _, vertex in ipairs(object.polygon) do
|
||
|
vertex.x = vertex.x + x
|
||
|
vertex.y = vertex.y + y
|
||
|
vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
|
||
|
end
|
||
|
elseif object.shape == "polyline" then
|
||
|
for _, vertex in ipairs(object.polyline) do
|
||
|
vertex.x = vertex.x + x
|
||
|
vertex.y = vertex.y + y
|
||
|
vertex.x, vertex.y = utils.rotate_vertex(self, vertex, x, y, cos, sin)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Convert tile location to tile instance location
|
||
|
-- @param layer Tile layer
|
||
|
-- @param tile Tile
|
||
|
-- @param x Tile location on X axis (in tiles)
|
||
|
-- @param y Tile location on Y axis (in tiles)
|
||
|
-- @return number Tile instance location on X axis (in pixels)
|
||
|
-- @return number Tile instance location on Y axis (in pixels)
|
||
|
function Map:getLayerTilePosition(layer, tile, x, y)
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local tileX, tileY
|
||
|
|
||
|
if self.orientation == "orthogonal" then
|
||
|
local tileset = self.tilesets[tile.tileset]
|
||
|
tileX = (x - 1) * tileW + tile.offset.x
|
||
|
tileY = (y - 0) * tileH + tile.offset.y - tileset.tileheight
|
||
|
tileX, tileY = utils.compensate(tile, tileX, tileY, tileW, tileH)
|
||
|
elseif self.orientation == "isometric" then
|
||
|
tileX = (x - y) * (tileW / 2) + tile.offset.x + layer.width * tileW / 2 - self.tilewidth / 2
|
||
|
tileY = (x + y - 2) * (tileH / 2) + tile.offset.y
|
||
|
else
|
||
|
local sideLen = self.hexsidelength or 0
|
||
|
if self.staggeraxis == "y" then
|
||
|
if self.staggerindex == "odd" then
|
||
|
if y % 2 == 0 then
|
||
|
tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
|
||
|
else
|
||
|
tileX = (x - 1) * tileW + tile.offset.x
|
||
|
end
|
||
|
else
|
||
|
if y % 2 == 0 then
|
||
|
tileX = (x - 1) * tileW + tile.offset.x
|
||
|
else
|
||
|
tileX = (x - 1) * tileW + tileW / 2 + tile.offset.x
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local rowH = tileH - (tileH - sideLen) / 2
|
||
|
tileY = (y - 1) * rowH + tile.offset.y
|
||
|
else
|
||
|
if self.staggerindex == "odd" then
|
||
|
if x % 2 == 0 then
|
||
|
tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
|
||
|
else
|
||
|
tileY = (y - 1) * tileH + tile.offset.y
|
||
|
end
|
||
|
else
|
||
|
if x % 2 == 0 then
|
||
|
tileY = (y - 1) * tileH + tile.offset.y
|
||
|
else
|
||
|
tileY = (y - 1) * tileH + tileH / 2 + tile.offset.y
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local colW = tileW - (tileW - sideLen) / 2
|
||
|
tileX = (x - 1) * colW + tile.offset.x
|
||
|
end
|
||
|
end
|
||
|
|
||
|
return tileX, tileY
|
||
|
end
|
||
|
|
||
|
--- Place new tile instance
|
||
|
-- @param layer Tile layer
|
||
|
-- @param tile Tile
|
||
|
-- @param number Tile location on X axis (in tiles)
|
||
|
-- @param number Tile location on Y axis (in tiles)
|
||
|
function Map:addNewLayerTile(layer, tile, x, y)
|
||
|
local tileset = tile.tileset
|
||
|
local image = self.tilesets[tile.tileset].image
|
||
|
|
||
|
layer.batches[tileset] = layer.batches[tileset]
|
||
|
or lg.newSpriteBatch(image, layer.width * layer.height)
|
||
|
|
||
|
local batch = layer.batches[tileset]
|
||
|
local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
|
||
|
|
||
|
local tab = {
|
||
|
layer = layer,
|
||
|
gid = tile.gid,
|
||
|
x = tileX,
|
||
|
y = tileY,
|
||
|
r = tile.r,
|
||
|
oy = 0
|
||
|
}
|
||
|
|
||
|
if batch then
|
||
|
tab.batch = batch
|
||
|
tab.id = batch:add(tile.quad, tileX, tileY, tile.r, tile.sx, tile.sy)
|
||
|
end
|
||
|
|
||
|
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
|
||
|
table.insert(self.tileInstances[tile.gid], tab)
|
||
|
end
|
||
|
|
||
|
--- Batch Tiles in Tile Layer for improved draw speed
|
||
|
-- @param layer The Tile Layer
|
||
|
function Map:setSpriteBatches(layer)
|
||
|
layer.batches = {}
|
||
|
|
||
|
if self.orientation == "orthogonal" or self.orientation == "isometric" then
|
||
|
local startX = 1
|
||
|
local startY = 1
|
||
|
local endX = layer.width
|
||
|
local endY = layer.height
|
||
|
local incrementX = 1
|
||
|
local incrementY = 1
|
||
|
|
||
|
-- Determine order to add tiles to sprite batch
|
||
|
-- Defaults to right-down
|
||
|
if self.renderorder == "right-up" then
|
||
|
startX, endX, incrementX = startX, endX, 1
|
||
|
startY, endY, incrementY = endY, startY, -1
|
||
|
elseif self.renderorder == "left-down" then
|
||
|
startX, endX, incrementX = endX, startX, -1
|
||
|
startY, endY, incrementY = startY, endY, 1
|
||
|
elseif self.renderorder == "left-up" then
|
||
|
startX, endX, incrementX = endX, startX, -1
|
||
|
startY, endY, incrementY = endY, startY, -1
|
||
|
end
|
||
|
|
||
|
for y = startY, endY, incrementY do
|
||
|
for x = startX, endX, incrementX do
|
||
|
local tile = layer.data[y][x]
|
||
|
|
||
|
if tile then
|
||
|
self:addNewLayerTile(layer, tile, x, y)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
local sideLen = self.hexsidelength or 0
|
||
|
|
||
|
if self.staggeraxis == "y" then
|
||
|
for y = 1, layer.height do
|
||
|
for x = 1, layer.width do
|
||
|
local tile = layer.data[y][x]
|
||
|
|
||
|
if tile then
|
||
|
self:addNewLayerTile(layer, tile, x, y)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
local i = 0
|
||
|
local _x
|
||
|
|
||
|
if self.staggerindex == "odd" then
|
||
|
_x = 1
|
||
|
else
|
||
|
_x = 2
|
||
|
end
|
||
|
|
||
|
while i < layer.width * layer.height do
|
||
|
for _y = 1, layer.height + 0.5, 0.5 do
|
||
|
local y = floor(_y)
|
||
|
|
||
|
for x = _x, layer.width, 2 do
|
||
|
i = i + 1
|
||
|
local tile = layer.data[y][x]
|
||
|
|
||
|
if tile then
|
||
|
self:addNewLayerTile(layer, tile, x, y)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if _x == 1 then
|
||
|
_x = 2
|
||
|
else
|
||
|
_x = 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Batch Tiles in Object Layer for improved draw speed
|
||
|
-- @param layer The Object Layer
|
||
|
function Map:setObjectSpriteBatches(layer)
|
||
|
local newBatch = lg.newSpriteBatch
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local batches = {}
|
||
|
|
||
|
if layer.draworder == "topdown" then
|
||
|
table.sort(layer.objects, function(a, b)
|
||
|
return a.y + a.height < b.y + b.height
|
||
|
end)
|
||
|
end
|
||
|
|
||
|
for _, object in ipairs(layer.objects) do
|
||
|
if object.gid then
|
||
|
local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid)
|
||
|
local tileset = tile.tileset
|
||
|
local image = self.tilesets[tile.tileset].image
|
||
|
|
||
|
batches[tileset] = batches[tileset] or newBatch(image)
|
||
|
|
||
|
local batch = batches[tileset]
|
||
|
local tileX = object.x + tile.offset.x
|
||
|
local tileY = object.y + tile.offset.y - tile.height
|
||
|
local tileR = math.rad(object.rotation)
|
||
|
local oy = 0
|
||
|
|
||
|
-- Compensation for scale/rotation shift
|
||
|
if tile.sx == 1 and tile.sy == 1 then
|
||
|
if tileR ~= 0 then
|
||
|
tileY = tileY + tileH
|
||
|
oy = tileH
|
||
|
end
|
||
|
else
|
||
|
if tile.sx < 0 then tileX = tileX + tileW end
|
||
|
if tile.sy < 0 then tileY = tileY + tileH end
|
||
|
if tileR > 0 then tileX = tileX + tileW end
|
||
|
if tileR < 0 then tileY = tileY + tileH end
|
||
|
end
|
||
|
|
||
|
local tab = {
|
||
|
layer = layer,
|
||
|
gid = tile.gid,
|
||
|
x = tileX,
|
||
|
y = tileY,
|
||
|
r = tileR,
|
||
|
oy = oy
|
||
|
}
|
||
|
|
||
|
if batch then
|
||
|
tab.batch = batch
|
||
|
tab.id = batch:add(tile.quad, tileX, tileY, tileR, tile.sx, tile.sy, 0, oy)
|
||
|
end
|
||
|
|
||
|
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
|
||
|
table.insert(self.tileInstances[tile.gid], tab)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
layer.batches = batches
|
||
|
end
|
||
|
|
||
|
--- Create a Custom Layer to place userdata in (such as player sprites)
|
||
|
-- @param name Name of Custom Layer
|
||
|
-- @param index Draw order within Layer stack
|
||
|
-- @return table Custom Layer
|
||
|
function Map:addCustomLayer(name, index)
|
||
|
index = index or #self.layers + 1
|
||
|
local layer = {
|
||
|
type = "customlayer",
|
||
|
name = name,
|
||
|
visible = true,
|
||
|
opacity = 1,
|
||
|
properties = {},
|
||
|
}
|
||
|
|
||
|
function layer.draw() end
|
||
|
function layer.update() end
|
||
|
|
||
|
table.insert(self.layers, index, layer)
|
||
|
self.layers[name] = self.layers[index]
|
||
|
|
||
|
return layer
|
||
|
end
|
||
|
|
||
|
--- Convert another Layer into a Custom Layer
|
||
|
-- @param index Index or name of Layer to convert
|
||
|
-- @return table Custom Layer
|
||
|
function Map:convertToCustomLayer(index)
|
||
|
local layer = assert(self.layers[index], "Layer not found: " .. index)
|
||
|
|
||
|
layer.type = "customlayer"
|
||
|
layer.x = nil
|
||
|
layer.y = nil
|
||
|
layer.width = nil
|
||
|
layer.height = nil
|
||
|
layer.encoding = nil
|
||
|
layer.data = nil
|
||
|
layer.objects = nil
|
||
|
layer.image = nil
|
||
|
|
||
|
function layer.draw() end
|
||
|
function layer.update() end
|
||
|
|
||
|
return layer
|
||
|
end
|
||
|
|
||
|
--- Remove a Layer from the Layer stack
|
||
|
-- @param index Index or name of Layer to convert
|
||
|
function Map:removeLayer(index)
|
||
|
local layer = assert(self.layers[index], "Layer not found: " .. index)
|
||
|
|
||
|
if type(index) == "string" then
|
||
|
for i, l in ipairs(self.layers) do
|
||
|
if l.name == index then
|
||
|
table.remove(self.layers, i)
|
||
|
self.layers[index] = nil
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
else
|
||
|
local name = self.layers[index].name
|
||
|
table.remove(self.layers, index)
|
||
|
self.layers[name] = nil
|
||
|
end
|
||
|
|
||
|
-- Remove tile instances
|
||
|
if layer.batches then
|
||
|
for _, batch in pairs(layer.batches) do
|
||
|
self.freeBatchSprites[batch] = nil
|
||
|
end
|
||
|
|
||
|
for _, tiles in pairs(self.tileInstances) do
|
||
|
for i = #tiles, 1, -1 do
|
||
|
local tile = tiles[i]
|
||
|
if tile.layer == layer then
|
||
|
table.remove(tiles, i)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Remove objects
|
||
|
if layer.objects then
|
||
|
for i, object in pairs(self.objects) do
|
||
|
if object.layer == layer then
|
||
|
self.objects[i] = nil
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Animate Tiles and update every Layer
|
||
|
-- @param dt Delta Time
|
||
|
function Map:update(dt)
|
||
|
for _, tile in pairs(self.tiles) do
|
||
|
local update = false
|
||
|
|
||
|
if tile.animation then
|
||
|
tile.time = tile.time + dt * 1000
|
||
|
|
||
|
while tile.time > tonumber(tile.animation[tile.frame].duration) do
|
||
|
update = true
|
||
|
tile.time = tile.time - tonumber(tile.animation[tile.frame].duration)
|
||
|
tile.frame = tile.frame + 1
|
||
|
|
||
|
if tile.frame > #tile.animation then tile.frame = 1 end
|
||
|
end
|
||
|
|
||
|
if update and self.tileInstances[tile.gid] then
|
||
|
for _, j in pairs(self.tileInstances[tile.gid]) do
|
||
|
local t = self.tiles[tonumber(tile.animation[tile.frame].tileid) + self.tilesets[tile.tileset].firstgid]
|
||
|
j.batch:set(j.id, t.quad, j.x, j.y, j.r, tile.sx, tile.sy, 0, j.oy)
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
for _, layer in ipairs(self.layers) do
|
||
|
layer:update(dt)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Draw every Layer
|
||
|
-- @param tx Translate on X
|
||
|
-- @param ty Translate on Y
|
||
|
-- @param sx Scale on X
|
||
|
-- @param sy Scale on Y
|
||
|
function Map:draw(tx, ty, sx, sy)
|
||
|
local current_canvas = lg.getCanvas()
|
||
|
lg.setCanvas(self.canvas)
|
||
|
lg.clear()
|
||
|
|
||
|
-- Scale map to 1.0 to draw onto canvas, this fixes tearing issues
|
||
|
-- Map is translated to correct position so the right section is drawn
|
||
|
lg.push()
|
||
|
lg.origin()
|
||
|
lg.translate(math.floor(tx or 0), math.floor(ty or 0))
|
||
|
|
||
|
for _, layer in ipairs(self.layers) do
|
||
|
if layer.visible and layer.opacity > 0 then
|
||
|
self:drawLayer(layer)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
lg.pop()
|
||
|
|
||
|
-- Draw canvas at 0,0; this fixes scissoring issues
|
||
|
-- Map is scaled to correct scale so the right section is shown
|
||
|
lg.push()
|
||
|
lg.origin()
|
||
|
lg.scale(sx or 1, sy or sx or 1)
|
||
|
|
||
|
lg.setCanvas(current_canvas)
|
||
|
lg.draw(self.canvas)
|
||
|
|
||
|
lg.pop()
|
||
|
end
|
||
|
|
||
|
--- Draw an individual Layer
|
||
|
-- @param layer The Layer to draw
|
||
|
function Map.drawLayer(_, layer)
|
||
|
local r,g,b,a = lg.getColor()
|
||
|
lg.setColor(r, g, b, a * layer.opacity)
|
||
|
layer:draw()
|
||
|
lg.setColor(r,g,b,a)
|
||
|
end
|
||
|
|
||
|
--- Default draw function for Tile Layers
|
||
|
-- @param layer The Tile Layer to draw
|
||
|
function Map:drawTileLayer(layer)
|
||
|
if type(layer) == "string" or type(layer) == "number" then
|
||
|
layer = self.layers[layer]
|
||
|
end
|
||
|
|
||
|
assert(layer.type == "tilelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: tilelayer")
|
||
|
|
||
|
for _, batch in pairs(layer.batches) do
|
||
|
lg.draw(batch, floor(layer.x), floor(layer.y))
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Default draw function for Object Layers
|
||
|
-- @param layer The Object Layer to draw
|
||
|
function Map:drawObjectLayer(layer)
|
||
|
if type(layer) == "string" or type(layer) == "number" then
|
||
|
layer = self.layers[layer]
|
||
|
end
|
||
|
|
||
|
assert(layer.type == "objectgroup", "Invalid layer type: " .. layer.type .. ". Layer must be of type: objectgroup")
|
||
|
|
||
|
local line = { 160, 160, 160, 255 * layer.opacity }
|
||
|
local fill = { 160, 160, 160, 255 * layer.opacity * 0.5 }
|
||
|
local r,g,b,a = lg.getColor()
|
||
|
local reset = { r, g, b, a * layer.opacity }
|
||
|
|
||
|
local function sortVertices(obj)
|
||
|
local vertex = {}
|
||
|
|
||
|
for _, v in ipairs(obj) do
|
||
|
table.insert(vertex, v.x)
|
||
|
table.insert(vertex, v.y)
|
||
|
end
|
||
|
|
||
|
return vertex
|
||
|
end
|
||
|
|
||
|
local function drawShape(obj, shape)
|
||
|
local vertex = sortVertices(obj)
|
||
|
|
||
|
if shape == "polyline" then
|
||
|
lg.setColor(line)
|
||
|
lg.line(vertex)
|
||
|
return
|
||
|
elseif shape == "polygon" then
|
||
|
lg.setColor(fill)
|
||
|
if not love.math.isConvex(vertex) then
|
||
|
local triangles = love.math.triangulate(vertex)
|
||
|
for _, triangle in ipairs(triangles) do
|
||
|
lg.polygon("fill", triangle)
|
||
|
end
|
||
|
else
|
||
|
lg.polygon("fill", vertex)
|
||
|
end
|
||
|
else
|
||
|
lg.setColor(fill)
|
||
|
lg.polygon("fill", vertex)
|
||
|
end
|
||
|
|
||
|
lg.setColor(line)
|
||
|
lg.polygon("line", vertex)
|
||
|
end
|
||
|
|
||
|
for _, object in ipairs(layer.objects) do
|
||
|
if object.shape == "rectangle" and not object.gid then
|
||
|
drawShape(object.rectangle, "rectangle")
|
||
|
elseif object.shape == "ellipse" then
|
||
|
drawShape(object.ellipse, "ellipse")
|
||
|
elseif object.shape == "polygon" then
|
||
|
drawShape(object.polygon, "polygon")
|
||
|
elseif object.shape == "polyline" then
|
||
|
drawShape(object.polyline, "polyline")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
lg.setColor(reset)
|
||
|
for _, batch in pairs(layer.batches) do
|
||
|
lg.draw(batch, 0, 0)
|
||
|
end
|
||
|
lg.setColor(r,g,b,a)
|
||
|
end
|
||
|
|
||
|
--- Default draw function for Image Layers
|
||
|
-- @param layer The Image Layer to draw
|
||
|
function Map:drawImageLayer(layer)
|
||
|
if type(layer) == "string" or type(layer) == "number" then
|
||
|
layer = self.layers[layer]
|
||
|
end
|
||
|
|
||
|
assert(layer.type == "imagelayer", "Invalid layer type: " .. layer.type .. ". Layer must be of type: imagelayer")
|
||
|
|
||
|
if layer.image ~= "" then
|
||
|
lg.draw(layer.image, layer.x, layer.y)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Resize the drawable area of the Map
|
||
|
-- @param w The new width of the drawable area (in pixels)
|
||
|
-- @param h The new Height of the drawable area (in pixels)
|
||
|
function Map:resize(w, h)
|
||
|
if lg.isCreated then
|
||
|
w = w or lg.getWidth()
|
||
|
h = h or lg.getHeight()
|
||
|
|
||
|
self.canvas = lg.newCanvas(w, h)
|
||
|
self.canvas:setFilter("nearest", "nearest")
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Create flipped or rotated Tiles based on bitop flags
|
||
|
-- @param gid The flagged Global ID
|
||
|
-- @return table Flipped Tile
|
||
|
function Map:setFlippedGID(gid)
|
||
|
local bit31 = 2147483648
|
||
|
local bit30 = 1073741824
|
||
|
local bit29 = 536870912
|
||
|
local flipX = false
|
||
|
local flipY = false
|
||
|
local flipD = false
|
||
|
local realgid = gid
|
||
|
|
||
|
if realgid >= bit31 then
|
||
|
realgid = realgid - bit31
|
||
|
flipX = not flipX
|
||
|
end
|
||
|
|
||
|
if realgid >= bit30 then
|
||
|
realgid = realgid - bit30
|
||
|
flipY = not flipY
|
||
|
end
|
||
|
|
||
|
if realgid >= bit29 then
|
||
|
realgid = realgid - bit29
|
||
|
flipD = not flipD
|
||
|
end
|
||
|
|
||
|
local tile = self.tiles[realgid]
|
||
|
local data = {
|
||
|
id = tile.id,
|
||
|
gid = gid,
|
||
|
tileset = tile.tileset,
|
||
|
frame = tile.frame,
|
||
|
time = tile.time,
|
||
|
width = tile.width,
|
||
|
height = tile.height,
|
||
|
offset = tile.offset,
|
||
|
quad = tile.quad,
|
||
|
properties = tile.properties,
|
||
|
terrain = tile.terrain,
|
||
|
animation = tile.animation,
|
||
|
sx = tile.sx,
|
||
|
sy = tile.sy,
|
||
|
r = tile.r,
|
||
|
}
|
||
|
|
||
|
if flipX then
|
||
|
if flipY and flipD then
|
||
|
data.r = math.rad(-90)
|
||
|
data.sy = -1
|
||
|
elseif flipY then
|
||
|
data.sx = -1
|
||
|
data.sy = -1
|
||
|
elseif flipD then
|
||
|
data.r = math.rad(90)
|
||
|
else
|
||
|
data.sx = -1
|
||
|
end
|
||
|
elseif flipY then
|
||
|
if flipD then
|
||
|
data.r = math.rad(-90)
|
||
|
else
|
||
|
data.sy = -1
|
||
|
end
|
||
|
elseif flipD then
|
||
|
data.r = math.rad(90)
|
||
|
data.sy = -1
|
||
|
end
|
||
|
|
||
|
self.tiles[gid] = data
|
||
|
|
||
|
return self.tiles[gid]
|
||
|
end
|
||
|
|
||
|
--- Get custom properties from Layer
|
||
|
-- @param layer The Layer
|
||
|
-- @return table List of properties
|
||
|
function Map:getLayerProperties(layer)
|
||
|
local l = self.layers[layer]
|
||
|
|
||
|
if not l then
|
||
|
return {}
|
||
|
end
|
||
|
|
||
|
return l.properties
|
||
|
end
|
||
|
|
||
|
--- Get custom properties from Tile
|
||
|
-- @param layer The Layer that the Tile belongs to
|
||
|
-- @param x The X axis location of the Tile (in tiles)
|
||
|
-- @param y The Y axis location of the Tile (in tiles)
|
||
|
-- @return table List of properties
|
||
|
function Map:getTileProperties(layer, x, y)
|
||
|
local tile = self.layers[layer].data[y][x]
|
||
|
|
||
|
if not tile then
|
||
|
return {}
|
||
|
end
|
||
|
|
||
|
return tile.properties
|
||
|
end
|
||
|
|
||
|
--- Get custom properties from Object
|
||
|
-- @param layer The Layer that the Object belongs to
|
||
|
-- @param object The index or name of the Object
|
||
|
-- @return table List of properties
|
||
|
function Map:getObjectProperties(layer, object)
|
||
|
local o = self.layers[layer].objects
|
||
|
|
||
|
if type(object) == "number" then
|
||
|
o = o[object]
|
||
|
else
|
||
|
for _, v in ipairs(o) do
|
||
|
if v.name == object then
|
||
|
o = v
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if not o then
|
||
|
return {}
|
||
|
end
|
||
|
|
||
|
return o.properties
|
||
|
end
|
||
|
|
||
|
--- Change a tile in a layer to another tile
|
||
|
-- @param layer The Layer that the Tile belongs to
|
||
|
-- @param x The X axis location of the Tile (in tiles)
|
||
|
-- @param y The Y axis location of the Tile (in tiles)
|
||
|
-- @param gid The gid of the new tile
|
||
|
function Map:setLayerTile(layer, x, y, gid)
|
||
|
layer = self.layers[layer]
|
||
|
|
||
|
layer.data[y] = layer.data[y] or {}
|
||
|
local tile = layer.data[y][x]
|
||
|
local instance
|
||
|
if tile then
|
||
|
local tileX, tileY = self:getLayerTilePosition(layer, tile, x, y)
|
||
|
for _, inst in pairs(self.tileInstances[tile.gid]) do
|
||
|
if inst.x == tileX and inst.y == tileY then
|
||
|
instance = inst
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
if tile == self.tiles[gid] then
|
||
|
return
|
||
|
end
|
||
|
|
||
|
tile = self.tiles[gid]
|
||
|
|
||
|
if instance then
|
||
|
self:swapTile(instance, tile)
|
||
|
else
|
||
|
self:addNewLayerTile(layer, tile, x, y)
|
||
|
end
|
||
|
layer.data[y][x] = tile
|
||
|
end
|
||
|
|
||
|
--- Swap a tile in a spritebatch
|
||
|
-- @param instance The current Instance object we want to replace
|
||
|
-- @param tile The Tile object we want to use
|
||
|
-- @return none
|
||
|
function Map:swapTile(instance, tile)
|
||
|
-- Update sprite batch
|
||
|
if instance.batch then
|
||
|
if tile then
|
||
|
instance.batch:set(
|
||
|
instance.id,
|
||
|
tile.quad,
|
||
|
instance.x,
|
||
|
instance.y,
|
||
|
tile.r,
|
||
|
tile.sx,
|
||
|
tile.sy
|
||
|
)
|
||
|
else
|
||
|
instance.batch:set(
|
||
|
instance.id,
|
||
|
instance.x,
|
||
|
instance.y,
|
||
|
0,
|
||
|
0)
|
||
|
|
||
|
self.freeBatchSprites[instance.batch] =
|
||
|
self.freeBatchSprites[instance.batch] or {}
|
||
|
|
||
|
table.insert(self.freeBatchSprites[instance.batch], instance)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Remove old tile instance
|
||
|
for i, ins in ipairs(self.tileInstances[instance.gid]) do
|
||
|
if ins.batch == instance.batch and ins.id == instance.id then
|
||
|
table.remove(self.tileInstances[instance.gid], i)
|
||
|
break
|
||
|
end
|
||
|
end
|
||
|
|
||
|
-- Add new tile instance
|
||
|
if tile then
|
||
|
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
|
||
|
|
||
|
local freeBatchSprites = self.freeBatchSprites[instance.batch]
|
||
|
local newInstance
|
||
|
if freeBatchSprites and #freeBatchSprites > 0 then
|
||
|
newInstance = freeBatchSprites[#freeBatchSprites]
|
||
|
freeBatchSprites[#freeBatchSprites] = nil
|
||
|
else
|
||
|
newInstance = {}
|
||
|
end
|
||
|
|
||
|
newInstance.layer = instance.layer
|
||
|
newInstance.batch = instance.batch
|
||
|
newInstance.id = instance.id
|
||
|
newInstance.gid = tile.gid or 0
|
||
|
newInstance.x = instance.x
|
||
|
newInstance.y = instance.y
|
||
|
newInstance.r = tile.r or 0
|
||
|
newInstance.oy = tile.r ~= 0 and tile.height or 0
|
||
|
table.insert(self.tileInstances[tile.gid], newInstance)
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Convert tile location to pixel location
|
||
|
-- @param x The X axis location of the point (in tiles)
|
||
|
-- @param y The Y axis location of the point (in tiles)
|
||
|
-- @return number The X axis location of the point (in pixels)
|
||
|
-- @return number The Y axis location of the point (in pixels)
|
||
|
function Map:convertTileToPixel(x,y)
|
||
|
if self.orientation == "orthogonal" then
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
return
|
||
|
x * tileW,
|
||
|
y * tileH
|
||
|
elseif self.orientation == "isometric" then
|
||
|
local mapH = self.height
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local offsetX = mapH * tileW / 2
|
||
|
return
|
||
|
(x - y) * tileW / 2 + offsetX,
|
||
|
(x + y) * tileH / 2
|
||
|
elseif self.orientation == "staggered" or
|
||
|
self.orientation == "hexagonal" then
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local sideLen = self.hexsidelength or 0
|
||
|
|
||
|
if self.staggeraxis == "x" then
|
||
|
return
|
||
|
x * tileW,
|
||
|
ceil(y) * (tileH + sideLen) + (ceil(y) % 2 == 0 and tileH or 0)
|
||
|
else
|
||
|
return
|
||
|
ceil(x) * (tileW + sideLen) + (ceil(x) % 2 == 0 and tileW or 0),
|
||
|
y * tileH
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- Convert pixel location to tile location
|
||
|
-- @param x The X axis location of the point (in pixels)
|
||
|
-- @param y The Y axis location of the point (in pixels)
|
||
|
-- @return number The X axis location of the point (in tiles)
|
||
|
-- @return number The Y axis location of the point (in tiles)
|
||
|
function Map:convertPixelToTile(x, y)
|
||
|
if self.orientation == "orthogonal" then
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
return
|
||
|
x / tileW,
|
||
|
y / tileH
|
||
|
elseif self.orientation == "isometric" then
|
||
|
local mapH = self.height
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local offsetX = mapH * tileW / 2
|
||
|
return
|
||
|
y / tileH + (x - offsetX) / tileW,
|
||
|
y / tileH - (x - offsetX) / tileW
|
||
|
elseif self.orientation == "staggered" then
|
||
|
local staggerX = self.staggeraxis == "x"
|
||
|
local even = self.staggerindex == "even"
|
||
|
|
||
|
local function topLeft(x, y)
|
||
|
if staggerX then
|
||
|
if ceil(x) % 2 == 1 and even then
|
||
|
return x - 1, y
|
||
|
else
|
||
|
return x - 1, y - 1
|
||
|
end
|
||
|
else
|
||
|
if ceil(y) % 2 == 1 and even then
|
||
|
return x, y - 1
|
||
|
else
|
||
|
return x - 1, y - 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function topRight(x, y)
|
||
|
if staggerX then
|
||
|
if ceil(x) % 2 == 1 and even then
|
||
|
return x + 1, y
|
||
|
else
|
||
|
return x + 1, y - 1
|
||
|
end
|
||
|
else
|
||
|
if ceil(y) % 2 == 1 and even then
|
||
|
return x + 1, y - 1
|
||
|
else
|
||
|
return x, y - 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function bottomLeft(x, y)
|
||
|
if staggerX then
|
||
|
if ceil(x) % 2 == 1 and even then
|
||
|
return x - 1, y + 1
|
||
|
else
|
||
|
return x - 1, y
|
||
|
end
|
||
|
else
|
||
|
if ceil(y) % 2 == 1 and even then
|
||
|
return x, y + 1
|
||
|
else
|
||
|
return x - 1, y + 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local function bottomRight(x, y)
|
||
|
if staggerX then
|
||
|
if ceil(x) % 2 == 1 and even then
|
||
|
return x + 1, y + 1
|
||
|
else
|
||
|
return x + 1, y
|
||
|
end
|
||
|
else
|
||
|
if ceil(y) % 2 == 1 and even then
|
||
|
return x + 1, y + 1
|
||
|
else
|
||
|
return x, y + 1
|
||
|
end
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
|
||
|
if staggerX then
|
||
|
x = x - (even and tileW / 2 or 0)
|
||
|
else
|
||
|
y = y - (even and tileH / 2 or 0)
|
||
|
end
|
||
|
|
||
|
local halfH = tileH / 2
|
||
|
local ratio = tileH / tileW
|
||
|
local referenceX = ceil(x / tileW)
|
||
|
local referenceY = ceil(y / tileH)
|
||
|
local relativeX = x - referenceX * tileW
|
||
|
local relativeY = y - referenceY * tileH
|
||
|
|
||
|
if (halfH - relativeX * ratio > relativeY) then
|
||
|
return topLeft(referenceX, referenceY)
|
||
|
elseif (-halfH + relativeX * ratio > relativeY) then
|
||
|
return topRight(referenceX, referenceY)
|
||
|
elseif (halfH + relativeX * ratio < relativeY) then
|
||
|
return bottomLeft(referenceX, referenceY)
|
||
|
elseif (halfH * 3 - relativeX * ratio < relativeY) then
|
||
|
return bottomRight(referenceX, referenceY)
|
||
|
end
|
||
|
|
||
|
return referenceX, referenceY
|
||
|
elseif self.orientation == "hexagonal" then
|
||
|
local staggerX = self.staggeraxis == "x"
|
||
|
local even = self.staggerindex == "even"
|
||
|
local tileW = self.tilewidth
|
||
|
local tileH = self.tileheight
|
||
|
local sideLenX = 0
|
||
|
local sideLenY = 0
|
||
|
|
||
|
if staggerX then
|
||
|
sideLenX = self.hexsidelength
|
||
|
x = x - (even and tileW or (tileW - sideLenX) / 2)
|
||
|
else
|
||
|
sideLenY = self.hexsidelength
|
||
|
y = y - (even and tileH or (tileH - sideLenY) / 2)
|
||
|
end
|
||
|
|
||
|
local colW = ((tileW - sideLenX) / 2) + sideLenX
|
||
|
local rowH = ((tileH - sideLenY) / 2) + sideLenY
|
||
|
local referenceX = ceil(x) / (colW * 2)
|
||
|
local referenceY = ceil(y) / (rowH * 2)
|
||
|
local relativeX = x - referenceX * colW * 2
|
||
|
local relativeY = y - referenceY * rowH * 2
|
||
|
local centers
|
||
|
|
||
|
if staggerX then
|
||
|
local left = sideLenX / 2
|
||
|
local centerX = left + colW
|
||
|
local centerY = tileH / 2
|
||
|
|
||
|
centers = {
|
||
|
{ x = left, y = centerY },
|
||
|
{ x = centerX, y = centerY - rowH },
|
||
|
{ x = centerX, y = centerY + rowH },
|
||
|
{ x = centerX + colW, y = centerY },
|
||
|
}
|
||
|
else
|
||
|
local top = sideLenY / 2
|
||
|
local centerX = tileW / 2
|
||
|
local centerY = top + rowH
|
||
|
|
||
|
centers = {
|
||
|
{ x = centerX, y = top },
|
||
|
{ x = centerX - colW, y = centerY },
|
||
|
{ x = centerX + colW, y = centerY },
|
||
|
{ x = centerX, y = centerY + rowH }
|
||
|
}
|
||
|
end
|
||
|
|
||
|
local nearest = 0
|
||
|
local minDist = math.huge
|
||
|
|
||
|
local function len2(ax, ay)
|
||
|
return ax * ax + ay * ay
|
||
|
end
|
||
|
|
||
|
for i = 1, 4 do
|
||
|
local dc = len2(centers[i].x - relativeX, centers[i].y - relativeY)
|
||
|
|
||
|
if dc < minDist then
|
||
|
minDist = dc
|
||
|
nearest = i
|
||
|
end
|
||
|
end
|
||
|
|
||
|
local offsetsStaggerX = {
|
||
|
{ x = 0, y = 0 },
|
||
|
{ x = 1, y = -1 },
|
||
|
{ x = 1, y = 0 },
|
||
|
{ x = 2, y = 0 },
|
||
|
}
|
||
|
|
||
|
local offsetsStaggerY = {
|
||
|
{ x = 0, y = 0 },
|
||
|
{ x = -1, y = 1 },
|
||
|
{ x = 0, y = 1 },
|
||
|
{ x = 0, y = 2 },
|
||
|
}
|
||
|
|
||
|
local offsets = staggerX and offsetsStaggerX or offsetsStaggerY
|
||
|
|
||
|
return
|
||
|
referenceX + offsets[nearest].x,
|
||
|
referenceY + offsets[nearest].y
|
||
|
end
|
||
|
end
|
||
|
|
||
|
--- A list of individual layers indexed both by draw order and name
|
||
|
-- @table Map.layers
|
||
|
-- @see TileLayer
|
||
|
-- @see ObjectLayer
|
||
|
-- @see ImageLayer
|
||
|
-- @see CustomLayer
|
||
|
|
||
|
--- A list of individual tiles indexed by Global ID
|
||
|
-- @table Map.tiles
|
||
|
-- @see Tile
|
||
|
-- @see Map.tileInstances
|
||
|
|
||
|
--- A list of tile instances indexed by Global ID
|
||
|
-- @table Map.tileInstances
|
||
|
-- @see TileInstance
|
||
|
-- @see Tile
|
||
|
-- @see Map.tiles
|
||
|
|
||
|
--- A list of no-longer-used batch sprites, indexed by batch
|
||
|
--@table Map.freeBatchSprites
|
||
|
|
||
|
--- A list of individual objects indexed by Global ID
|
||
|
-- @table Map.objects
|
||
|
-- @see Object
|
||
|
|
||
|
--- @table TileLayer
|
||
|
-- @field name The name of the layer
|
||
|
-- @field x Position on the X axis (in pixels)
|
||
|
-- @field y Position on the Y axis (in pixels)
|
||
|
-- @field width Width of layer (in tiles)
|
||
|
-- @field height Height of layer (in tiles)
|
||
|
-- @field visible Toggle if layer is visible or hidden
|
||
|
-- @field opacity Opacity of layer
|
||
|
-- @field properties Custom properties
|
||
|
-- @field data A tileWo dimensional table filled with individual tiles indexed by [y][x] (in tiles)
|
||
|
-- @field update Update function
|
||
|
-- @field draw Draw function
|
||
|
-- @see Map.layers
|
||
|
-- @see Tile
|
||
|
|
||
|
--- @table ObjectLayer
|
||
|
-- @field name The name of the layer
|
||
|
-- @field x Position on the X axis (in pixels)
|
||
|
-- @field y Position on the Y axis (in pixels)
|
||
|
-- @field visible Toggle if layer is visible or hidden
|
||
|
-- @field opacity Opacity of layer
|
||
|
-- @field properties Custom properties
|
||
|
-- @field objects List of objects indexed by draw order
|
||
|
-- @field update Update function
|
||
|
-- @field draw Draw function
|
||
|
-- @see Map.layers
|
||
|
-- @see Object
|
||
|
|
||
|
--- @table ImageLayer
|
||
|
-- @field name The name of the layer
|
||
|
-- @field x Position on the X axis (in pixels)
|
||
|
-- @field y Position on the Y axis (in pixels)
|
||
|
-- @field visible Toggle if layer is visible or hidden
|
||
|
-- @field opacity Opacity of layer
|
||
|
-- @field properties Custom properties
|
||
|
-- @field image Image to be drawn
|
||
|
-- @field update Update function
|
||
|
-- @field draw Draw function
|
||
|
-- @see Map.layers
|
||
|
|
||
|
--- Custom Layers are used to place userdata such as sprites within the draw order of the map.
|
||
|
-- @table CustomLayer
|
||
|
-- @field name The name of the layer
|
||
|
-- @field x Position on the X axis (in pixels)
|
||
|
-- @field y Position on the Y axis (in pixels)
|
||
|
-- @field visible Toggle if layer is visible or hidden
|
||
|
-- @field opacity Opacity of layer
|
||
|
-- @field properties Custom properties
|
||
|
-- @field update Update function
|
||
|
-- @field draw Draw function
|
||
|
-- @see Map.layers
|
||
|
-- @usage
|
||
|
-- -- Create a Custom Layer
|
||
|
-- local spriteLayer = map:addCustomLayer("Sprite Layer", 3)
|
||
|
--
|
||
|
-- -- Add data to Custom Layer
|
||
|
-- spriteLayer.sprites = {
|
||
|
-- player = {
|
||
|
-- image = lg.newImage("assets/sprites/player.png"),
|
||
|
-- x = 64,
|
||
|
-- y = 64,
|
||
|
-- r = 0,
|
||
|
-- }
|
||
|
-- }
|
||
|
--
|
||
|
-- -- Update callback for Custom Layer
|
||
|
-- function spriteLayer:update(dt)
|
||
|
-- for _, sprite in pairs(self.sprites) do
|
||
|
-- sprite.r = sprite.r + math.rad(90 * dt)
|
||
|
-- end
|
||
|
-- end
|
||
|
--
|
||
|
-- -- Draw callback for Custom Layer
|
||
|
-- function spriteLayer:draw()
|
||
|
-- for _, sprite in pairs(self.sprites) do
|
||
|
-- local x = math.floor(sprite.x)
|
||
|
-- local y = math.floor(sprite.y)
|
||
|
-- local r = sprite.r
|
||
|
-- lg.draw(sprite.image, x, y, r)
|
||
|
-- end
|
||
|
-- end
|
||
|
|
||
|
--- @table Tile
|
||
|
-- @field id Local ID within Tileset
|
||
|
-- @field gid Global ID
|
||
|
-- @field tileset Tileset ID
|
||
|
-- @field quad Quad object
|
||
|
-- @field properties Custom properties
|
||
|
-- @field terrain Terrain data
|
||
|
-- @field animation Animation data
|
||
|
-- @field frame Current animation frame
|
||
|
-- @field time Time spent on current animation frame
|
||
|
-- @field width Width of tile
|
||
|
-- @field height Height of tile
|
||
|
-- @field sx Scale value on the X axis
|
||
|
-- @field sy Scale value on the Y axis
|
||
|
-- @field r Rotation of tile (in radians)
|
||
|
-- @field offset Offset drawing position
|
||
|
-- @field offset.x Offset value on the X axis
|
||
|
-- @field offset.y Offset value on the Y axis
|
||
|
-- @see Map.tiles
|
||
|
|
||
|
--- @table TileInstance
|
||
|
-- @field batch Spritebatch the Tile Instance belongs to
|
||
|
-- @field id ID within the spritebatch
|
||
|
-- @field gid Global ID
|
||
|
-- @field x Position on the X axis (in pixels)
|
||
|
-- @field y Position on the Y axis (in pixels)
|
||
|
-- @see Map.tileInstances
|
||
|
-- @see Tile
|
||
|
|
||
|
--- @table Object
|
||
|
-- @field id Global ID
|
||
|
-- @field name Name of object (non-unique)
|
||
|
-- @field shape Shape of object
|
||
|
-- @field x Position of object on X axis (in pixels)
|
||
|
-- @field y Position of object on Y axis (in pixels)
|
||
|
-- @field width Width of object (in pixels)
|
||
|
-- @field height Heigh tof object (in pixels)
|
||
|
-- @field rotation Rotation of object (in radians)
|
||
|
-- @field visible Toggle if object is visible or hidden
|
||
|
-- @field properties Custom properties
|
||
|
-- @field ellipse List of verticies of specific shape
|
||
|
-- @field rectangle List of verticies of specific shape
|
||
|
-- @field polygon List of verticies of specific shape
|
||
|
-- @field polyline List of verticies of specific shape
|
||
|
-- @see Map.objects
|
||
|
|
||
|
return setmetatable({}, STI)
|