mirror of
https://github.com/bakpakin/tiny-ecs.git
synced 2024-11-28 23:54:21 +00:00
1264 lines
29 KiB
Lua
1264 lines
29 KiB
Lua
local Map = {
|
|
_LICENSE = "STI is distributed under the terms of the MIT license. See LICENSE.md.",
|
|
_URL = "https://github.com/karai17/Simple-Tiled-Implementation",
|
|
_VERSION = "0.9.7",
|
|
_DESCRIPTION = "Simple Tiled Implementation is a Tiled Map Editor library designed for the *awesome* LÖVE framework."
|
|
}
|
|
|
|
-- modified here
|
|
local function getFramework()
|
|
local lf = love.filesystem
|
|
local lg = love.graphics
|
|
local lm = love.math
|
|
local lp = love.physics
|
|
local framework = {}
|
|
|
|
framework.version = "LOVE"
|
|
|
|
assert(lf, "The love.filesystem module is required for this library.")
|
|
assert(lg, "The love.graphics module is required for this library.")
|
|
assert(lm, "The love.math module is required for this library.")
|
|
|
|
function framework.load(file)
|
|
return assert(lf.load(file), "File not found: " .. file)
|
|
end
|
|
|
|
function framework.newImage(path)
|
|
local image = lg.newImage(path)
|
|
image:setFilter("nearest", "nearest")
|
|
return image
|
|
end
|
|
|
|
function framework:newCanvas(w, h)
|
|
w = w or self.getWidth()
|
|
h = h or self.getHeight()
|
|
local canvas = lg.newCanvas(w, h)
|
|
canvas:setFilter("nearest", "nearest")
|
|
return canvas
|
|
end
|
|
|
|
-- Graphics Calls
|
|
framework.draw = lg.draw
|
|
framework.getCanvas = lg.getCanvas
|
|
framework.getHeight = lg.getHeight
|
|
framework.getWidth = lg.getWidth
|
|
framework.line = lg.line
|
|
framework.newSpriteBatch = lg.newSpriteBatch
|
|
framework.newQuad = lg.newQuad
|
|
framework.polygon = lg.polygon
|
|
framework.rectangle = lg.rectangle
|
|
framework.setColor = lg.setColor
|
|
framework.setCanvas = lg.setCanvas
|
|
framework.origin = lg.origin
|
|
framework.pop = lg.pop
|
|
framework.push = lg.push
|
|
|
|
-- Math Calls
|
|
framework.isConvex = lm.isConvex
|
|
framework.triangulate = lm.triangulate
|
|
|
|
-- Physics Calls
|
|
if lp then
|
|
framework.getMeter = lp.getMeter
|
|
framework.newBody = lp.newBody
|
|
framework.newChainShape = lp.newChainShape
|
|
framework.newFixture = lp.newFixture
|
|
framework.newPolygonShape = lp.newPolygonShape
|
|
end
|
|
return framework
|
|
end
|
|
|
|
local framework = getFramework()
|
|
-- to here
|
|
|
|
function string.split(s, d)
|
|
local magic = { "(", ")", ".", "%", "+", "-", "*", "?", "[", "^", "$" }
|
|
|
|
for _, v in ipairs(magic) do
|
|
if d == v then
|
|
d = "%"..d
|
|
break
|
|
end
|
|
end
|
|
|
|
local t = {}
|
|
local i = 0
|
|
local f
|
|
local match = '(.-)' .. d .. '()'
|
|
|
|
if string.find(s, d) == nil then
|
|
return {s}
|
|
end
|
|
|
|
for sub, j in string.gmatch(s, match) do
|
|
i = i + 1
|
|
t[i] = sub
|
|
f = j
|
|
end
|
|
|
|
if i ~= 0 then
|
|
t[i+1] = string.sub(s, f)
|
|
end
|
|
|
|
return t
|
|
end
|
|
|
|
-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286
|
|
local function formatPath(path)
|
|
local np_gen1,np_gen2 = '[^SEP]+SEP%.%.SEP?','SEP+%.?SEP'
|
|
local np_pat1, np_pat2 = np_gen1:gsub('SEP','/'), np_gen2:gsub('SEP','/')
|
|
local k
|
|
|
|
repeat -- /./ -> /
|
|
path,k = path:gsub(np_pat2,'/')
|
|
until k == 0
|
|
|
|
repeat -- A/../ -> (empty)
|
|
path,k = path:gsub(np_pat1,'')
|
|
until k == 0
|
|
|
|
if path == '' then path = '.' end
|
|
|
|
return path
|
|
end
|
|
|
|
local function rotateVertex(v, x, y, cos, sin)
|
|
local vertex = {
|
|
x = v.x,
|
|
y = v.y,
|
|
}
|
|
|
|
vertex.x = vertex.x - x
|
|
vertex.y = vertex.y - y
|
|
|
|
local vx = cos * vertex.x - sin * vertex.y
|
|
local vy = sin * vertex.x + cos * vertex.y
|
|
|
|
return vx + x, vy + y
|
|
end
|
|
|
|
local function convertEllipseToPolygon(x, y, w, h, max_segments)
|
|
local function calc_segments(segments)
|
|
local function vdist(a, b)
|
|
local c = {
|
|
x = a.x - b.x,
|
|
y = a.y - b.y,
|
|
}
|
|
|
|
return c.x * c.x + c.y * c.y
|
|
end
|
|
|
|
segments = segments or 64
|
|
local vertices = {}
|
|
|
|
local v = { 1, 2, math.ceil(segments/4-1), math.ceil(segments/4) }
|
|
|
|
local m
|
|
if framework.getMeter then
|
|
m = framework.getMeter()
|
|
else
|
|
m = 32
|
|
end
|
|
|
|
for _, i in ipairs(v) do
|
|
local angle = (i / segments) * math.pi * 2
|
|
local px = x + w / 2 + math.cos(angle) * w / 2
|
|
local py = y + h / 2 + math.sin(angle) * h / 2
|
|
|
|
table.insert(vertices, { x = px / m, y = py / m })
|
|
end
|
|
|
|
local dist1 = vdist(vertices[1], vertices[2])
|
|
local dist2 = vdist(vertices[3], vertices[4])
|
|
|
|
-- Box2D hard-coded threshold
|
|
if dist1 < 0.0025 or dist2 < 0.0025 then
|
|
return calc_segments(segments-2)
|
|
end
|
|
|
|
return segments
|
|
end
|
|
|
|
local segments = calc_segments(max_segments)
|
|
local vertices = {}
|
|
|
|
table.insert(vertices, { x = x + w / 2, y = y + h / 2 })
|
|
|
|
for i=0, segments do
|
|
local angle = (i / segments) * math.pi * 2
|
|
local px = x + w / 2 + math.cos(angle) * w / 2
|
|
local py = y + h / 2 + math.sin(angle) * h / 2
|
|
|
|
table.insert(vertices, { x = px, y = py })
|
|
end
|
|
|
|
return vertices
|
|
end
|
|
|
|
function Map:init(path, fw)
|
|
framework = fw
|
|
|
|
self.tiles = {}
|
|
self.tileInstances = {}
|
|
self.drawRange = {
|
|
sx = 1,
|
|
sy = 1,
|
|
ex = self.width,
|
|
ey = self.height,
|
|
}
|
|
|
|
-- Set tiles, images
|
|
local gid = 1
|
|
for i, tileset in ipairs(self.tilesets) do
|
|
local image = formatPath(path .. tileset.image)
|
|
tileset.image = framework.newImage(image)
|
|
gid = self:setTiles(i, tileset, gid)
|
|
end
|
|
|
|
-- Set layers
|
|
for i, layer in ipairs(self.layers) do
|
|
self:setLayer(layer, path)
|
|
end
|
|
end
|
|
|
|
function Map:initWorldCollision(world)
|
|
assert(framework.newBody, "To use the built-in collision system, please enable the physics module.")
|
|
|
|
local body = framework.newBody(world)
|
|
local collision = {
|
|
body = body,
|
|
}
|
|
|
|
local function addObjectToWorld(objshape, vertices, userdata)
|
|
local shape
|
|
|
|
if objshape == "polyline" then
|
|
shape = framework.newChainShape(false, unpack(vertices))
|
|
else
|
|
shape = framework.newPolygonShape(unpack(vertices))
|
|
end
|
|
|
|
local fixture = framework.newFixture(body, shape)
|
|
|
|
fixture:setUserData(userdata)
|
|
|
|
local obj = {
|
|
shape = shape,
|
|
fixture = fixture,
|
|
}
|
|
|
|
table.insert(collision, obj)
|
|
end
|
|
|
|
local function getPolygonVertices(object, tile, precalc)
|
|
local ox, oy = 0, 0
|
|
|
|
if not precalc then
|
|
ox = object.x
|
|
oy = object.y
|
|
end
|
|
|
|
local vertices = {}
|
|
for _, vertex in ipairs(object.polygon) do
|
|
table.insert(vertices, tile.x + ox + vertex.x)
|
|
table.insert(vertices, tile.y + oy + vertex.y)
|
|
end
|
|
|
|
return vertices
|
|
end
|
|
|
|
local function calculateObjectPosition(object, tile)
|
|
local o = {
|
|
shape = object.shape,
|
|
x = object.x,
|
|
y = object.y,
|
|
w = object.width,
|
|
h = object.height,
|
|
polygon = object.polygon or object.polyline or object.ellipse or object.rectangle
|
|
}
|
|
local t = tile or { x=0, y=0 }
|
|
|
|
local userdata = {
|
|
object = object,
|
|
instance = t,
|
|
tile = t.gid and self.tiles[t.gid]
|
|
}
|
|
|
|
if o.shape == "rectangle" then
|
|
o.r = object.rotation or 0
|
|
local cos = math.cos(math.rad(o.r))
|
|
local sin = math.sin(math.rad(o.r))
|
|
|
|
if object.gid then
|
|
local tileset = self.tiles[object.gid].tileset
|
|
local lid = object.gid - self.tilesets[tileset].firstgid
|
|
local tile = {}
|
|
|
|
-- This fixes a height issue
|
|
o.y = o.y + self.tiles[object.gid].offset.y
|
|
|
|
for _, t in ipairs(self.tilesets[tileset].tiles) do
|
|
if t.id == lid then
|
|
tile = t
|
|
break
|
|
end
|
|
end
|
|
|
|
if tile.objectGroup then
|
|
for _, obj in ipairs(tile.objectGroup.objects) do
|
|
-- Every object in the tile
|
|
calculateObjectPosition(obj, object)
|
|
end
|
|
|
|
return
|
|
else
|
|
o.w = self.tiles[object.gid].width
|
|
o.h = self.tiles[object.gid].height
|
|
end
|
|
end
|
|
|
|
o.polygon = {
|
|
{ x=o.x, y=o.y },
|
|
{ x=o.x + o.w, y=o.y },
|
|
{ x=o.x + o.w, y=o.y + o.h },
|
|
{ x=o.x, y=o.y + o.h },
|
|
}
|
|
|
|
for _, vertex in ipairs(o.polygon) do
|
|
if self.orientation == "isometric" then
|
|
vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y)
|
|
end
|
|
|
|
vertex.x, vertex.y = rotateVertex(vertex, o.x, o.y, cos, sin)
|
|
end
|
|
|
|
local vertices = getPolygonVertices(o, t, true)
|
|
addObjectToWorld(o.shape, vertices, userdata)
|
|
elseif o.shape == "ellipse" then
|
|
if not o.polygon then
|
|
o.polygon = convertEllipseToPolygon(o.x, o.y, o.w, o.h)
|
|
end
|
|
local vertices = getPolygonVertices(o, t, true)
|
|
local triangles = framework.triangulate(vertices)
|
|
|
|
for _, triangle in ipairs(triangles) do
|
|
addObjectToWorld(o.shape, triangle, userdata)
|
|
end
|
|
elseif o.shape == "polygon" then
|
|
local precalc = false
|
|
if not t.gid then precalc = true end
|
|
|
|
local vertices = getPolygonVertices(o, t, precalc)
|
|
local triangles = framework.triangulate(vertices)
|
|
|
|
for _, triangle in ipairs(triangles) do
|
|
addObjectToWorld(o.shape, triangle, userdata)
|
|
end
|
|
elseif o.shape == "polyline" then
|
|
local precalc = false
|
|
if not t.gid then precalc = true end
|
|
|
|
local vertices = getPolygonVertices(o, t, precalc)
|
|
addObjectToWorld(o.shape, vertices, userdata)
|
|
end
|
|
end
|
|
|
|
for _, tileset in ipairs(self.tilesets) do
|
|
for _, tile in ipairs(tileset.tiles) do
|
|
local gid = tileset.firstgid + tile.id
|
|
|
|
if tile.objectGroup then
|
|
if self.tileInstances[gid] then
|
|
for _, instance in ipairs(self.tileInstances[gid]) do
|
|
for _, object in ipairs(tile.objectGroup.objects) do
|
|
-- Every object in every instance of a tile
|
|
calculateObjectPosition(object, instance)
|
|
end
|
|
end
|
|
end
|
|
elseif tile.properties.collidable == "true" and self.tileInstances[gid] then
|
|
for _, instance in ipairs(self.tileInstances[gid]) do
|
|
-- Every instance of a tile
|
|
local object = {
|
|
shape = "rectangle",
|
|
x = 0,
|
|
y = 0,
|
|
width = tileset.tilewidth,
|
|
height = tileset.tileheight,
|
|
}
|
|
|
|
calculateObjectPosition(object, instance)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
for _, layer in ipairs(self.layers) do
|
|
if layer.properties.collidable == "true" then
|
|
-- Entire layer
|
|
if layer.type == "tilelayer" then
|
|
for y, tiles in ipairs(layer.data) do
|
|
for x, tile in pairs(tiles) do
|
|
local object = {
|
|
shape = "rectangle",
|
|
x = x * self.tilewidth + tile.offset.x,
|
|
y = y * self.tileheight + tile.offset.y,
|
|
width = tile.width,
|
|
height = tile.height,
|
|
}
|
|
calculateObjectPosition(object)
|
|
end
|
|
end
|
|
elseif layer.type == "objectgroup" then
|
|
for _, object in ipairs(layer.objects) do
|
|
calculateObjectPosition(object)
|
|
end
|
|
elseif layer.type == "imagelayer" then
|
|
local object = {
|
|
shape = "rectangle",
|
|
x = layer.x or 0,
|
|
y = layer.y or 0,
|
|
width = layer.width,
|
|
height = layer.height,
|
|
}
|
|
calculateObjectPosition(object)
|
|
end
|
|
end
|
|
|
|
if layer.type == "objectgroup" then
|
|
for _, object in ipairs(layer.objects) do
|
|
if object.properties.collidable == "true" then
|
|
-- Individual objects
|
|
calculateObjectPosition(object)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
return collision
|
|
end
|
|
|
|
function Map:setTiles(index, tileset, gid)
|
|
local function getTiles(i, t, m, s)
|
|
i = i - m
|
|
local n = 0
|
|
|
|
while i >= t do
|
|
i = i - t
|
|
if n ~= 0 then i = i - s end
|
|
if i >= 0 then n = n + 1 end
|
|
end
|
|
|
|
return n
|
|
end
|
|
|
|
local quad = framework.newQuad
|
|
local mw = self.tilewidth
|
|
local iw = tileset.imagewidth
|
|
local ih = tileset.imageheight
|
|
local tw = tileset.tilewidth
|
|
local th = tileset.tileheight
|
|
local s = tileset.spacing
|
|
local m = tileset.margin
|
|
local w = getTiles(iw, tw, m, s)
|
|
local h = getTiles(ih, th, m, s)
|
|
|
|
for y = 1, h do
|
|
for x = 1, w do
|
|
local id = gid - tileset.firstgid
|
|
local qx = (x - 1) * tw + m + (x - 1) * s
|
|
local qy = (y - 1) * th + m + (y - 1) * s
|
|
local properties
|
|
local terrain
|
|
local animation
|
|
|
|
for _, tile in pairs(tileset.tiles) do
|
|
if tile.id == id then
|
|
properties = tile.properties
|
|
animation = tile.animation
|
|
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(qx, qy, tw, th, iw, ih),
|
|
properties = properties,
|
|
terrain = terrain,
|
|
animation = animation,
|
|
frame = 1,
|
|
time = 0,
|
|
width = tw,
|
|
height = th,
|
|
sx = 1,
|
|
sy = 1,
|
|
r = 0,
|
|
offset = {
|
|
x = -mw + tileset.tileoffset.x,
|
|
y = -th + tileset.tileoffset.y,
|
|
},
|
|
}
|
|
|
|
if self.orientation == "isometric" then
|
|
tile.offset.x = -mw / 2
|
|
end
|
|
|
|
self.tiles[gid] = tile
|
|
gid = gid + 1
|
|
end
|
|
end
|
|
|
|
return gid
|
|
end
|
|
|
|
function Map:setLayer(layer, path)
|
|
layer.x = layer.x or 0
|
|
layer.y = layer.y or 0
|
|
layer.update = function(dt) return 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: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 image = formatPath(path..layer.image)
|
|
layer.image = framework.newImage(image)
|
|
layer.width = layer.image:getWidth()
|
|
layer.height = layer.image:getHeight()
|
|
end
|
|
end
|
|
|
|
self.layers[layer.name] = layer
|
|
end
|
|
|
|
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
|
|
|
|
function Map:setObjectCoordinates(layer)
|
|
local function updateVertex(vertex, x, y, cos, sin)
|
|
if self.orientation == "isometric" then
|
|
x, y = self:convertIsometricToScreen(x, y)
|
|
vertex.x, vertex.y = self:convertIsometricToScreen(vertex.x, vertex.y)
|
|
end
|
|
|
|
return rotateVertex(vertex, x, y, cos, sin)
|
|
end
|
|
|
|
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" then
|
|
object.rectangle = {}
|
|
|
|
local vertices
|
|
|
|
if object.gid then
|
|
vertices = {
|
|
{ x=x, y=y - h },
|
|
{ x=x + w, y=y - h },
|
|
{ x=x + w, y=y },
|
|
{ x=x, y=y },
|
|
}
|
|
else
|
|
vertices = {
|
|
{ x=x, y=y },
|
|
{ x=x + w, y=y },
|
|
{ x=x + w, y=y + h },
|
|
{ x=x, y=y + h },
|
|
}
|
|
end
|
|
|
|
for _, vertex in ipairs(vertices) do
|
|
vertex.x, vertex.y = updateVertex(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 = convertEllipseToPolygon(x, y, w, h)
|
|
|
|
for _, vertex in ipairs(vertices) do
|
|
vertex.x, vertex.y = updateVertex(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 = x + vertex.x
|
|
vertex.y = y + vertex.y
|
|
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
|
|
end
|
|
elseif object.shape == "polyline" then
|
|
for _, vertex in ipairs(object.polyline) do
|
|
vertex.x = x + vertex.x
|
|
vertex.y = y + vertex.y
|
|
vertex.x, vertex.y = updateVertex(vertex, x, y, cos, sin)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
function Map:setSpriteBatches(layer)
|
|
local newBatch = framework.newSpriteBatch
|
|
local w = framework.getWidth()
|
|
local h = framework.getHeight()
|
|
local tw = self.tilewidth
|
|
local th = self.tileheight
|
|
local bw = math.ceil(w / tw)
|
|
local bh = math.ceil(h / th)
|
|
|
|
-- Minimum of 400 tiles per batch
|
|
if bw < 20 then bw = 20 end
|
|
if bh < 20 then bh = 20 end
|
|
|
|
local size = bw * bh
|
|
local batches = {
|
|
width = bw,
|
|
height = bh,
|
|
data = {},
|
|
}
|
|
|
|
for y = 1, layer.height do
|
|
local by = math.ceil(y / bh)
|
|
|
|
for x = 1, layer.width do
|
|
local tile = layer.data[y][x]
|
|
local bx = math.ceil(x / bw)
|
|
local id
|
|
|
|
if tile then
|
|
local ts = tile.tileset
|
|
local image = self.tilesets[tile.tileset].image
|
|
|
|
batches.data[ts] = batches.data[ts] or {}
|
|
batches.data[ts][by] = batches.data[ts][by] or {}
|
|
batches.data[ts][by][bx] = batches.data[ts][by][bx] or newBatch(image, size)
|
|
|
|
local batch = batches.data[ts][by][bx]
|
|
local tx, ty, origx, origy
|
|
|
|
if self.orientation == "orthogonal" then
|
|
tx = x * tw + tile.offset.x
|
|
ty = y * th + tile.offset.y
|
|
|
|
origx = tx
|
|
origy = ty
|
|
|
|
-- Compensation for scale/rotation shift
|
|
local compx = 0
|
|
local compy = 0
|
|
if tile.sx < 0 then compx = tw end
|
|
if tile.sy < 0 then compy = th end
|
|
|
|
if tile.r > 0 then
|
|
tx = tx + th - compy
|
|
ty = ty + th - tw + compx
|
|
elseif tile.r < 0 then
|
|
tx = tx + compy
|
|
ty = ty + th - compx
|
|
else
|
|
tx = tx + compx
|
|
ty = ty + compy
|
|
end
|
|
elseif self.orientation == "isometric" then
|
|
tx = (x - y) * (tw / 2) + tile.offset.x + layer.width * tw / 2
|
|
ty = (x + y) * (th / 2) + tile.offset.y
|
|
elseif self.orientation == "staggered" then
|
|
if y % 2 == 0 then
|
|
tx = x * tw + tw / 2 + tile.offset.x
|
|
else
|
|
tx = x * tw + tile.offset.x
|
|
end
|
|
|
|
ty = y * th / 2 + tile.offset.y + th / 2
|
|
end
|
|
|
|
id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy)
|
|
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
|
|
table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=origx or tx, y=origy or ty })
|
|
end
|
|
end
|
|
end
|
|
|
|
layer.batches = batches
|
|
end
|
|
|
|
function Map:setObjectSpriteBatches(layer)
|
|
local newBatch = framework.newSpriteBatch
|
|
local tw = self.tilewidth
|
|
local th = self.tileheight
|
|
local batches = {}
|
|
|
|
for _, object in ipairs(layer.objects) do
|
|
if object.gid then
|
|
local tile = self.tiles[object.gid] or self:setFlippedGID(object.gid)
|
|
local ts = tile.tileset
|
|
local image = self.tilesets[tile.tileset].image
|
|
|
|
batches[ts] = batches[ts] or newBatch(image, 100)
|
|
|
|
local batch = batches[ts]
|
|
local tx = object.x + tw + tile.offset.x
|
|
local ty = object.y + tile.offset.y
|
|
|
|
-- Compensation for scale/rotation shift
|
|
if tile.sx < 0 then tx = tx + tw end
|
|
if tile.sy < 0 then ty = ty + th end
|
|
if tile.r > 0 then tx = tx + tw end
|
|
if tile.r < 0 then ty = ty + th end
|
|
|
|
id = batch:add(tile.quad, tx, ty, tile.r, tile.sx, tile.sy)
|
|
self.tileInstances[tile.gid] = self.tileInstances[tile.gid] or {}
|
|
table.insert(self.tileInstances[tile.gid], { batch=batch, id=id, gid=tile.gid, x=tx, y=ty })
|
|
end
|
|
end
|
|
|
|
layer.batches = batches
|
|
end
|
|
|
|
function Map:setDrawRange(tx, ty, w, h)
|
|
tx = -tx
|
|
ty = -ty
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local sx, sy, ex, ey
|
|
|
|
if self.orientation == "orthogonal" then
|
|
sx = math.ceil(tx / tw)
|
|
sy = math.ceil(ty / th)
|
|
ex = math.ceil(sx + w / tw)
|
|
ey = math.ceil(sy + h / th)
|
|
elseif self.orientation == "isometric" then
|
|
sx = math.ceil(((ty / (th / 2)) + (tx / (tw / 2))) / 2)
|
|
sy = math.ceil(((ty / (th / 2)) - (tx / (tw / 2))) / 2 - h / th)
|
|
ex = math.ceil(sx + (h / th) + (w / tw))
|
|
ey = math.ceil(sy + (h / th) * 2 + (w / tw))
|
|
elseif self.orientation == "staggered" then
|
|
sx = math.ceil(tx / tw - 1)
|
|
sy = math.ceil(ty / th)
|
|
ex = math.ceil(sx + w / tw + 1)
|
|
ey = math.ceil(sy + h / th * 2)
|
|
end
|
|
|
|
self.drawRange = {
|
|
sx = sx,
|
|
sy = sy,
|
|
ex = ex,
|
|
ey = ey,
|
|
}
|
|
end
|
|
|
|
function Map:addCustomLayer(name, index)
|
|
local layer = {
|
|
type = "customlayer",
|
|
name = name,
|
|
visible = true,
|
|
opacity = 1,
|
|
properties = {},
|
|
}
|
|
|
|
function layer:draw() return end
|
|
function layer:update(dt) return end
|
|
|
|
table.insert(self.layers, index, layer)
|
|
self.layers[name] = self.layers[index]
|
|
|
|
return layer
|
|
end
|
|
|
|
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() return end
|
|
function layer:update(dt) return end
|
|
end
|
|
|
|
function Map:removeLayer(index)
|
|
local layer = assert(self.layers[index], "Layer not found: " .. index)
|
|
|
|
if type(index) == "string" then
|
|
for i, layer in ipairs(self.layers) do
|
|
if layer.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
|
|
end
|
|
|
|
function Map:update(dt)
|
|
for gid, tile in pairs( self.tiles ) do
|
|
local update
|
|
local t
|
|
|
|
if tile.animation then
|
|
update = false
|
|
|
|
tile.time = tile.time + dt * 1000
|
|
while tile.time > tonumber(tile.animation[tile.frame].duration) do
|
|
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
|
|
update = true
|
|
end
|
|
if update == true and self.tileInstances[gid] ~= nil then
|
|
for _, j in pairs(self.tileInstances[gid]) do
|
|
t = self.tiles[tile.animation[tile.frame].tileid + self.tilesets[tile.tileset].firstgid]
|
|
j.batch:set( j.id, t.quad, j.x, j.y, 0 )
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
for _, layer in ipairs(self.layers) do
|
|
layer:update(dt)
|
|
end
|
|
end
|
|
|
|
-- modified
|
|
function Map:draw()
|
|
for _, layer in ipairs(self.layers) do
|
|
if layer.visible and layer.opacity > 0 then
|
|
self:drawLayer(layer)
|
|
end
|
|
end
|
|
end
|
|
|
|
function Map:drawLayer(layer)
|
|
framework.setColor(255, 255, 255, 255 * layer.opacity)
|
|
layer:draw()
|
|
framework.setColor(255, 255, 255, 255)
|
|
end
|
|
|
|
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")
|
|
|
|
local bw = layer.batches.width
|
|
local bh = layer.batches.height
|
|
local sx = math.ceil((self.drawRange.sx - layer.x / self.tilewidth - 1) / bw)
|
|
local sy = math.ceil((self.drawRange.sy - layer.y / self.tileheight - 1) / bh)
|
|
local ex = math.ceil((self.drawRange.ex - layer.x / self.tilewidth + 1) / bw)
|
|
local ey = math.ceil((self.drawRange.ey - layer.y / self.tileheight + 1) / bh)
|
|
local mx = math.ceil(self.width / bw)
|
|
local my = math.ceil(self.height / bh)
|
|
|
|
for by=sy, ey do
|
|
for bx=sx, ex do
|
|
if bx >= 1 and bx <= mx and by >= 1 and by <= my then
|
|
for _, batches in pairs(layer.batches.data) do
|
|
local batch = batches[by] and batches[by][bx]
|
|
|
|
if batch then
|
|
framework.draw(batch, math.floor(layer.x), math.floor(layer.y))
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
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.2 }
|
|
local shadow = { 0, 0, 0, 255 * layer.opacity }
|
|
local reset = { 255, 255, 255, 255 * layer.opacity }
|
|
|
|
local function sortVertices(obj)
|
|
local vertices = {{},{}}
|
|
|
|
for _, vertex in ipairs(obj) do
|
|
table.insert(vertices[1], vertex.x)
|
|
table.insert(vertices[1], vertex.y)
|
|
table.insert(vertices[2], vertex.x+1)
|
|
table.insert(vertices[2], vertex.y+1)
|
|
end
|
|
|
|
return vertices
|
|
end
|
|
|
|
local function drawShape(obj, shape)
|
|
local vertices = sortVertices(obj)
|
|
|
|
if shape == "polyline" then
|
|
framework.setColor(shadow)
|
|
framework.line(vertices[2])
|
|
framework.setColor(line)
|
|
framework.line(vertices[1])
|
|
|
|
return
|
|
elseif shape == "polygon" then
|
|
framework.setColor(fill)
|
|
if not framework.isConvex(vertices[1]) then
|
|
local triangles = framework.triangulate(vertices[1])
|
|
for _, triangle in ipairs(triangles) do
|
|
framework.polygon("fill", triangle)
|
|
end
|
|
else
|
|
framework.polygon("fill", vertices[1])
|
|
end
|
|
else
|
|
framework.setColor(fill)
|
|
framework.polygon("fill", vertices[1])
|
|
end
|
|
|
|
framework.setColor(shadow)
|
|
framework.polygon("line", vertices[2])
|
|
framework.setColor(line)
|
|
framework.polygon("line", vertices[1])
|
|
end
|
|
|
|
for _, object in ipairs(layer.objects) do
|
|
if object.shape == "rectangle" 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
|
|
|
|
framework.setColor(reset)
|
|
for _, batch in pairs(layer.batches) do
|
|
framework.draw(batch, 0, 0)
|
|
end
|
|
end
|
|
|
|
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
|
|
framework.draw(layer.image, layer.x, layer.y)
|
|
end
|
|
end
|
|
|
|
function Map:drawWorldCollision(collision)
|
|
for _, obj in ipairs(collision) do
|
|
framework.polygon("line", collision.body:getWorldPoints(obj.shape:getPoints()))
|
|
end
|
|
end
|
|
|
|
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 = tile.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
|
|
|
|
function Map:getLayerProperties(layer)
|
|
local l = self.layers[layer]
|
|
|
|
if not l then return {} end
|
|
|
|
return l.properties
|
|
end
|
|
|
|
function Map:getTileProperties(layer, x, y)
|
|
local tile = self.layers[layer].data[y][x]
|
|
|
|
if not tile then return {} end
|
|
|
|
return tile.properties
|
|
end
|
|
|
|
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
|
|
|
|
function Map:convertIsometricToScreen(x, y)
|
|
local mw = self.width
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local ox = mw * tw / 2
|
|
|
|
local sx = (x - y) + ox
|
|
local sy = (x + y) / 2
|
|
|
|
return sx, sy
|
|
end
|
|
|
|
function Map:convertScreenToIsometric(x, y)
|
|
local mw, mh = self.width, self.height
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local ox = mw * tw / 2
|
|
local oy = mh * th / 2
|
|
|
|
local tx = (x / 2 + y) - ox / 2
|
|
local ty = (-x / 2 + y) + oy
|
|
|
|
return tx, ty
|
|
end
|
|
|
|
function Map:convertTileToScreen(x, y)
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
|
|
local sx = x * tw
|
|
local sy = y * th
|
|
|
|
return sx, sy
|
|
end
|
|
|
|
function Map:convertScreenToTile(x, y)
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
|
|
local tx = x / tw
|
|
local ty = y / th
|
|
|
|
return tx, ty
|
|
end
|
|
|
|
function Map:convertIsometricTileToScreen(x, y)
|
|
local mw = self.width
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local ox = mw * tw / 2
|
|
|
|
local sx = (x - y) * tw / 2 + ox
|
|
local sy = (x + y) * th / 2
|
|
|
|
return sx, sy
|
|
end
|
|
|
|
function Map:convertScreenToIsometricTile(x, y)
|
|
local mw = self.width
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local ox = mw * tw / 2
|
|
|
|
local tx = y / th + (x - ox) / tw
|
|
local ty = y / th - (x - ox) / tw
|
|
|
|
return tx, ty
|
|
end
|
|
|
|
function Map:convertStaggeredTileToScreen(x, y)
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
|
|
local sx = x * tw + math.abs(math.ceil(y) % 2) * (tw / 2) - (math.ceil(y) % 2 * tw/2)
|
|
local sy = y * (th / 2) + th/2
|
|
|
|
return sx, sy
|
|
end
|
|
|
|
function Map:convertScreenToStaggeredTile(x, y)
|
|
local function topLeft(x, y)
|
|
if (math.ceil(y) % 2) then
|
|
return x, y - 1
|
|
else
|
|
return x - 1, y - 1
|
|
end
|
|
end
|
|
|
|
local function topRight(x, y)
|
|
if (math.ceil(y) % 2) then
|
|
return x + 1, y - 1
|
|
else
|
|
return x, y - 1
|
|
end
|
|
end
|
|
|
|
local function bottomLeft(x, y)
|
|
if (math.ceil(y) % 2) then
|
|
return x, y + 1
|
|
else
|
|
return x - 1, y + 1
|
|
end
|
|
end
|
|
|
|
local function bottomRight(x, y)
|
|
if (math.ceil(y) % 2) then
|
|
return x + 1, y + 1
|
|
else
|
|
return x, y + 1
|
|
end
|
|
end
|
|
|
|
local tw, th = self.tilewidth, self.tileheight
|
|
local hh = th / 2
|
|
local ratio = th / tw
|
|
|
|
local tx = x / tw
|
|
local ty = y / th * 2
|
|
|
|
local ctx = math.ceil(x / tw)
|
|
local cty = math.ceil(y / th) * 2
|
|
|
|
local rx = x - ctx * tw
|
|
local ry = y - (cty / 2) * th
|
|
|
|
if (hh - rx * ratio > ry) then
|
|
return topLeft(tx, ty)
|
|
elseif (-hh + rx * ratio > ry) then
|
|
return topRight(tx, ty)
|
|
elseif (hh + rx * ratio < ry) then
|
|
return bottomLeft(tx, ty)
|
|
elseif (hh * 3 - rx * ratio < ry) then
|
|
return bottomRight(tx, ty)
|
|
end
|
|
|
|
return tx, ty
|
|
end
|
|
|
|
function Map.new(map)
|
|
map = map .. ".lua"
|
|
|
|
-- Get path to map
|
|
local path = map:reverse():find("[/\\]") or ""
|
|
if path ~= "" then
|
|
path = map:sub(1, 1 + (#map - path))
|
|
end
|
|
|
|
-- Load map
|
|
map = framework.load(map)
|
|
setfenv(map, {})
|
|
map = setmetatable(map(), {__index = Map})
|
|
|
|
map:init(path, framework)
|
|
|
|
return map
|
|
end
|
|
|
|
return Map
|