2016-04-26 13:23:11 +00:00
--- Map object
-- @module map
local path = ( ... ) : gsub ( ' %.[^%.]+$ ' , ' ' ) .. " . "
local pluginPath = string.gsub ( path , " [.] " , " / " ) .. " plugins/ "
local Map = { }
2014-12-06 04:35:32 +00:00
-- https://github.com/stevedonovan/Penlight/blob/master/lua/pl/path.lua#L286
local function formatPath ( path )
2016-04-26 13:23:11 +00:00
local np_gen1 , np_gen2 = ' [^SEP]+SEP%.%.SEP? ' , ' SEP+%.?SEP '
local np_pat1 , np_pat2 = np_gen1 : gsub ( ' SEP ' , ' / ' ) , np_gen2 : gsub ( ' SEP ' , ' / ' )
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
-- Compensation for scale/rotation shift
local function compensate ( tile , x , y , tw , th )
local tx = x + tile.offset . x
local ty = y + tile.offset . y
local origx = tx
local origy = ty
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
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
return tx , ty
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
-- Cache images in main STI module
local function cache_image ( sti , path )
local image = love.graphics . newImage ( path )
image : setFilter ( " nearest " , " nearest " )
sti.cache [ path ] = image
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- 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)
-- @return nil
function Map : init ( STI , path , plugins , ox , oy )
if type ( plugins ) == " table " then
self : loadPlugins ( plugins )
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
self : resize ( )
self.objects = { }
self.tiles = { }
self.tileInstances = { }
self.drawRange = {
2014-12-06 04:35:32 +00:00
sx = 1 ,
sy = 1 ,
ex = self.width ,
ey = self.height ,
}
2016-04-26 13:23:11 +00:00
self.offsetx = ox or 0
self.offsety = oy or 0
self.sti = STI
2014-12-06 04:35:32 +00:00
-- Set tiles, images
local gid = 1
for i , tileset in ipairs ( self.tilesets ) do
2016-04-26 13:23:11 +00:00
assert ( tileset.image , " STI does not support Tile Collections. \n You need to create a Texture Atlas. " )
-- Cache images
local formatted_path = formatPath ( path .. tileset.image )
if not self.sti . cache [ formatted_path ] then
cache_image ( self.sti , formatted_path )
end
-- Pull images from cache
tileset.image = self.sti . cache [ formatted_path ]
2014-12-06 04:35:32 +00:00
gid = self : setTiles ( i , tileset , gid )
end
-- Set layers
for i , layer in ipairs ( self.layers ) do
self : setLayer ( layer , path )
end
end
2016-04-26 13:23:11 +00:00
--- Load plugins
-- @param plugins A list of plugins to load
-- @return nil
function Map : loadPlugins ( plugins )
for _ , plugin in ipairs ( plugins ) do
local p = pluginPath .. plugin .. " .lua "
if love.filesystem . isFile ( p ) then
local file = love.filesystem . load ( p ) ( )
for k , func in pairs ( file ) do
if not self [ k ] then
self [ k ] = func
2014-12-06 04:35:32 +00:00
end
end
end
end
end
2016-04-26 13:23:11 +00:00
--- 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
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
local quad = love.graphics . 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 )
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
local properties , terrain , animation , objectGroup
2014-12-06 04:35:32 +00:00
for _ , tile in pairs ( tileset.tiles ) do
if tile.id == id then
2016-04-26 13:23:11 +00:00
properties = tile.properties
animation = tile.animation
objectGroup = tile.objectGroup
2014-12-06 04:35:32 +00:00
if tile.terrain then
terrain = { }
2016-04-26 13:23:11 +00:00
for i = 1 , # tile.terrain do
2014-12-06 04:35:32 +00:00
terrain [ i ] = tileset.terrains [ tile.terrain [ i ] + 1 ]
end
end
end
end
local tile = {
2016-04-26 13:23:11 +00:00
id = id ,
gid = gid ,
tileset = index ,
quad = quad ( qx , qy , tw , th , iw , ih ) ,
properties = properties or { } ,
2014-12-06 04:35:32 +00:00
terrain = terrain ,
animation = animation ,
2016-04-26 13:23:11 +00:00
objectGroup = objectGroup ,
2014-12-06 04:35:32 +00:00
frame = 1 ,
time = 0 ,
2016-04-26 13:23:11 +00:00
width = tw ,
height = th ,
sx = 1 ,
sy = 1 ,
r = 0 ,
offset = {
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
gid = gid + 1
2014-12-06 04:35:32 +00:00
end
end
return gid
end
2016-04-26 13:23:11 +00:00
--- Create Layers
-- @param layer Layer data
-- @param path (Optional) Path to an Image Layer's image
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : setLayer ( layer , path )
2016-04-26 13:23:11 +00:00
if layer.encoding then
if layer.encoding == " base64 " then
local ffi = assert ( require " ffi " , " Compressed maps require LuaJIT FFI. \n Please Switch your interperator to LuaJIT or your Tile Layer Format to \" CSV \" . " )
local fd = love.filesystem . newFileData ( layer.data , " data " , " base64 " ) : getString ( )
local function getDecompressedData ( data )
local d = { }
local decoded = ffi.cast ( " uint32_t* " , data )
for i = 0 , data : len ( ) / ffi.sizeof ( " uint32_t " ) do
table.insert ( d , tonumber ( decoded [ i ] ) )
end
return d
end
if not layer.compression then
layer.data = getDecompressedData ( fd )
else
assert ( love.math . decompress , " zlib and gzip compression require LOVE 0.10.0+. \n Please set your Tile Layer Format to \" Base64 (uncompressed) \" or \" CSV \" . " )
if layer.compression == " zlib " then
local data = love.math . decompress ( fd , " zlib " )
layer.data = getDecompressedData ( data )
end
if layer.compression == " gzip " then
local data = love.math . decompress ( fd , " gzip " )
layer.data = getDecompressedData ( data )
end
end
end
end
layer.x = ( layer.x or 0 ) + self.offsetx
layer.y = ( layer.y or 0 ) + self.offsety
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
self : setObjectData ( layer )
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
local formatted_path = formatPath ( path .. layer.image )
if not self.sti . cache [ formatted_path ] then
cache_image ( self.sti , formatted_path )
end
layer.image = self.sti . cache [ formatted_path ]
layer.width = layer.image : getWidth ( )
2014-12-06 04:35:32 +00:00
layer.height = layer.image : getHeight ( )
end
end
self.layers [ layer.name ] = layer
end
2016-04-26 13:23:11 +00:00
--- Add Tiles to Tile Layer
-- @param layer The Tile Layer
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : setTileData ( layer )
2016-04-26 13:23:11 +00:00
local i = 1
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
map [ y ] [ x ] = self.tiles [ gid ] or self : setFlippedGID ( gid )
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
i = i + 1
end
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
layer.data = map
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
--- Add Objects to Layer
-- @param layer The Object Layer
-- @return nil
function Map : setObjectData ( layer )
for _ , object in ipairs ( layer.objects ) do
object.layer = layer
self.objects [ object.id ] = object
end
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
--- Correct position and orientation of Objects in an Object Layer
-- @param layer The Object Layer
-- @return nil
function Map : setObjectCoordinates ( layer )
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 love.physics then
m = love.physics . getMeter ( )
2014-12-06 04:35:32 +00:00
else
2016-04-26 13:23:11 +00:00
m = 32
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
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 threshold
if dist1 < 0.0025 or dist2 < 0.0025 then
return calc_segments ( segments - 2 )
end
return segments
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
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
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
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
2014-12-06 04:35:32 +00:00
local function updateVertex ( vertex , x , y , cos , sin )
if self.orientation == " isometric " then
2016-04-26 13:23:11 +00:00
x , y = self : convertIsometricToScreen ( x , y )
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
local x = layer.x + object.x
local y = layer.y + object.y
local w = object.width
local h = object.height
local r = object.rotation
2014-12-06 04:35:32 +00:00
local cos = math.cos ( math.rad ( r ) )
local sin = math.sin ( math.rad ( r ) )
2016-04-26 13:23:11 +00:00
if object.shape == " rectangle " and not object.gid then
2014-12-06 04:35:32 +00:00
object.rectangle = { }
local vertices = {
2016-04-26 13:23:11 +00:00
{ x = x , y = y } ,
{ x = x + w , y = y } ,
{ x = x + w , y = y + h } ,
{ x = x , y = y + h } ,
2014-12-06 04:35:32 +00:00
}
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
2016-04-26 13:23:11 +00:00
vertex.x = vertex.x + x
vertex.y = vertex.y + y
2014-12-06 04:35:32 +00:00
vertex.x , vertex.y = updateVertex ( vertex , x , y , cos , sin )
end
elseif object.shape == " polyline " then
for _ , vertex in ipairs ( object.polyline ) do
2016-04-26 13:23:11 +00:00
vertex.x = vertex.x + x
vertex.y = vertex.y + y
2014-12-06 04:35:32 +00:00
vertex.x , vertex.y = updateVertex ( vertex , x , y , cos , sin )
end
end
end
end
2016-04-26 13:23:11 +00:00
--- Batch Tiles in Tile Layer for improved draw speed
-- @param layer The Tile Layer
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : setSpriteBatches ( layer )
2016-04-26 13:23:11 +00:00
local newBatch = love.graphics . newSpriteBatch
local w = love.graphics . getWidth ( )
local h = love.graphics . getHeight ( )
local tw = self.tilewidth
local th = self.tileheight
local bw = math.ceil ( w / tw )
local bh = math.ceil ( h / th )
local sx = 1
local sy = 1
local ex = layer.width
local ey = layer.height
local ix = 1
local iy = 1
-- Determine order to add tiles to sprite batch
-- Defaults to right-down
if self.renderorder == " right-up " then
sx , ex , ix = sx , ex , 1
sy , ey , iy = ey , sy , - 1
elseif self.renderorder == " left-down " then
sx , ex , ix = ex , sx , - 1
sy , ey , iy = sy , ey , 1
elseif self.renderorder == " left-up " then
sx , ex , ix = ex , sx , - 1
sy , ey , iy = ey , sy , - 1
end
2014-12-06 04:35:32 +00:00
-- Minimum of 400 tiles per batch
if bw < 20 then bw = 20 end
if bh < 20 then bh = 20 end
2016-04-26 13:23:11 +00:00
local size = bw * bh
local batches = {
width = bw ,
height = bh ,
data = { } ,
2014-12-06 04:35:32 +00:00
}
2016-04-26 13:23:11 +00:00
for y = sy , ey , iy do
2014-12-06 04:35:32 +00:00
local by = math.ceil ( y / bh )
2016-04-26 13:23:11 +00:00
for x = sx , ex , ix do
local tile = layer.data [ y ] [ x ]
local bx = math.ceil ( x / bw )
2014-12-06 04:35:32 +00:00
local id
if tile then
2016-04-26 13:23:11 +00:00
local ts = tile.tileset
2014-12-06 04:35:32 +00:00
local image = self.tilesets [ tile.tileset ] . image
2016-04-26 13:23:11 +00:00
batches.data [ ts ] = batches.data [ ts ] or { }
batches.data [ ts ] [ by ] = batches.data [ ts ] [ by ] or { }
2014-12-06 04:35:32 +00:00
batches.data [ ts ] [ by ] [ bx ] = batches.data [ ts ] [ by ] [ bx ] or newBatch ( image , size )
local batch = batches.data [ ts ] [ by ] [ bx ]
local tx , ty
if self.orientation == " orthogonal " then
2016-04-26 13:23:11 +00:00
tx , ty = compensate ( tile , x * tw , y * th , tw , th )
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
elseif self.orientation == " staggered " or self.orientation == " hexagonal " then
if self.staggeraxis == " y " then
if self.staggerindex == " odd " then
if y % 2 == 0 then
tx = x * tw + tw / 2 + ( self.hexsidelength or 0 ) + tile.offset . x
else
tx = x * tw + ( self.hexsidelength or 0 ) + tile.offset . x
end
else
if y % 2 == 0 then
tx = x * tw + ( self.hexsidelength or 0 ) + tile.offset . x
else
tx = x * tw + tw / 2 + ( self.hexsidelength or 0 ) + tile.offset . x
end
end
if self.orientation == " hexagonal " then
ty = y * ( th - ( th - self.hexsidelength ) / 2 ) + tile.offset . y + ( th - ( th - self.hexsidelength ) / 2 )
else
ty = y * th / 2 + tile.offset . y + th / 2
end
2014-12-06 04:35:32 +00:00
else
2016-04-26 13:23:11 +00:00
if self.staggerindex == " odd " then
if x % 2 == 0 then
ty = y * th + th / 2 + ( self.hexsidelength or 0 ) + tile.offset . y
else
ty = y * th + ( self.hexsidelength or 0 ) + tile.offset . y
end
else
if x % 2 == 0 then
ty = y * th + ( self.hexsidelength or 0 ) + tile.offset . y
else
ty = y * th + th / 2 + ( self.hexsidelength or 0 ) + tile.offset . y
end
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
if self.orientation == " hexagonal " then
tx = x * ( tw - ( tw - self.hexsidelength ) / 2 ) + tile.offset . x + ( tw - ( tw - self.hexsidelength ) / 2 )
else
tx = x * tw / 2 + tile.offset . x + tw / 2
end
end
2014-12-06 04:35:32 +00:00
end
id = batch : add ( tile.quad , tx , ty , tile.r , tile.sx , tile.sy )
self.tileInstances [ tile.gid ] = self.tileInstances [ tile.gid ] or { }
2016-04-26 13:23:11 +00:00
table.insert ( self.tileInstances [ tile.gid ] , {
layer = layer ,
batch = batch ,
id = id ,
gid = tile.gid ,
x = tx ,
y = ty ,
r = tile.r ,
oy = 0
} )
2014-12-06 04:35:32 +00:00
end
end
end
layer.batches = batches
end
2016-04-26 13:23:11 +00:00
--- Batch Tiles in Object Layer for improved draw speed
-- @param layer The Object Layer
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : setObjectSpriteBatches ( layer )
2016-04-26 13:23:11 +00:00
local newBatch = love.graphics . newSpriteBatch
local tw = self.tilewidth
local th = self.tileheight
local batches = { }
2014-12-06 04:35:32 +00:00
for _ , object in ipairs ( layer.objects ) do
if object.gid then
2016-04-26 13:23:11 +00:00
local tile = self.tiles [ object.gid ] or self : setFlippedGID ( object.gid )
local ts = tile.tileset
2014-12-06 04:35:32 +00:00
local image = self.tilesets [ tile.tileset ] . image
batches [ ts ] = batches [ ts ] or newBatch ( image , 100 )
local batch = batches [ ts ]
2016-04-26 13:23:11 +00:00
local tx = object.x + tw + tile.offset . x
local ty = object.y + tile.offset . y
local tr = math.rad ( object.rotation )
local oy = 0
2014-12-06 04:35:32 +00:00
-- Compensation for scale/rotation shift
2016-04-26 13:23:11 +00:00
if tile.sx == 1 and tile.sy == 1 then
if tr ~= 0 then
ty = ty + th
oy = th
end
else
if tile.sx < 0 then tx = tx + tw end
if tile.sy < 0 then ty = ty + th end
if tr > 0 then tx = tx + tw end
if tr < 0 then ty = ty + th end
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
id = batch : add ( tile.quad , tx , ty , tr , tile.sx , tile.sy , 0 , oy )
2014-12-06 04:35:32 +00:00
self.tileInstances [ tile.gid ] = self.tileInstances [ tile.gid ] or { }
2016-04-26 13:23:11 +00:00
table.insert ( self.tileInstances [ tile.gid ] , {
layer = layer ,
batch = batch ,
id = id ,
gid = tile.gid ,
x = tx ,
y = ty ,
r = tr ,
oy = oy
} )
2014-12-06 04:35:32 +00:00
end
end
layer.batches = batches
end
2016-04-26 13:23:11 +00:00
--- Only draw what is visible on screen for improved draw speed
-- @param tx Translate X axis (in pixels)
-- @param ty Translate Y axis (in pixels)
-- @param w Width of screen (in pixels)
-- @param h Height of screen (in pixels)
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : setDrawRange ( tx , ty , w , h )
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 ) )
2016-04-26 13:23:11 +00:00
elseif self.orientation == " staggered " or self.orientation == " hexagonal " then
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
self.drawRange . sx = sx
self.drawRange . sy = sy
self.drawRange . ex = ex
self.drawRange . ey = ey
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- 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
2014-12-06 04:35:32 +00:00
function Map : addCustomLayer ( name , index )
2016-04-26 13:23:11 +00:00
local index = index or # self.layers + 1
2014-12-06 04:35:32 +00:00
local layer = {
2016-04-26 13:23:11 +00:00
type = " customlayer " ,
name = name ,
visible = true ,
opacity = 1 ,
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
--- Convert another Layer into a Custom Layer
-- @param index Index or name of Layer to convert
-- @return table Custom Layer
2014-12-06 04:35:32 +00:00
function Map : convertToCustomLayer ( index )
local layer = assert ( self.layers [ index ] , " Layer not found: " .. index )
2016-04-26 13:23:11 +00:00
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
2014-12-06 04:35:32 +00:00
function layer : draw ( ) return end
function layer : update ( dt ) return end
2016-04-26 13:23:11 +00:00
return layer
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Remove a Layer from the Layer stack
-- @param index Index or name of Layer to convert
-- @return nil
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
-- Remove tile instances
if layer.batches then
for gid , 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
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Animate Tiles and update every Layer
-- @param dt Delta Time
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : update ( dt )
2016-04-26 13:23:11 +00:00
for gid , tile in pairs ( self.tiles ) do
local update = false
2014-12-06 04:35:32 +00:00
if tile.animation then
tile.time = tile.time + dt * 1000
2016-04-26 13:23:11 +00:00
2014-12-06 04:35:32 +00:00
while tile.time > tonumber ( tile.animation [ tile.frame ] . duration ) do
2016-04-26 13:23:11 +00:00
update = true
tile.time = tile.time - tonumber ( tile.animation [ tile.frame ] . duration )
2014-12-06 04:35:32 +00:00
tile.frame = tile.frame + 1
2016-04-26 13:23:11 +00:00
2014-12-06 04:35:32 +00:00
if tile.frame > # tile.animation then tile.frame = 1 end
end
2016-04-26 13:23:11 +00:00
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 )
2014-12-06 04:35:32 +00:00
end
end
end
end
for _ , layer in ipairs ( self.layers ) do
layer : update ( dt )
end
end
2016-04-26 13:23:11 +00:00
--- Draw every Layer
-- @return nil
function Map : draw ( )
local current_canvas = love.graphics . getCanvas ( )
love.graphics . setCanvas ( self.canvas )
if self.canvas . clear then
self.canvas : clear ( )
else
local r , g , b , a = love.graphics . getBackgroundColor ( )
love.graphics . clear ( r , g , b , a , self.canvas )
end
2014-12-06 04:35:32 +00:00
for _ , layer in ipairs ( self.layers ) do
if layer.visible and layer.opacity > 0 then
self : drawLayer ( layer )
end
end
2016-04-26 13:23:11 +00:00
love.graphics . setCanvas ( current_canvas )
love.graphics . push ( )
love.graphics . origin ( )
love.graphics . draw ( self.canvas )
love.graphics . pop ( )
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Draw an individual Layer
-- @param layer The Layer to draw
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : drawLayer ( layer )
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( 255 , 255 , 255 , 255 * layer.opacity )
2014-12-06 04:35:32 +00:00
layer : draw ( )
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( 255 , 255 , 255 , 255 )
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Default draw function for Tile Layers
-- @param layer The Tile Layer to draw
-- @return nil
2014-12-06 04:35:32 +00:00
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 )
2016-04-26 13:23:11 +00:00
local ix = 1
local iy = 1
2014-12-06 04:35:32 +00:00
local mx = math.ceil ( self.width / bw )
local my = math.ceil ( self.height / bh )
2016-04-26 13:23:11 +00:00
-- Determine order to draw batches
-- Defaults to right-down
if self.renderorder == " right-up " then
sx , ex , ix = sx , ex , 1
sy , ey , iy = ey , sy , - 1
elseif self.renderorder == " left-down " then
sx , ex , ix = ex , sx , - 1
sy , ey , iy = sy , ey , 1
elseif self.renderorder == " left-up " then
sx , ex , ix = ex , sx , - 1
sy , ey , iy = ey , sy , - 1
end
for by = sy , ey , iy do
for bx = sx , ex , ix do
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
love.graphics . draw ( batch , math.floor ( layer.x ) , math.floor ( layer.y ) )
2014-12-06 04:35:32 +00:00
end
end
end
end
end
end
2016-04-26 13:23:11 +00:00
--- Default draw function for Object Layers
-- @param layer The Object Layer to draw
-- @return nil
2014-12-06 04:35:32 +00:00
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 " )
2016-04-26 13:23:11 +00:00
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 }
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( shadow )
love.graphics . line ( vertices [ 2 ] )
love.graphics . setColor ( line )
love.graphics . line ( vertices [ 1 ] )
2014-12-06 04:35:32 +00:00
return
elseif shape == " polygon " then
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( fill )
if not love.math . isConvex ( vertices [ 1 ] ) then
local triangles = love.math . triangulate ( vertices [ 1 ] )
2014-12-06 04:35:32 +00:00
for _ , triangle in ipairs ( triangles ) do
2016-04-26 13:23:11 +00:00
love.graphics . polygon ( " fill " , triangle )
2014-12-06 04:35:32 +00:00
end
else
2016-04-26 13:23:11 +00:00
love.graphics . polygon ( " fill " , vertices [ 1 ] )
2014-12-06 04:35:32 +00:00
end
else
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( fill )
love.graphics . polygon ( " fill " , vertices [ 1 ] )
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( shadow )
love.graphics . polygon ( " line " , vertices [ 2 ] )
love.graphics . setColor ( line )
love.graphics . polygon ( " line " , vertices [ 1 ] )
2014-12-06 04:35:32 +00:00
end
for _ , object in ipairs ( layer.objects ) do
2016-04-26 13:23:11 +00:00
if object.shape == " rectangle " and not object.gid then
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
love.graphics . setColor ( reset )
2014-12-06 04:35:32 +00:00
for _ , batch in pairs ( layer.batches ) do
2016-04-26 13:23:11 +00:00
love.graphics . draw ( batch , 0 , 0 )
2014-12-06 04:35:32 +00:00
end
end
2016-04-26 13:23:11 +00:00
--- Default draw function for Image Layers
-- @param layer The Image Layer to draw
-- @return nil
2014-12-06 04:35:32 +00:00
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
2016-04-26 13:23:11 +00:00
love.graphics . draw ( layer.image , layer.x , layer.y )
2014-12-06 04:35:32 +00:00
end
end
2016-04-26 13:23:11 +00:00
--- 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)
-- @return nil
2014-12-06 04:35:32 +00:00
function Map : resize ( w , h )
2016-04-26 13:23:11 +00:00
w = w or love.graphics . getWidth ( )
h = h or love.graphics . getHeight ( )
self.canvas = love.graphics . newCanvas ( w , h )
self.canvas : setFilter ( " nearest " , " nearest " )
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- 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
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
if realgid >= bit30 then
realgid = realgid - bit30
flipY = not flipY
end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
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 ]
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Get custom properties from Layer
-- @param layer The Layer
-- @return table List of properties
function Map : getLayerProperties ( layer )
local l = self.layers [ layer ]
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
if not l then return { } end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return l.properties
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- 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 ]
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
if not tile then return { } end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return tile.properties
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- 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
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
if not o then return { } end
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return o.properties
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Project isometric position to orthoganal position
-- @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 pixels)
-- @return number The Y axis location of the point (in pixels)
function Map : convertIsometricToScreen ( x , y )
2014-12-06 04:35:32 +00:00
local mw = self.width
2016-04-26 13:23:11 +00:00
local tw = self.tilewidth
local th = self.tileheight
2014-12-06 04:35:32 +00:00
local ox = mw * tw / 2
2016-04-26 13:23:11 +00:00
local sx = ( x - y ) + ox
local sy = ( x + y ) / 2
2014-12-06 04:35:32 +00:00
return sx , sy
end
2016-04-26 13:23:11 +00:00
--- Project orthoganal position to isometric position
-- @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 pixels)
-- @return number The Y axis location of the point (in pixels)
function Map : convertScreenToIsometric ( x , y )
2014-12-06 04:35:32 +00:00
local mw = self.width
2016-04-26 13:23:11 +00:00
local mh = self.height
local tw = self.tilewidth
local th = self.tileheight
2014-12-06 04:35:32 +00:00
local ox = mw * tw / 2
2016-04-26 13:23:11 +00:00
local oy = mh * th / 2
local tx = ( x / 2 + y ) - ox / 2
local ty = ( - x / 2 + y ) + oy
2014-12-06 04:35:32 +00:00
return tx , ty
end
2016-04-26 13:23:11 +00:00
--- Convert tile space to screen space
-- @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 : convertWorldToScreen ( x , y )
if self.orientation == " orthogonal " then
local tw = self.tilewidth
local th = self.tileheight
local sx = x * tw
local sy = y * th
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return sx , sy
elseif self.orientation == " isometric " then
local mw = self.width
local tw = self.tilewidth
local th = self.tileheight
local ox = mw * tw / 2
local sx = ( x - y ) * tw / 2 + ox
local sy = ( x + y ) * th / 2
return sx , sy
elseif self.orientation == " staggered " then
local tw = self.tilewidth
local th = 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
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return sx , sy
end
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
--- Convert screen space to tile space
-- @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 : convertScreenToWorld ( x , y )
if self.orientation == " orthogonal " then
local tw = self.tilewidth
local th = self.tileheight
local tx = x / tw
local ty = y / th
return tx , ty
elseif self.orientation == " isometric " then
local mw = self.width
local tw = self.tilewidth
local th = self.tileheight
local ox = mw * tw / 2
local tx = y / th + ( x - ox ) / tw
local ty = y / th - ( x - ox ) / tw
return tx , ty
elseif self.orientation == " staggered " then
local function topLeft ( x , y )
if ( math.ceil ( y ) % 2 ) then
return x , y - 1
else
return x - 1 , y - 1
end
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
local function topRight ( x , y )
if ( math.ceil ( y ) % 2 ) then
return x + 1 , y - 1
else
return x , y - 1
end
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
local function bottomLeft ( x , y )
if ( math.ceil ( y ) % 2 ) then
return x , y + 1
else
return x - 1 , y + 1
end
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
local function bottomRight ( x , y )
if ( math.ceil ( y ) % 2 ) then
return x + 1 , y + 1
else
return x , y + 1
end
2014-12-06 04:35:32 +00:00
end
2016-04-26 13:23:11 +00:00
local tw = self.tilewidth
local th = 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
2014-12-06 04:35:32 +00:00
2016-04-26 13:23:11 +00:00
return tx , ty
end
2014-12-06 04:35:32 +00:00
end
return Map
2016-04-26 13:23:11 +00:00
--- 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 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 two 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 = love.graphics.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
-- love.graphics.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