mirror of
https://github.com/Reuh/ubiquitousse.git
synced 2025-10-27 17:19:31 +00:00
706 lines
21 KiB
Text
706 lines
21 KiB
Text
--- [LDtk](https://ldtk.io/) level importer for Lua and drawing using LÖVE.
|
|
-- Support most LDtk features, and allow easy usage in LÖVE projects.
|
|
--
|
|
-- Every unit is in pixel in the API unless written otherwise.
|
|
-- Colors are reprsented as a table `{r,g,b}` where `r`,`b`,`g` in [0-1].
|
|
--
|
|
-- This modules returns a single function, @{LDtk}(path).
|
|
--
|
|
-- No mandatory dependency.
|
|
-- Optionally requires LÖVE `love.graphics` (drawing Image, SpriteBatch, Quad) for drawing only.
|
|
--
|
|
-- @module ldtk
|
|
-- @require love
|
|
-- @usage
|
|
-- local ldtk = require("ubiquitousse.ldtk")
|
|
--
|
|
-- -- load ldtk project file
|
|
-- local project = ldtk("example.ldtk")
|
|
--
|
|
-- -- can define callbacks when loading: for example to setup entities defined in LDtk
|
|
-- local callbacks = {
|
|
-- onAddEntity = function(entity)
|
|
-- -- handle entity...
|
|
-- end
|
|
-- }
|
|
--
|
|
-- -- load every level, with callbacks
|
|
-- for _, lvl in ipairs(project.levels) do lvl:load(callbacks) end
|
|
--
|
|
-- function love.draw()
|
|
-- -- draw every level
|
|
-- for _, lvl in ipairs(project.levels) do lvl:draw() end
|
|
-- end
|
|
|
|
-- TODO: give associated tile & color with enum values, also give enum info
|
|
|
|
--- LÖVE wrappers/placeholder
|
|
let lg = (love or {}).graphics
|
|
let newQuad
|
|
if lg then
|
|
newQuad = lg.newQuad
|
|
else
|
|
newQuad = (x, y, w , h, image)
|
|
return { x, y, w, h }
|
|
end
|
|
end
|
|
|
|
--- json helpers
|
|
let json_decode = require((...):gsub("ldtk$", "json")).decode
|
|
let readJson = (file)
|
|
let f = assert(io.open(file, "r"))
|
|
local t = json_decode(f:read("*a"))
|
|
f:close()
|
|
return t
|
|
end
|
|
|
|
--- color helpers
|
|
let parseColor = (str)
|
|
local r, g, b = str:match("^#(..)(..)(..)")
|
|
r, g, b = tonumber(r, 16), tonumber(g, 16), tonumber(b, 16)
|
|
return { r/255, g/255, b/255 }
|
|
end
|
|
let white = {1,1,1}
|
|
|
|
--- returns a lua table from some fieldInstances
|
|
let toLua = (type, val)
|
|
if val == nil then return val end
|
|
if type:match("^Array%<") then
|
|
local itype = type:match("^Array%<(.*)%>$")
|
|
for i, v in ipairs(val) do
|
|
val[i] = toLua(itype, v)
|
|
end
|
|
elseif type == "Color" then
|
|
return parseColor(val)
|
|
elseif type == "Point" then
|
|
return { x = val.cx, y = val.cy }
|
|
end
|
|
return val
|
|
end
|
|
let getFields = (f)
|
|
local t = {}
|
|
for _, v in ipairs(f) do
|
|
t[v.__identifier] = toLua(v.__type, v.__value)
|
|
end
|
|
return t
|
|
end
|
|
|
|
let tileset_mt
|
|
|
|
--- cached values
|
|
let make_cache = (new_fn)
|
|
return setmetatable({}, {
|
|
__mode = "v",
|
|
__call = (cache, id)
|
|
if not cache[id] then
|
|
cache[id] = new_fn(id)
|
|
end
|
|
return cache[id]
|
|
end
|
|
})
|
|
end
|
|
let cache = {
|
|
tileset = make_cache((tilesetDef)
|
|
return tileset_mt._init(tilesetDef)
|
|
end),
|
|
image = make_cache((path)
|
|
if lg then
|
|
return lg.newImage(path)
|
|
else
|
|
return path
|
|
end
|
|
end),
|
|
}
|
|
|
|
--- Tileset object.
|
|
-- Stores the image associated with the tileset; can be shared among several layers and levels.
|
|
-- @type Tileset
|
|
tileset_mt = {
|
|
_newQuad = :(x, y, width, height)
|
|
return newQuad(x, y, width, height, @image)
|
|
end,
|
|
_getTileQuad = :(tileid, x, y, size)
|
|
if not @_tileQuads[tileid] then
|
|
@_tileQuads[tileid] = @_newQuad(x, y, size, size)
|
|
end
|
|
return @_tileQuads[tileid]
|
|
end,
|
|
_init = (tilesetDef)
|
|
local t = {
|
|
--- The tileset LÖVE image object.
|
|
-- If LÖVE is not available, this is the path to the image (string).
|
|
image = cache.image(tilesetDef.path),
|
|
|
|
_tileQuads = {}
|
|
}
|
|
return setmetatable(t, tileset_mt)
|
|
end
|
|
}
|
|
tileset_mt.__index = tileset_mt
|
|
|
|
--- Layer object.
|
|
--
|
|
-- Part of a @{Level}.
|
|
--
|
|
-- @type Layer
|
|
let layer_mt = {
|
|
--- Draw the current layer.
|
|
-- Assumes we are currently in level coordinates (i.e. level top-left is at 0,0).
|
|
-- @require love
|
|
draw = :()
|
|
if @visible then
|
|
lg.push()
|
|
lg.translate(@x, @y)
|
|
if @spritebatch then
|
|
lg.setColor(1, 1, 1, @opacity)
|
|
lg.draw(@spritebatch)
|
|
elseif @intTiles then
|
|
for _, t in ipairs(@intTiles) do
|
|
lg.setColor(t.color)
|
|
lg.rectangle("fill", t.x, t.y, t.layer.gridSize, t.layer.gridSize)
|
|
end
|
|
elseif @entities then
|
|
for _, e in ipairs(@entities) do
|
|
if e.draw then e:draw() end
|
|
end
|
|
end
|
|
lg.pop()
|
|
end
|
|
end,
|
|
|
|
_unloadCallbacks = :(callbacks)
|
|
local onRemoveTile = callbacks.onRemoveTile
|
|
if @tiles and onRemoveTile then
|
|
for _, t in ipairs(@tiles) do
|
|
onRemoveTile(t)
|
|
end
|
|
end
|
|
local onRemoveIntTile = callbacks.onRemoveIntTile
|
|
if @intTiles and onRemoveIntTile then
|
|
for _, t in ipairs(@intTiles) do
|
|
onRemoveIntTile(t)
|
|
end
|
|
end
|
|
local onRemoveEntity = callbacks.onRemoveEntity
|
|
if @entities and onRemoveEntity then
|
|
for _, e in ipairs(@entities) do
|
|
onRemoveEntity(e)
|
|
end
|
|
end
|
|
end,
|
|
_init = (layer, level, order, callbacks)
|
|
let gridSize = layer.__gridSize
|
|
let t = {
|
|
--- `Level` this layer belongs to.
|
|
-- @ftype Level
|
|
level = level,
|
|
--- The layer name.
|
|
-- @ftype string
|
|
identifier = layer.__identifier,
|
|
--- Type of layer: IntGrid, Entities, Tiles or AutoLayer.
|
|
-- @ftype string
|
|
type = layer.__type,
|
|
--- Whether the layer is visible or not.
|
|
-- @ftype boolean
|
|
visible = layer.visible,
|
|
--- The layer opacity (0-1).
|
|
-- @ftype number
|
|
opacity = layer.opacity,
|
|
--- The layer order: smaller order means it is on top.
|
|
-- @ftype number
|
|
order = order,
|
|
--- X position of the layer relative to the level.
|
|
-- @ftype number
|
|
x = layer.__pxTotalOffsetX,
|
|
--- Y position of the layer relative to the level.
|
|
-- @ftype number
|
|
y = layer.__pxTotalOffsetY,
|
|
--- Size of the grid on this layer.
|
|
-- @ftype number
|
|
gridSize = gridSize,
|
|
--- Width of the layer, in grid units.
|
|
-- @ftype number
|
|
gridWidth = layer.__cWid,
|
|
--- Height of the layer, in grid units.
|
|
-- @ftype number
|
|
gridHeight = layer.__cHei,
|
|
--- _(Entities layer only)_ List of `Entity` in the layer.
|
|
-- @ftype {Entity,...}
|
|
entities = nil,
|
|
--- _(Tiles, AutoLayer, or IntGrid with AutoLayer rules layers only)_ List of `Tile`s in the layer.
|
|
-- @ftype {Tile,...}
|
|
-- @ftype nil if not applicable
|
|
tiles = nil,
|
|
--- _(Tiles, AutoLayer, or IntGrid with AutoLayer rules layers only)_ `Tileset` object associated with the layer.
|
|
-- @ftype Tileset
|
|
-- @ftype nil if not applicable
|
|
tileset = nil,
|
|
--- _(Tiles, AutoLayer, or IntGrid with AutoLayer rules layers only)_ [LÖVE SpriteBatch](https://love2d.org/wiki/SpriteBatch) containing the layer.
|
|
-- @ftype SpriteBatch
|
|
-- @ftype nil if LÖVE not available.
|
|
-- @require love
|
|
spritebatch = nil,
|
|
--- _(IntGrid without AutoLayer rules layer only)_ list of `IntTile`s in the layer.
|
|
-- @ftype {IntTile,...}
|
|
-- @ftype nil if not applicable
|
|
intTiles = nil,
|
|
}
|
|
-- Layers with an associated tileset (otherwise ignore as there is nothing to draw) (Tiles, AutoLayer & IntGrid with AutoLayer rules)
|
|
if layer.__tilesetDefUid then
|
|
t.tiles = {}
|
|
local tilesetData = level.project._tilesetData[layer.__tilesetDefUid]
|
|
t.tileset = cache.tileset(tilesetData)
|
|
local tiles = layer.__type == "Tiles" and layer.gridTiles or layer.autoLayerTiles
|
|
local onAddTile = callbacks.onAddTile
|
|
if lg then t.spritebatch = lg.newSpriteBatch(t.tileset.image) end
|
|
for _, tl in ipairs(tiles) do
|
|
let quad = t.tileset:_getTileQuad(tl.t, tl.src[1], tl.src[2], gridSize)
|
|
let sx, sy = 1, 1
|
|
let x, y = tl.px[1], tl.px[2]
|
|
--- Tile object.
|
|
--
|
|
-- This represent the tiles from a Tiles, AutoLayer or IntGrid with AutoLayer rules layer.
|
|
--
|
|
-- Can be retrived from the `Layer.tiles` list or `onAddTile` level load callback.
|
|
--
|
|
-- @type Tile
|
|
let tile = {
|
|
--- `Layer` the tile belongs to.
|
|
-- @ftype Layer
|
|
layer = t,
|
|
--- X position of the tile relative to the layer.
|
|
-- @ftype number
|
|
x = x,
|
|
--- Y position of the tile relative to the layer.
|
|
-- @ftype number
|
|
y = y,
|
|
--- Whether the tile is flipped horizontally.
|
|
-- @ftype boolean
|
|
flipX = false,
|
|
--- Whether the tile is flipped vertically.
|
|
-- @ftype boolean
|
|
flipY = false,
|
|
--- Tags associated with the tile: can be used either as a list of tags or a map of activated tags tags[name] == true.
|
|
-- @ftype {"tag",["tag"]=true,...}
|
|
tags = tilesetData[tl.t].tags,
|
|
--- Custom data associated with the tile, if any.
|
|
-- @ftype string
|
|
data = tilesetData[tl.t].data,
|
|
--- Quad associated with the tile (relative to the layer's tileset).
|
|
-- @ftype LÖVE Quad if LÖVE is available
|
|
-- @ftype table { x, y, width, height } if LÖVE not available
|
|
quad = quad
|
|
}
|
|
if tl.f == 1 or tl.f == 3 then
|
|
sx = -1
|
|
x += gridSize
|
|
tile.flipX = true
|
|
end
|
|
if tl.f == 2 or tl.f == 3 then
|
|
sy = -1
|
|
y += gridSize
|
|
tile.flipY = true
|
|
end
|
|
if t.spritebatch then t.spritebatch:add(quad, x, y, 0, sx, sy) end
|
|
table.insert(t.tiles, tile)
|
|
if onAddTile then onAddTile(tile) end
|
|
end
|
|
-- IntGrid
|
|
elseif layer.__type == "IntGrid" then
|
|
t.intTiles = {}
|
|
local onAddIntTile = callbacks.onAddIntTile
|
|
local values = level.project._layerDef[layer.layerDefUid].intGridValues
|
|
for i, tl in ipairs(layer.intGridCsv) do
|
|
if tl > 0 then
|
|
let y = math.floor((i-1) / t.gridWidth) * gridSize
|
|
let x = ((i-1) % t.gridWidth) * gridSize
|
|
--- IntTile object.
|
|
--
|
|
-- This represent the tiles from a IntGrid without AutoLayer rules layer.
|
|
--
|
|
-- Can be retrived from the @{intTiles} list or `onAddIntTile` level load callback.
|
|
--
|
|
-- @type IntTile
|
|
let tile = {
|
|
--- `Layer` the IntTile belongs to.
|
|
-- @ftype Layer
|
|
layer = t,
|
|
--- X position of the IntTile relative to the layer.
|
|
-- @ftype number
|
|
x = x,
|
|
--- Y position of the IntTile relative to the layer.
|
|
-- @ftype number
|
|
y = y,
|
|
--- Name of the IntTile.
|
|
-- @ftype string
|
|
identifier = values[tl].identifier,
|
|
--- Integer value of the IntTile.
|
|
-- @ftype number
|
|
value = tl,
|
|
--- Color of the IntTile.
|
|
-- @ftype table {r,g,b} with r,g,b in [0-1]
|
|
color = values[tl].color
|
|
}
|
|
table.insert(t.intTiles, tile)
|
|
if onAddIntTile then onAddIntTile(tile) end
|
|
end
|
|
end
|
|
end
|
|
-- Entities layers
|
|
if layer.__type == "Entities" then
|
|
t.entities = {}
|
|
local onAddEntity = callbacks.onAddEntity
|
|
for _, e in ipairs(layer.entityInstances) do
|
|
let entityDef = level.project._entityData[e.defUid]
|
|
--- Entity object.
|
|
--
|
|
-- This represent an entity from an Entities layer.
|
|
--
|
|
-- Can be retrived from the @{entities} list or `onAddEntity` level load callback.
|
|
--
|
|
-- @type Entity
|
|
let entity = {
|
|
--- `Layer` this entity belongs to.
|
|
-- @ftype Layer
|
|
layer = t,
|
|
--- The entity name.
|
|
-- @ftype string
|
|
identifier = e.__identifier,
|
|
--- X position of the entity relative to the layer.
|
|
-- @ftype number
|
|
x = e.px[1],
|
|
--- Y position of the entity relative to the layer.
|
|
-- @ftype number
|
|
y = e.px[2],
|
|
--- The entity width.
|
|
-- @ftype number
|
|
width = e.width,
|
|
--- The entity height.
|
|
-- @ftype number
|
|
height = e.height,
|
|
--- Scale factor on x axis relative to original entity size.
|
|
-- @ftype number
|
|
sx = e.width / entityDef.width,
|
|
--- Scale factor on y axis relative to original entity size.
|
|
-- @ftype number
|
|
sy = e.height / entityDef.height,
|
|
--- The entity pivot point x position relative to the entity.
|
|
-- @ftype number
|
|
pivotX = e.__pivot[1] * e.width,
|
|
--- The entity pivot point x position relative to the entity.
|
|
-- @ftype number
|
|
pivotY = e.__pivot[2] * e.height,
|
|
--- Entity color.
|
|
-- @ftype table {r,g,b} with r,g,b in [0-1]
|
|
color = entityDef.color,
|
|
--- Tile associated with the entity, if any. Is a table { tileset = associated tileset object, quad = associated quad }.
|
|
-- `quad` is a LÖVE Quad if LÖVE is available, otherwise a table `{ x, y, width, height }`.
|
|
-- @ftype table
|
|
tile = nil,
|
|
--- Map of `CustomFields` of the entity.
|
|
-- @ftype CustomFields
|
|
fields = getFields(e.fieldInstances),
|
|
--- Called for the entity when drawing the associated entity layer (you will likely want to redefine it).
|
|
--
|
|
-- By default, this draws the tile associated with the entity if there is one, or a rectangle around the entity position otherwise.
|
|
-- @require love
|
|
draw = :()
|
|
if @tile then
|
|
let _, _, w, h = @tile.quad:getViewport()
|
|
lg.setColor(white)
|
|
lg.draw(@tile.tileset.image, @tile.quad, @x-@pivotX, @y-@pivotY, 0, @width / w, @height / h)
|
|
else
|
|
lg.setColor(@color)
|
|
lg.rectangle("line", @x-@pivotX, @y-@pivotY, @width, @height)
|
|
end
|
|
end
|
|
}
|
|
if e.__tile then
|
|
local tileset = cache.tileset(level.project._tilesetData[e.__tile.tilesetUid])
|
|
local srcRect = e.__tile.srcRect
|
|
local quad = tileset:_newQuad(srcRect[1], srcRect[2], srcRect[3], srcRect[4])
|
|
entity.tile = {
|
|
tileset = tileset,
|
|
quad = quad
|
|
}
|
|
end
|
|
table.insert(t.entities, entity)
|
|
if onAddEntity then onAddEntity(entity) end
|
|
end
|
|
end
|
|
return setmetatable(t, layer_mt)
|
|
end
|
|
}
|
|
layer_mt.__index = layer_mt
|
|
|
|
--- Level object.
|
|
--
|
|
-- Levels are not automatically loaded in order to not waste ressources if your project is large; so before drawing or operating on a level, you will need to call its @{Level:load} method.
|
|
--
|
|
-- Part of a @{Project}.
|
|
--
|
|
-- @type Level
|
|
let level_mt = {
|
|
--- Draw this level (background and layers).
|
|
-- Assumes we are currently in world coordinates (i.e. world top-left is at 0,0).
|
|
-- The level must be loaded.
|
|
-- Will draw the eventual backgrounds and all the layers in the level.
|
|
-- @require love
|
|
draw = :()
|
|
assert(@loaded == true, "level not loaded")
|
|
lg.push()
|
|
lg.translate(@x, @y)
|
|
@drawBackground()
|
|
-- layers
|
|
for _, l in ipairs(@layers) do
|
|
l:draw()
|
|
end
|
|
lg.pop()
|
|
end,
|
|
--- Draw this level background.
|
|
-- Assumes we are currently in level coordinates (i.e. level top-left is at 0,0).
|
|
-- The level must be loaded.
|
|
-- @require love
|
|
drawBackground = :()
|
|
assert(@loaded == true, "level not loaded")
|
|
-- background color
|
|
lg.setColor(@background.color)
|
|
lg.rectangle("fill", 0, 0, @width, @height)
|
|
-- background image
|
|
lg.setColor(white)
|
|
let bgImage = @background.image
|
|
if bgImage then
|
|
lg.draw(bgImage.image, bgImage.quad, bgImage.x, bgImage.y, 0, bgImage.sx, bgImage.sy)
|
|
end
|
|
end,
|
|
|
|
--- Load the level.
|
|
-- Will load every layer in the level and the associated images.
|
|
--
|
|
-- You can optionally specify some callbacks for the loading process:
|
|
--
|
|
-- * `onAddLayer(layer)` will be called for every new layer loaded, with the @{Layer} as sole argument
|
|
-- * `onAddTile(tile)` will be called for every new tile loaded, with the @{Tile} as sole argument
|
|
-- * `onAddIntTile(tile)` will be called for every new IntGrid tile loaded, with the @{IntTile} as sole argument
|
|
-- * `onAddEntity(entity)` will be called for every new entity loaded, with the @{Entity} as sole argument
|
|
--
|
|
-- These callbacks should allow you to capture all the important elements needed to use the level, so you can hopefully
|
|
-- integrate it into your current game engine easily.
|
|
--
|
|
-- @tparam[opt] table callbacks
|
|
load = :(callbacks={})
|
|
assert(@loaded == false, "level already loaded")
|
|
if @_json.bgRelPath then
|
|
let pos = @_json.__bgPos
|
|
let cropRect = pos.cropRect
|
|
let image = cache.image(@project._directory..@_json.bgRelPath)
|
|
@background.image = {
|
|
image = image,
|
|
quad = newQuad(cropRect[1], cropRect[2], cropRect[3], cropRect[4], image),
|
|
x = pos.topLeftPx[1],
|
|
y = pos.topLeftPx[2],
|
|
sx = pos.scale[1],
|
|
sy = pos.scale[1]
|
|
}
|
|
end
|
|
let layerInstances
|
|
if @_json.externalRelPath then
|
|
layerInstances = readJson(@project._directory..@_json.externalRelPath).layerInstances
|
|
else
|
|
layerInstances = @_json.layerInstances
|
|
end
|
|
@layers = {}
|
|
let onAddLayer = callbacks.onAddLayer
|
|
for i=#layerInstances, 1, -1 do
|
|
local layer = layer_mt._init(layerInstances[i], @, i, callbacks)
|
|
table.insert(@layers, layer)
|
|
if onAddLayer then onAddLayer(layer) end
|
|
end
|
|
@loaded = true
|
|
end,
|
|
--- Unload the level.
|
|
-- Images loaded by the level will be freed on the next garbage collection cycle.
|
|
--
|
|
-- You can optionally specify some callbacks for the unloading process:
|
|
--
|
|
-- * `onAddLayer(layer)` will be called for every new layer unloaded, with the @{Layer} as sole argument
|
|
-- * `onAddTile(tile)` will be called for every new tile unloaded, with the @{Tile} as sole argument
|
|
-- * `onAddIntTile(tile)` will be called for every new IntGrid tile unloaded, with the @{IntTile} as sole argument
|
|
-- * `onAddEntity(entity)` will be called for every new entity unloaded, with the @{Entity} as sole argument
|
|
--
|
|
-- @tparam[opt] table callbacks
|
|
unload = :(callbacks={})
|
|
assert(@loaded == true, "level not loaded")
|
|
let onRemoveLayer = callbacks.onRemoveLayer
|
|
for _, l in ipairs(@layers) do
|
|
l:_unloadCallbacks(callbacks)
|
|
if onRemoveLayer then onRemoveLayer(l) end
|
|
end
|
|
@loaded = false
|
|
@background.image = nil
|
|
@layers = nil
|
|
end,
|
|
|
|
_init = (level, project)
|
|
let t = {
|
|
--- `Project` this level belongs to.
|
|
-- @ftype Project
|
|
project = project,
|
|
--- Whether this level is currently loaded or not.
|
|
-- @ftype boolean
|
|
loaded = false,
|
|
--- The level name.
|
|
-- @ftype string
|
|
identifier = level.identifier,
|
|
--- The level x position.
|
|
-- @ftype number
|
|
x = level.worldX,
|
|
--- The level y position.
|
|
-- @ftype number
|
|
y = level.worldY,
|
|
--- The level width.
|
|
-- @ftype number
|
|
width = level.pxWid,
|
|
--- The level height.
|
|
-- @ftype number
|
|
height = level.pxHei,
|
|
--- Map of `CustomFields` of the level (table).
|
|
-- @ftype CustomFields
|
|
fields = getFields(level.fieldInstances),
|
|
--- List of `Layer`s in the level (table).
|
|
-- @ftype {Layer,...}
|
|
layers = nil,
|
|
--- Level background.
|
|
--
|
|
-- If there is a background image, `background.image` contains a table `{image=image, x=number, y=number, sx=number, sy=number}`
|
|
-- where `image` is the LÖVE image (or image filepath if LÖVE not available) `x` and `y` are the top-left position,
|
|
-- and `sx` and `sy` the horizontal and vertical scale factors.
|
|
-- @field color backrgound color {r,g,b} with r,g,b in [0-1]
|
|
-- @field image backrgound image information, if any
|
|
background = {
|
|
color = parseColor(level.__bgColor),
|
|
image = nil,
|
|
},
|
|
|
|
-- private
|
|
_json = level,
|
|
}
|
|
return setmetatable(t, level_mt)
|
|
end
|
|
}
|
|
level_mt.__index = level_mt
|
|
|
|
--- Project object.
|
|
--
|
|
-- Returned by @{LDtk}.
|
|
--
|
|
-- @type Project
|
|
let project_mt = {
|
|
_init = (project, directory)
|
|
assert(project.jsonVersion == "0.9.3", "the map was made with LDtk version %s but the importer is made for 0.9.3":format(project.jsonVersion))
|
|
let t = {
|
|
--- List of `Level`s in this project.
|
|
-- @ftype {Level,...}
|
|
levels = nil,
|
|
|
|
-- private
|
|
_directory = directory,
|
|
_layerDef = nil,
|
|
_tilesetData = nil,
|
|
_entityData = nil,
|
|
}
|
|
t.levels = [
|
|
for _, lvl in ipairs(project.levels) do
|
|
push level_mt._init(lvl, t)
|
|
end
|
|
]
|
|
t._tilesetData = [
|
|
for _, ts in ipairs(project.defs.tilesets) do
|
|
@[ts.uid] = {
|
|
path = directory..ts.relPath
|
|
}
|
|
local tilesetData = @[ts.uid]
|
|
for gridx=0, ts.__cWid-1 do
|
|
for gridy=0, ts.__cHei-1 do
|
|
tilesetData[gridx + gridy * ts.__cWid] = {
|
|
tags = {},
|
|
data = nil
|
|
}
|
|
end
|
|
end
|
|
for _, custom in ipairs(ts.customData) do
|
|
tilesetData[custom.tileId].data = custom.data
|
|
end
|
|
for _, tag in ipairs(ts.enumTags) do
|
|
local value = tag.enumValueId
|
|
for _, tileId in ipairs(tag.tileIds) do
|
|
table.insert(tilesetData[tileId].tags, value)
|
|
tilesetData[tileId].tags[value] = true
|
|
end
|
|
end
|
|
end
|
|
]
|
|
t._layerDef = [
|
|
for _, lay in ipairs(project.defs.layers) do
|
|
@[lay.uid] = {
|
|
intGridValues = nil
|
|
}
|
|
local layerDef = @[lay.uid]
|
|
if lay.__type == "IntGrid" then
|
|
layerDef.intGridValues = [
|
|
for _, v in ipairs(lay.intGridValues) do
|
|
@[v.value] = {
|
|
color = parseColor(v.color),
|
|
identifier = v.identifier
|
|
}
|
|
end
|
|
]
|
|
end
|
|
end
|
|
]
|
|
t._entityData = [
|
|
for _, ent in ipairs(project.defs.entities) do
|
|
@[ent.uid] = {
|
|
color = parseColor(ent.color),
|
|
width = ent.width,
|
|
height = ent.height
|
|
}
|
|
end
|
|
]
|
|
return setmetatable(t, project_mt)
|
|
end
|
|
}
|
|
project_mt.__index = project_mt
|
|
|
|
--- Custom fields: map of each field name to its value.
|
|
--
|
|
-- LDtk allows to defined custom fields in some places (`Entity.fields`, `Level.fields`). This library allows you to access them in a table that
|
|
-- map each field name to its value `{["fieldName"]=value,...}`.
|
|
--
|
|
-- @type CustomFields
|
|
|
|
--- Type conversion.
|
|
--
|
|
-- Here is how the values are converted to Lua values:
|
|
--
|
|
-- * Integers, Floats are converted into a Lua number.
|
|
-- * Booleans are converted into a Lua boolean.
|
|
-- * Strings, Multilines are converted in a Lua string.
|
|
-- * Enum are converted into a Lua string giving the currently selected enum value.
|
|
-- * Filepath are converted into a Lua string giving the file path.
|
|
-- * Arrays are converted into a Lua table with the elements in it as a list.
|
|
-- * Points are converted into a Lua table with the fields `x` and `y`: `{ x=number, y=number }`.
|
|
-- * Colors are converted into a Lua table with the red, green and blue components in [0-1] as a list: `{r,g,b}`.
|
|
-- @doc conversion
|
|
|
|
--- LDtk module.
|
|
-- `ubiquitousse.ldtk` returns a single function, @{LDtk}.
|
|
-- @section end
|
|
|
|
--- Load a LDtk project.
|
|
-- @string path to LDtk project file (.ldtk)
|
|
-- @treturn Project the loaded LDtk project
|
|
-- @function LDtk
|
|
return (file)
|
|
return project_mt._init(readJson(file), file:match("^(.-)[^%/%\\]+$"))
|
|
end
|