Difference between revisions of "Tuto tiny-ecs 2d platformer Part 4 LevelX"

From GiderosMobile
 
(2 intermediate revisions by the same user not shown)
Line 6: Line 6:
 
The LevelX scene holds the game loop and controls the flow of each levels.
 
The LevelX scene holds the game loop and controls the flow of each levels.
  
When the scene loads, it constructs the level which is organised into layers, more on that in the code comments below ;-)
+
When the scene loads, it constructs the level into layers, more on that in the code comments below ;-)
  
 
The code is not that long given it takes care of all levels of the game: level 1, 2 and 3. To make it easy to follow and debug, I use '''FIGlet''' (https://en.wikipedia.org/wiki/FIGlet).
 
The code is not that long given it takes care of all levels of the game: level 1, 2 and 3. To make it easy to follow and debug, I use '''FIGlet''' (https://en.wikipedia.org/wiki/FIGlet).
Line 241: Line 241:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
=== LevelX:init() ===
+
== LevelX Code comments ==
 +
=== LevelX:init ===
 
==== plugins ====
 
==== plugins ====
 
Here we initialize our plugins. tiny-ecs is declared as a Class variable (''self.tiny'') because we use it outside the ''init'' function. Bump can be local as we only use it in the ''init'' function.
 
Here we initialize our plugins. tiny-ecs is declared as a Class variable (''self.tiny'') because we use it outside the ''init'' function. Bump can be local as we only use it in the ''init'' function.
  
It is worth noting that ''tiny.tworld'' is attached to ''self.tiny'' variable. This makes it much easier to access through the rest of the code in our project.
+
It is worth noting that ''tiny.tworld'' is attached to ''self.tiny'' variable. This makes it easier to access through the rest of the code in our project.
  
 
==== layers ====
 
==== layers ====
Line 257: Line 258:
 
We use '''Tiled''' to build our levels (https://www.mapeditor.org/) and we call the ''TiledLevels Class'' to manage the parsing of the Tiled elements in our game.
 
We use '''Tiled''' to build our levels (https://www.mapeditor.org/) and we call the ''TiledLevels Class'' to manage the parsing of the Tiled elements in our game.
  
  '''We dedicate an entire folder for Tiled in our project and we will explain more about it down below'''
+
  '''We dedicate an entire folder for Tiled in our project and we talk more about it down below'''
  
 
The ''mapdef'' is a table which has the map definition (dimensions), so we can pass it to functions that will require the size of the map for calculations.
 
The ''mapdef'' is a table which has the map definition (dimensions), so we can pass it to functions that will require the size of the map for calculations.
Line 272: Line 273:
 
We use a camera in our game and ''camfollowoffsety'' will offset the camera following the player. It is easier to change it being a variable.
 
We use a camera in our game and ''camfollowoffsety'' will offset the camera following the player. It is easier to change it being a variable.
  
We use a slightly modified version of '''MultiPain''''s (aka rrraptor on Gideros forum) '''GCam''' Class for our camera.
+
We use a slightly modified version of '''MultiPain''''s (aka rrraptor on Gideros forum) '''GCam''' Class for our camera. We pass it the ''mainlayer'' as the content and the player1 to follow.
 
 
Please grab it here '''[[Media:gcam_beu.lua]]''' '''(tip: right click and save link as)''' and put it in the "'''classes'''" folder.
 
 
 
We pass the ''mainlayer'' as the content and the player1 to follow.
 
  
 
Using the map definition we set the camera bounds. We also set the soft and the dead size parameters and we tell it to follow the player.
 
Using the map definition we set the camera bounds. We also set the soft and the dead size parameters and we tell it to follow the player.
Line 305: Line 302:
  
 
== TiledLevels Class ==
 
== TiledLevels Class ==
 +
To better organise the game, I put all files related to '''Tiled''' in a '''tiled''' folder.
 +
 
Let's have a look at how we construct our levels.
 
Let's have a look at how we construct our levels.
  
As mentionned before I use '''Tiled'''. In Tiled we add many layers, in our case '''Object Layers'''. Each layers have a specific name to better build our levels (eg. ''physics_ptpfs'', ''physics_coins'', ''bg_deco_images'', ...). In each layers we draw the level using shapes (rectangles, ellipses, ...). Shapes are also used to position the actors in the level, like the player, the enemies, moving platforms, ...
+
In Tiled we add many layers, in our case '''Object Layers'''. Each layers have a type and a name (eg. ''physics_ptpfs'', ''physics_coins'', ''bg_deco_images'', ...). In each layers we draw the level using shapes (rectangles, ellipses, ...). Shapes are also used to position the actors in the level, like the player, the enemies, moving platforms, ...
 +
 
 +
Once the level is done in Tiled, we export it as a '''lua''' file Gideros will use (read the layers name, the shapes, the color, the position, ...).
 +
 
 +
=== TiledLevels Class Code comments ===
 +
The very first thing we do is include the Tiled lua file. I also set up some variables to check if we are testing a prototype level and if we are in development mode.
 +
 
 +
==== Tileset ====
 +
This is the classic: a tileset with a tilemap image ('''Tile Layer''' in Tiled). We don't use tilsets in this game but feel free to do so. The code extracts the tilemap info: number of columns, rows, tile id (gid) and texture.
 +
 
 +
==== Tileset images ====
 +
Instead of tilesets we use single images to make our levels. Here we read the gid and the image path and store them in tables. The tables are then turned into '''[[TexturePack]]''' so the game runs faster.
 +
 
 +
==== Build Level ====
 +
To build a level, we iterate all the tilemap layers according to their '''type''' and their '''name'''. We either build shapes the '''physics world''' can use or we add images to decorate the level using the ''parseImage'' function.
 +
 
 +
'''note''': we don't use Tiled ''Tile Layer'' in this game but you can, Build Level takes care of it for you in ''layer.type == "tilelayer"''
 +
 
 +
And this is the code that reads the Tiled lua file to build our levels:
 +
<syntaxhighlight lang="lua">
 +
TiledLevels = Core.class(Sprite)
 +
 
 +
function TiledLevels:init(tmpath, xtiny, xbworld, xlayers)
 +
local tm = require(tmpath) -- eg.: "tiled/test" without ".lua" extension + exclude from execution!
 +
local proto = false
 +
if tmpath:find("proto") then
 +
proto = true
 +
end
 +
-- some functions
 +
local function rgb2hex(r, g, b)
 +
return (r << 16) + (g << 8) + b
 +
end
 +
local dev = false -- true, false
 +
local tsimgpath -- "tiled/", root path to tileset tilemap images
 +
if dev then -- in development
 +
tsimgpath = -- path to external folder
 +
"C:/X_MOKA/PROJECTS/_gideros/WIKI/_assets/tuto_pf_cbump_tecs/"
 +
else -- prod
 +
if g_currlevel == 1 then tsimgpath = "tiled/lvl001/"
 +
elseif g_currlevel == 2 then tsimgpath = "tiled/lvl001/"
 +
elseif g_currlevel == 3 then tsimgpath = "tiled/lvl002/"
 +
end
 +
end
 +
-- _______ _____ _      ______  _____ ______ _______
 +
--|__  __|_  _| |    |  ____|/ ____|  ____|__  __|
 +
--  | |    | | | |    | |__  | (___ | |__    | | 
 +
--  | |    | | | |    |  __|  \___ \|  __|    | | 
 +
--  | |  _| |_| |____| |____ ____) | |____  | | 
 +
--  |_|  |_____|______|______|_____/|______|  |_| 
 +
-- this is the classic: a tileset with a tilemap image
 +
for i = 1, #tm.tilesets do -- important
 +
local tileset = tm.tilesets[i]
 +
-- add extra values (variables) to a tm.tilesets[i] table
 +
if tileset.image then -- only tileset tilemap layers
 +
tileset.numcols = math.floor(
 +
(tileset.imagewidth-tileset.margin+tileset.spacing)/
 +
(tileset.tilewidth+tileset.spacing)
 +
)
 +
tileset.numrows = math.floor(
 +
(tileset.imageheight-tileset.margin+tileset.spacing)/
 +
(tileset.tileheight+tileset.spacing)
 +
)
 +
tileset.lastgid = tileset.firstgid+(tileset.numcols*tileset.numrows)-1
 +
tileset.texture = Texture.new(
 +
tsimgpath..tileset.image, false,
 +
{ transparentColor=tonumber(tileset.transparentcolor), }
 +
)
 +
end
 +
end
 +
-- tileset function
 +
local function gid2tileset(tm, gid)
 +
for i = 1, #tm.tilesets do
 +
local tileset = tm.tilesets[i]
 +
if tileset.image then -- only valid tileset layers
 +
if tileset.firstgid <= gid and gid <= tileset.lastgid then
 +
return tileset
 +
end
 +
end
 +
end
 +
end
 +
-- _______ _____ _      ______  _____ ______ _______
 +
--|__  __|_  _| |    |  ____|/ ____|  ____|__  __|
 +
--  | |    | | | |    | |__  | (___ | |__    | | 
 +
--  | |    | | | |    |  __|  \___ \|  __|    | | 
 +
--  | |  _| |_| |____| |____ ____) | |____  | | 
 +
--  |_|  |_____|______|______|_____/|______|  |_| 
 +
-- _____ __  __          _____ ______  _____
 +
--|_  _|  \/  |  /\  / ____|  ____|/ ____|
 +
--  | | | \  / |  /  \ | |  __| |__  | (___ 
 +
--  | | | |\/| | / /\ \| | |_ |  __|  \___ \
 +
-- _| |_| |  | |/ ____ \ |__| | |____ ____) |
 +
--|_____|_|  |_/_/    \_\_____|______|_____/
 +
-- this one parses individual images stored in a tileset
 +
local tilesetimages = {} -- table holding all the tileset images info (path, width, height)
 +
for i = 1, #tm.tilesets do
 +
local tileset = tm.tilesets[i]
 +
if not tileset.image then -- filter out tileset tilemap layers, only tileset images
 +
local tiles = tileset.tiles
 +
for j = 1, #tiles do
 +
-- populate the tilesetimages table based on the tile gid and id
 +
-- note: you may have to adjust the path to point to the image folder
 +
tilesetimages[tileset.firstgid + tiles[j].id] = {
 +
path=tsimgpath..tiles[j].image,
 +
width=tiles[j].width,
 +
height=tiles[j].height,
 +
}
 +
end
 +
end
 +
end
 +
-- pack all the deco images (TexturePack), perfs? XXX
 +
local bgtpt = {} -- background texture pack table
 +
local bgtptx = {} -- background texture pack table x
 +
local fgtpt = {} -- foreground texture pack table
 +
local fgtptx = {} -- foreground texture pack table x
 +
for i = 1, #tm.layers do
 +
local layer = tm.layers[i]
 +
if layer.name:match("bg_deco_images") then
 +
if layer.name:match("x$") then -- x$ = string ends with x
 +
for i = 1, #layer.objects do
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
-- print("bg", tilesetimages[layer.objects[i].gid].path)
 +
bgtptx[#bgtptx+1] = tilesetimages[layer.objects[i].gid].path
 +
end
 +
end
 +
else
 +
for i = 1, #layer.objects do
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
-- print("bg", tilesetimages[layer.objects[i].gid].path)
 +
bgtpt[#bgtpt+1] = tilesetimages[layer.objects[i].gid].path
 +
end
 +
end
 +
end
 +
elseif layer.name:match("fg_deco_images") then
 +
if layer.name:match("x$") then -- x$ = string ends with x
 +
for i = 1, #layer.objects do
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
-- print("fg", tilesetimages[layer.objects[i].gid].path)
 +
fgtptx[#fgtptx+1] = tilesetimages[layer.objects[i].gid].path
 +
end
 +
end
 +
else
 +
for i = 1, #layer.objects do
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
-- print("fg", tilesetimages[layer.objects[i].gid].path)
 +
fgtpt[#fgtpt+1] = tilesetimages[layer.objects[i].gid].path
 +
end
 +
end
 +
end
 +
end
 +
end
 +
local bgtp = TexturePack.new(bgtpt, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
 +
local bgtpx = TexturePack.new(bgtptx, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
 +
local fgtp = TexturePack.new(fgtpt, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
 +
local fgtpx = TexturePack.new(fgtptx, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
 +
if #bgtp:getRegionsNames() > 0 then print("bgtp", bgtp:getSize()) end
 +
if #bgtpx:getRegionsNames() > 0 then print("bgtpx", bgtpx:getSize()) end
 +
if #fgtp:getRegionsNames() > 0 then print("fgtp", fgtp:getSize()) end
 +
if #fgtpx:getRegionsNames() > 0 then print("fgtpx", fgtpx:getSize()) end
 +
-- ____  _    _ _____ _      _____    _      ________      ________ _     
 +
--|  _ \| |  | |_  _| |    |  __ \  | |    |  ____\ \    / /  ____| |   
 +
--| |_) | |  | | | | | |    | |  | | | |    | |__  \ \  / /| |__  | |   
 +
--|  _ <| |  | | | | | |    | |  | | | |    |  __|  \ \/ / |  __| | |   
 +
--| |_) | |__| |_| |_| |____| |__| | | |____| |____  \  /  | |____| |____
 +
--|____/ \____/|_____|______|_____/  |______|______|  \/  |______|______|
 +
-- tileset images function
 +
local function parseImage(xobject, xlayer, xtplayer, xtexpack)
 +
local bitmap
 +
if xtplayer == "bg" then
 +
if xtexpack == "x" then
 +
bitmap = Bitmap.new(bgtpx:getTextureRegion(tilesetimages[xobject.gid].path))
 +
else
 +
bitmap = Bitmap.new(bgtp:getTextureRegion(tilesetimages[xobject.gid].path))
 +
end
 +
else -- fg
 +
if xtexpack == "x" then
 +
bitmap = Bitmap.new(fgtpx:getTextureRegion(tilesetimages[xobject.gid].path))
 +
else
 +
bitmap = Bitmap.new(fgtp:getTextureRegion(tilesetimages[xobject.gid].path))
 +
end
 +
end
 +
bitmap:setAnchorPoint(0, 1) -- because I always forget to modify Tiled objects alignment
 +
-- supports Tiled image scaling
 +
local scalex, scaley = xobject.width/bitmap:getWidth(), xobject.height/bitmap:getHeight()
 +
bitmap:setScale(scalex, scaley)
 +
bitmap:setRotation(xobject.rotation)
 +
bitmap:setPosition(xobject.x, xobject.y)
 +
xlayer:addChild(bitmap)
 +
end
 +
for i = 1, #tm.layers do
 +
local layer = tm.layers[i]
 +
local tilemaps = {}
 +
local group -- group = Sprite.new()
 +
-- _______ _____ _      ______ _          __    ________ _____ 
 +
--|__  __|_  _| |    |  ____| |        /\\ \  / /  ____|  __ \
 +
--  | |    | | | |    | |__  | |      /  \\ \_/ /| |__  | |__) |
 +
--  | |    | | | |    |  __| | |      / /\ \\  / |  __| |  _  /
 +
--  | |  _| |_| |____| |____| |____ / ____ \| |  | |____| | \ \
 +
--  |_|  |_____|______|______|______/_/    \_\_|  |______|_|  \_\
 +
if layer.type == "tilelayer" and (layer.name:match("bg") or layer.name:match("fg")) then
 +
if layer.name:match("bg") then group = xlayers["bg"]
 +
else group = xlayers["fg"]
 +
end
 +
for y = 1, layer.height do
 +
for x = 1, layer.width do
 +
local index = x + (y - 1) * layer.width
 +
local gid = layer.data[index]
 +
local gidtileset = gid2tileset(tm, gid)
 +
if gidtileset then
 +
local tilemap
 +
if tilemaps[gidtileset] then
 +
tilemap = tilemaps[gidtileset]
 +
else
 +
tilemap = TileMap.new(
 +
layer.width, layer.height,
 +
gidtileset.texture, gidtileset.tilewidth, gidtileset.tileheight,
 +
gidtileset.spacing, gidtileset.spacing,
 +
gidtileset.margin, gidtileset.margin,
 +
tm.tilewidth, tm.tileheight
 +
)
 +
tilemaps[gidtileset] = tilemap
 +
group:addChild(tilemap)
 +
end
 +
local tx = (gid - gidtileset.firstgid) % gidtileset.numcols + 1
 +
local ty = math.floor((gid - gidtileset.firstgid) / gidtileset.numcols) + 1
 +
-- set the tile with flip info
 +
tilemap:setTile(x, y, tx, ty)
 +
end
 +
end
 +
end
 +
group:setAlpha(layer.opacity)
 +
--  ____  ____      _ ______ _____ _______ _          __    ________ _____ 
 +
-- / __ \|  _ \    | |  ____/ ____|__  __| |        /\\ \  / /  ____|  __ \
 +
--| |  | | |_) |    | | |__ | |      | |  | |      /  \\ \_/ /| |__  | |__) |
 +
--| |  | |  _ < _  | |  __|| |      | |  | |      / /\ \\  / |  __| |  _  /
 +
--| |__| | |_) | |__| | |___| |____  | |  | |____ / ____ \| |  | |____| | \ \
 +
-- \____/|____/ \____/|______\_____|  |_|  |______/_/    \_\_|  |______|_|  \_\
 +
elseif layer.type == "objectgroup" then
 +
local o
 +
local myshape, mytable
 +
--                            _      __ _      _ _  _           
 +
--                            | |    / _(_)    (_) | (_)           
 +
-- _ __ ___  __ _ _ __    __| | ___| |_ _ _ __  _| |_ _  ___  _ __ 
 +
--| '_ ` _ \ / _` | '_ \  / _` |/ _ \  _| | '_ \| | __| |/ _ \| '_ \
 +
--| | | | | | (_| | |_) | | (_| |  __/ | | | | | | | |_| | (_) | | | |
 +
--|_| |_| |_|\__,_| .__/  \__,_|\___|_| |_|_| |_|_|\__|_|\___/|_| |_|
 +
--                | |                                               
 +
--                |_|                                               
 +
if layer.name == "physics_map_def" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
self.mapdef = {}
 +
self.mapdef.t = o.y
 +
self.mapdef.l = o.x
 +
self.mapdef.r = o.width
 +
self.mapdef.b = o.height
 +
end
 +
--    _                  _                         
 +
--    | |                | |                         
 +
--  __| | ___  ___ ___  | | __ _ _  _  ___ _ __ ___
 +
-- / _` |/ _ \/ __/ _ \  | |/ _` | | | |/ _ \ '__/ __|
 +
--| (_| |  __/ (_| (_) | | | (_| | |_| |  __/ |  \__ \
 +
-- \__,_|\___|\___\___/  |_|\__,_|\__, |\___|_|  |___/
 +
--                                __/ |             
 +
--                                |___/             
 +
elseif layer.name == "bg_deco_texts" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
mytable = {
 +
color=rgb2hex(table.unpack(o.color or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
elseif layer.name:match("bg_deco_shapes") then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local texpath
 +
local color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )) -- math.random(0xffffff)
 +
local r, g, b
 +
if o.name == "hint" then
 +
color = 0xffffff
 +
elseif o.name == "lava" then
 +
texpath = "gfx/textures/Water_01_Grey_1.png"
 +
color = nil
 +
r, g, b = 124, 205, 70
 +
elseif o.name == "spikes" then
 +
color = 0xff5500
 +
end
 +
mytable = {
 +
texpath=texpath, istexpot=true, scalex=0.4,
 +
color=color, r=r, g=g, b=b,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
elseif layer.name:match("bg_deco_images") then
 +
--parseImage(xobject, xlayer, xtplayer, xtexpack)
 +
for i = 1, #layer.objects do
 +
if layer.name:match("x$") then
 +
parseImage(layer.objects[i], xlayers["bg"], "bg", "x")
 +
else
 +
parseImage(layer.objects[i], xlayers["bg"], "bg", nil)
 +
end
 +
end
 +
elseif layer.name == "fg_deco_texts" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
mytable = {
 +
color=rgb2hex(table.unpack(o.color or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["fg"]:addChild(myshape)
 +
end
 +
elseif layer.name:match("fg_deco_shapes") then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["fg"]:addChild(myshape)
 +
end
 +
elseif layer.name:match("fg_deco_images") then
 +
for i = 1, #layer.objects do
 +
if layer.name:match("x$") then
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
parseImage(layer.objects[i], xlayers["fg"], "fg", "x")
 +
end
 +
else
 +
if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
 +
parseImage(layer.objects[i], xlayers["fg"], "fg", nil)
 +
end
 +
end
 +
end
 +
--      _              _            _                         
 +
--      | |            (_)          | |                         
 +
-- _ __ | |__  _  _ ___ _  ___ ___  | | __ _ _  _  ___ _ __ ___
 +
--| '_ \| '_ \| | | / __| |/ __/ __| | |/ _` | | | |/ _ \ '__/ __|
 +
--| |_) | | | | |_| \__ \ | (__\__ \ | | (_| | |_| |  __/ |  \__ \
 +
--| .__/|_| |_|\__, |___/_|\___|___/ |_|\__,_|\__, |\___|_|  |___/
 +
--| |          __/ |                          __/ |             
 +
--|_|          |___/                          |___/             
 +
--                    _    _
 +
--                  | |  | |
 +
--__      _____  _ __| | __| |
 +
--\ \ /\ / / _ \| '__| |/ _` |
 +
-- \ V  V / (_) | |  | | (_| |
 +
--  \_/\_/ \___/|_|  |_|\__,_|
 +
elseif layer.name == "physics_exits" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isexit = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_voids" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isvoid = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_spikes" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isspike = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_springs" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local vx, vy
 +
local index
 +
index = o.name:find("vx") -- index vx
 +
vx = tonumber(o.name:sub(index):match("%d+")) -- find vxNNN
 +
index = o.name:find("vy") -- index vy
 +
vy = tonumber(o.name:sub(index):match("%d+")) -- find vyNNN
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isspring = true
 +
o.vx = vx*64 -- some multiplier, magik XXX
 +
o.vy = vy*64 -- some multiplier, magik XXX
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_sensors" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local xid = o.name -- examples: "mvgrAA", ...
 +
local opos = vector(o.x, o.y)
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
alpha=0.25,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
--ESensor:init(xid, xspritelayer, xpos, w, h, xlayers["bgfx"])
 +
local e = ESensor.new(
 +
xid, xlayers["actors"], opos, o.width, o.height, xlayers["bgfx"]
 +
)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
-- some cleaning?
 +
e = nil
 +
end
 +
elseif layer.name:match("physics_doors") then -- doors = doorAAdx0dy8vel10dirU
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local xpos = vector(o.x, o.y)
 +
local dx, dy, vel, xdir
 +
local index
 +
index = o.name:find("dx") -- index dx
 +
local xid = o.name:sub(1, index-1) -- examples: "doorAA", "doorBB", ...
 +
dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
 +
index = o.name:find("dy") -- index dy
 +
dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
 +
index = o.name:find("vel") -- index vel
 +
vel = tonumber(o.name:sub(index):match("%d+")) -- find velNN
 +
index = o.name:find("dir") -- index dir
 +
xdir = o.name:sub(index+3) -- initial heading direction, examples: "U", "DR", "UL", ...
 +
local angle = math.atan2(dy, dx)
 +
local xspeed = vector(vel*math.cos(angle), vel*math.sin(angle))*vector(8, 8)
 +
local xtexpath
 +
local xcolor
 +
if proto then
 +
xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
xtexpath = "gfx/textures/Concrete_01_Grey_1.png"
 +
xcolor = 0xa76f53 -- plus a tint color
 +
end
 +
--EDoor:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
 +
local e = EDoor.new(
 +
xid, xlayers["actors"], xpos, xtexpath, xcolor, o.width, o.height,
 +
dx, dy, xdir, xspeed, xlayers["bgfx"]
 +
)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
 +
-- some cleaning?
 +
e = nil
 +
end
 +
elseif layer.name:match("physics_ptmvpfs") then -- passthrough moving platform
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local vpos = vector(o.x, o.y)
 +
local dx, dy, vel, xdir
 +
local index
 +
index = o.name:find("dx") -- index dx
 +
local xid = o.name:sub(1, index-1) -- examples: "idAA", "idBB", ...
 +
dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
 +
index = o.name:find("dy") -- index dy
 +
dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
 +
index = o.name:find("vel") -- index vel
 +
vel = tonumber(o.name:sub(index):match("%d+"))*8 -- find velNNN
 +
index = o.name:find("dir") -- index dir
 +
xdir = o.name:sub(index+3) -- initial heading direction, examples: "U", "DR", "UL", ...
 +
local angle = math.atan2(dy, dx)
 +
local vspeed = vector(vel*math.cos(angle), vel*math.sin(angle))
 +
local xtexpath
 +
local xcolor
 +
if proto then
 +
xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
xtexpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
 +
xcolor = 0xffaa7f -- plus a tint color
 +
end
 +
--EMvpf:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
 +
local e = EMvpf.new(
 +
xid, xlayers["actors"], vpos, xtexpath, xcolor, o.width, o.height,
 +
dx, dy, xdir, vspeed, true, xlayers["bgfx"]
 +
)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
 +
-- some cleaning?
 +
e = nil
 +
end
 +
elseif layer.name:match("physics_mvpfs") then -- non passthrough moving platform
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local vpos = vector(o.x, o.y)
 +
local dx, dy, vel, xdir
 +
local index
 +
index = o.name:find("dx") -- index dx
 +
local xid = o.name:sub(1, index-1) -- examples: "idAA", "idBB", ...
 +
dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
 +
index = o.name:find("dy") -- index dy
 +
dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
 +
index = o.name:find("vel") -- index vel
 +
vel = tonumber(o.name:sub(index):match("%d+"))*8 -- find velNNN
 +
index = o.name:find("dir") -- index dir
 +
xdir = o.name:sub(index+3) -- initial heading direction, examples: "N", "SE", "NW", ...
 +
local angle = math.atan2(dy, dx)
 +
local vspeed = vector(vel*math.cos(angle), vel*math.sin(angle))
 +
local xtexpath
 +
local xcolor
 +
if proto then
 +
xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
xtexpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
 +
xcolor = 0xffaa7f -- plus a tint color
 +
end
 +
--EMvpf:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
 +
local e = EMvpf.new(
 +
xid, xlayers["actors"], vpos, xtexpath, xcolor, o.width, o.height,
 +
dx, dy, xdir, vspeed, nil, xlayers["bgfx"]
 +
)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
 +
-- some cleaning?
 +
e = nil
 +
end
 +
elseif layer.name == "physics_ladders" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
alpha=0.5,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isladder = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_walls" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local texpath
 +
local color
 +
local r, g, b
 +
if proto then
 +
color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
texpath = "gfx/textures/Rock_Grey_02.png"
 +
r, g, b =139, 56, 28
 +
end
 +
mytable = {
 +
texpath=texpath, istexpot=true, scalex=0.5,
 +
color=color,
 +
r=r, g=g, b=b,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
o.iswall = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_steps" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
if proto then
 +
mytable = {
 +
color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
end
 +
o.isstep = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_ptpfs" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
o.pos = vector(o.x, o.y)
 +
local texpath
 +
local color
 +
local r, g, b
 +
if proto then
 +
color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
if o.name == "t" then
 +
-- use "t" as the object name to make it "transparent"
 +
else
 +
texpath = "gfx/textures/1K-brick_wall_17_baseColor.jpg_0001.png"
 +
r, g, b = 139, 56, 28 -- plus a tint color
 +
end
 +
end
 +
mytable = {
 +
texpath=texpath, istexpot=true, scalex=0.5,
 +
color=color,
 +
r=r, g=g, b=b,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
o.isptpf = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
elseif layer.name == "physics_grounds" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local texpath
 +
local color
 +
local r, g, b
 +
if proto then
 +
color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
 +
else
 +
if o.name == "t" then -- transparent
 +
-- use "t" as the object name to make it "transparent"
 +
else
 +
texpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
 +
r, g, b = 255, 192, 160 -- plus a tint color
 +
end
 +
end
 +
mytable = {
 +
texpath=texpath, istexpot=true, scalex=1, skewx=3,
 +
color=color,
 +
r=r, g=g, b=b,
 +
}
 +
myshape = self:buildShapes(o, mytable)
 +
myshape:setPosition(o.x, o.y)
 +
xlayers["bg"]:addChild(myshape)
 +
o.isfloor = true
 +
xbworld:add(o, o.x, o.y, o.width, o.height)
 +
end
 +
--          _ _          _  _ _    _         
 +
--          | | |        | | (_) |  | |         
 +
--  ___ ___ | | | ___  ___| |_ _| |__ | | ___  ___
 +
-- / __/ _ \| | |/ _ \/ __| __| | '_ \| |/ _ \/ __|
 +
--| (_| (_) | | |  __/ (__| |_| | |_) | |  __/\__ \
 +
-- \___\___/|_|_|\___|\___|\__|_|_.__/|_|\___||___/
 +
elseif layer.name == "physics_keys" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local xid = o.name -- example "doorAA", "mvpfBB", ...
 +
xid = "key"..tostring(xid) -- we append the keyword key to tell it is a key XXX
 +
local opos = vector(o.x, o.y)
 +
--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
 +
local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
elseif layer.name == "physics_coins" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
local xid = layer.name:gsub("physics_", "")
 +
--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
 +
local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 4*8, 4*8)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
elseif layer.name == "physics_lives" then
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
local xid = layer.name:gsub("physics_", "")
 +
--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
 +
local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
--            _               
 +
--          | |               
 +
--  __ _  ___| |_ ___  _ __ ___
 +
-- / _` |/ __| __/ _ \| '__/ __|
 +
--| (_| | (__| || (_) | |  \__ \
 +
-- \__,_|\___|\__\___/|_|  |___/
 +
elseif layer.name == "physics_ground_nmes03" then -- nmes
 +
-- 300: no move, no jump, shoot all angles
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
local collectible = o.name
 +
if collectible == "" then collectible = "coins" -- default to coins
 +
elseif collectible == "x" then collectible = nil
 +
end
 +
--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
 +
local e = EGroundNmes.new(300, xlayers["actors"], opos, xlayers["bgfx"], collectible)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
elseif layer.name == "physics_ground_nmes02" then -- nmes
 +
-- 200: move, jump, no shoot
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
local collectible = o.name
 +
if collectible == "" then collectible = "coins" -- default to coins
 +
elseif collectible == "x" then collectible = nil
 +
end
 +
--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
 +
local e = EGroundNmes.new(200, xlayers["actors"], opos, xlayers["bgfx"], collectible)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
elseif layer.name == "physics_ground_nmes" then
 +
-- 100: no move, no jump, no shoot
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
local collectible = o.name
 +
if collectible == "" then collectible = nil -- default to coins
 +
elseif collectible == "x" then collectible = nil
 +
end
 +
--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
 +
local e = EGroundNmes.new(100, xlayers["actors"], opos, xlayers["bgfx"], collectible)
 +
xtiny.tworld:addEntity(e)
 +
xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
 +
end
 +
elseif layer.name == "physics_players" then -- player1
 +
for i = 1, #layer.objects do
 +
o = layer.objects[i]
 +
local opos = vector(o.x, o.y)
 +
-- EPlayer1:init(xspritelayer, xpos, xbgfxlayer)
 +
self.player1 = EPlayer1.new(xlayers["actors"], opos, xlayers["bgfx"])
 +
xtiny.tworld:addEntity(self.player1)
 +
xbworld:add(
 +
self.player1,
 +
self.player1.pos.x, self.player1.pos.y,
 +
self.player1.collbox.w, self.player1.collbox.h
 +
)
 +
end
 +
end
 +
-- some cleaning?
 +
o = nil
 +
myshape, mytable = nil, nil
 +
end
 +
end
 +
end
  
In the TiledLevels Class we read the .lua file exported from Tiled and we use it to build the shapes in the game using Gideros '''[[Shape]]'''. In order to build the shapes, I have written several classes for each one of them: ''Tiled_Shape_Ellipse'', ''Tiled_Shape_Point'', ''Tiled_Shape_Polygon'', ...
+
function TiledLevels:buildShapes(xobject, xlevelsetup)
 +
local myshape -- Tiled shapes: ellipse, point, polygon, polyline, rectangle, text
 +
local tablebase = {}
 +
if xobject.shape == "ellipse" then
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
w=xobject.width, h=xobject.height,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Ellipse.new(tablebase)
 +
elseif xobject.shape == "point" then
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Point.new(tablebase)
 +
elseif xobject.shape == "polygon" then
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
coords=xobject.polygon,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Polygon.new(tablebase)
 +
elseif xobject.shape == "polyline" then -- lines
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
coords=xobject.polyline,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Polyline.new(tablebase)
 +
elseif xobject.shape == "rectangle" then
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
w=xobject.width, h=xobject.height,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Rectangle.new(tablebase)
 +
elseif xobject.shape == "text" then
 +
tablebase = {
 +
x=xobject.x, y=xobject.y,
 +
text=xobject.text,
 +
w=xobject.width, h=xobject.height,
 +
rotation=xobject.rotation,
 +
}
 +
for k, v in pairs(xlevelsetup) do tablebase[k] = v end
 +
myshape = Tiled_Shape_Text.new(tablebase)
 +
else
 +
print("*** CANNOT PROCESS THIS SHAPE! ***", xobject.shape, xobject.name)
 +
return
 +
end
  
When we read the Tiled file we call the appropriate shape class and voilà, we see the level :-)
+
return myshape
 +
end
 +
</syntaxhighlight>
  
'''note''': we give each shape a color and a texture when reading the Tiled file in the TiledLevels Class
+
To build the shapes in the game we use the Gideros '''[[Shape]]''' class. I have written several classes for each one of them: ''Tiled_Shape_Ellipse'', ''Tiled_Shape_Point'', ''Tiled_Shape_Polygon'', ... . When we read the Tiled file we look for the layer name, if the layer contains shapes, we call the appropriate shape class and voilà :-). The ''buildShapes'' function is at the very bottom of the class.
  
To better organise the game, I put all files related to Tiled in a '''tiled''' folder.
+
Please add the Tiled Deco Shapes classes to your project: '''[[Tiled Deco Shapes]]'''
  
You can read more about Tiled and Gideros here: [[Tiled_Bump]].
+
That's basically how we build our levels using Tiled. You can read more about Tiled and Gideros here: [[Tiled_Bump]].
  
 
== Next? ==
 
== Next? ==

Latest revision as of 01:43, 26 October 2025

the levelX.lua file

This is where all the fun begins!

The LevelX scene holds the game loop and controls the flow of each levels.

When the scene loads, it constructs the level into layers, more on that in the code comments below ;-)

The code is not that long given it takes care of all levels of the game: level 1, 2 and 3. To make it easy to follow and debug, I use FIGlet (https://en.wikipedia.org/wiki/FIGlet).

I use this one https://sourceforge.net/projects/figletgenerator/

The code:

LevelX = Core.class(Sprite)

local ispaused = false

function LevelX:init()
	-- move the cursor out of the way
	if not application:isPlayerMode() then
		local sw, sh = application:get("screenSize") -- the user's screen size!
		application:set("cursorPosition", sw-10, sh-10) -- 0, 0
	end
	-- _____  _     _    _  _____ _____ _   _  _____ 
	--|  __ \| |   | |  | |/ ____|_   _| \ | |/ ____|
	--| |__) | |   | |  | | |  __  | | |  \| | (___  
	--|  ___/| |   | |  | | | |_ | | | | . ` |\___ \ 
	--| |    | |___| |__| | |__| |_| |_| |\  |____) |
	--|_|    |______\____/ \_____|_____|_| \_|_____/ 
	-- tiny-ecs
	if not self.tiny then self.tiny = require "classes/tiny-ecs" end
	self.tiny.tworld = self.tiny.world()
	-- cbump (bworld)
	local bump = require "cbump"
	local bworld = bump.newWorld() -- 16, grid cell size, default = 64
	-- _           __     ________ _____   _____ 
	--| |        /\\ \   / /  ____|  __ \ / ____|
	--| |       /  \\ \_/ /| |__  | |__) | (___  
	--| |      / /\ \\   / |  __| |  _  / \___ \ 
	--| |____ / ____ \| |  | |____| | \ \ ____) |
	--|______/_/    \_\_|  |______|_|  \_\_____/ 
	local layers = {}
	layers["main"] = Sprite.new() -- one Sprite to hold them all
	layers["bg"] = Sprite.new() -- bg layer
	layers["bgfx"] = Sprite.new() -- bg fx layer
	layers["actors"] = Sprite.new() -- actors layer
	layers["fgfx"] = Sprite.new() -- fg fx layer
	layers["fg"] = Sprite.new() -- fg layer
	layers["player1input"] = Sprite.new() -- player1 input layer
	-- _      ________      ________ _       _____ 
	--| |    |  ____\ \    / /  ____| |     / ____|
	--| |    | |__   \ \  / /| |__  | |    | (___  
	--| |    |  __|   \ \/ / |  __| | |     \___ \ 
	--| |____| |____   \  /  | |____| |____ ____) |
	--|______|______|   \/   |______|______|_____/ 
	self.tiledlevels = {}
	self.tiledlevels[1] = "tiled/lvl001/_level1_proto" -- lua file without extension
	self.tiledlevels[2] = "tiled/lvl001/level1" -- lua file without extension
--	self.tiledlevels[3] = "tiled/lvl002/level1_proto" -- lua file without extension
	local mapdef = {} -- game area (rect: top, left, right, bottom)
	local zoom = 1 -- 1.3, 1.5, 1.7
	local camfollowoffsety = 28 -- 32, 36, 48, camera follow player1 y offset, magik XXX
	self.tiny.player1inventory = {} -- player1 inventory
	self.tiny.numberofcoins = 0
	local currlevel = TiledLevels.new(
		self.tiledlevels[g_currlevel], self.tiny, bworld, layers
	)
	-- currlevel map definition (top, left, right, bottom) for the camera
	mapdef.t, mapdef.l =
		currlevel.mapdef.t, currlevel.mapdef.l
	mapdef.r, mapdef.b =
		currlevel.mapdef.l+currlevel.mapdef.r, currlevel.mapdef.t+currlevel.mapdef.b
	-- _____  _           __     ________ _____  __ 
	--|  __ \| |        /\\ \   / /  ____|  __ \/_ |
	--| |__) | |       /  \\ \_/ /| |__  | |__) || |
	--|  ___/| |      / /\ \\   / |  __| |  _  / | |
	--| |    | |____ / ____ \| |  | |____| | \ \ | |
	--|_|    |______/_/    \_\_|  |______|_|  \_\|_|
	self.player1 = currlevel.player1
	-- _    _ _    _ _____  
	--| |  | | |  | |  __ \ 
	--| |__| | |  | | |  | |
	--|  __  | |  | | |  | |
	--| |  | | |__| | |__| |
	--|_|  |_|\____/|_____/ 
	-- function
	local function clamp(v, mn, mx) return (v><mx)<>mn end
	local function map(v, minSrc, maxSrc, minDst, maxDst, clampValue)
		local newV = (v-minSrc)/(maxSrc-minSrc)*(maxDst-minDst)+minDst
		return not clampValue and newV or clamp(newV, minDst><maxDst, minDst<>maxDst)
	end
	self.tiny.hud = Sprite.new()
	-- hud lives
	self.tiny.hudlives = {}
	local pixellife
	for i = 1, self.player1.currlives do
		pixellife = Pixel.new(0xffff00, 0.8, 16, 16)
		pixellife:setPosition(8+(i-1)*(16+8), 8)
		self.tiny.hud:addChild(pixellife)
		self.tiny.hudlives[i] = pixellife
	end
	-- hud health
	local hudhealthwidth = map(self.player1.currhealth, 0, self.player1.totalhealth, 0, 100, true)
	self.tiny.hudhealth = Pixel.new(0x00ff00, 2, hudhealthwidth, 8)
	self.tiny.hudhealth:setPosition(8*1, 8*3.5)
	self.tiny.hud:addChild(self.tiny.hudhealth)
	-- hud coins
	self.tiny.hudcoins = TextField.new(cf2, "COINS: "..self.tiny.numberofcoins)
	self.tiny.hudcoins:setTextColor(0xff5500)
	self.tiny.hudcoins:setPosition(8*20, 8*2.3)
	self.tiny.hud:addChild(self.tiny.hudcoins)
	--  _____          __  __ ______ _____            
	-- / ____|   /\   |  \/  |  ____|  __ \     /\    
	--| |       /  \  | \  / | |__  | |__) |   /  \   
	--| |      / /\ \ | |\/| |  __| |  _  /   / /\ \  
	--| |____ / ____ \| |  | | |____| | \ \  / ____ \ 
	-- \_____/_/    \_\_|  |_|______|_|  \_\/_/    \_\
	-- camera: 'content' is a Sprite which holds all your graphics
	self.camera = GCam.new(layers["main"], nil, nil, self.player1) -- (content [, anchorX, anchorY]) -- anchor default 0.5, 0.5
	self.camera:setAutoSize(true)
	self.camera:setZoom(zoom)
	self.camera:setBounds(
		mapdef.l+myappwidth/2/zoom, -- left
		mapdef.t+myappheight/2/zoom, -- top
		mapdef.r-myappwidth/2/zoom, -- right
		mapdef.b-myappheight/2/zoom -- bottom
	)
	self.camera:setSoftSize(1.5*32, 2.2*32) -- 1.5*32, 3*32, w, h
	self.camera:setDeadSize(3*32, 4.4*32) -- 3*32, 5*32, w, h
	self.camera:setFollow(self.player1.sprite)
	self.camera:setFollowOffset(0, camfollowoffsety)
	self.camera:setPredictMode(true)
	self.camera:setPrediction(0.9) -- 0.8, 0.75, 0.5, number betwwen 0 and 1
	self.camera:lockPredictionY() -- no prediction on Y axis
	self.camera:setPredictionSmoothing(4) -- 8, 4, smooth prediction
--	self.camera:setDebug(true) -- uncomment for camera debug mode
	--  ____  _____  _____  ______ _____  
	-- / __ \|  __ \|  __ \|  ____|  __ \ 
	--| |  | | |__) | |  | | |__  | |__) |
	--| |  | |  _  /| |  | |  __| |  _  / 
	--| |__| | | \ \| |__| | |____| | \ \ 
	-- \____/|_|  \_\_____/|______|_|  \_\
	layers["main"]:addChild(layers["bg"])
	layers["main"]:addChild(layers["bgfx"])
	layers["main"]:addChild(layers["actors"])
	layers["main"]:addChild(layers["fgfx"])
	layers["main"]:addChild(layers["fg"])
	self:addChild(self.camera)
	self:addChild(self.tiny.hud)
	self:addChild(layers["player1input"])
	--  _______     _______ _______ ______ __  __  _____ 
	-- / ____\ \   / / ____|__   __|  ____|  \/  |/ ____|
	--| (___  \ \_/ / (___    | |  | |__  | \  / | (___  
	-- \___ \  \   / \___ \   | |  |  __| | |\/| |\___ \ 
	-- ____) |  | |  ____) |  | |  | |____| |  | |____) |
	--|_____/   |_| |_____/   |_|  |______|_|  |_|_____/ 
	self.tiny.tworld:add(
		-- debug
--		SDebugPosition.new(self.tiny),
--		SDebugCollision.new(self.tiny),
--	 	SDebugShield.new(self.tiny),
		-- systems
		SDrawable.new(self.tiny),
		SAnimation.new(self.tiny),
		SPlayer1.new(self.tiny, bworld, self.camera),
		SPlayer1Control.new(self.tiny, self.camera, mapdef, player1inputlayer),
		SNmes.new(self.tiny, bworld, self.player1),
		SAI.new(self.tiny, self.player1),
		SSensor.new(self.tiny, bworld, self.player1),
		SDoor.new(self.tiny, bworld, self.player1),
		SMvpf.new(self.tiny, bworld),
		SCollectibles.new(self.tiny, bworld, self.player1),
		SProjectiles.new(self.tiny, bworld),
		SOscillation.new(self.tiny, bworld, self.player1),
		SCollision.new(self.tiny, bworld, self.player1)
	)
	-- _      ______ _______ _  _____    _____  ____  _ 
	--| |    |  ____|__   __( )/ ____|  / ____|/ __ \| |
	--| |    | |__     | |  |/| (___   | |  __| |  | | |
	--| |    |  __|    | |     \___ \  | | |_ | |  | | |
	--| |____| |____   | |     ____) | | |__| | |__| |_|
	--|______|______|  |_|    |_____/   \_____|\____/(_)
	self:addEventListener(Event.ENTER_FRAME, self.onEnterFrame, self) -- the game loop
	self:myKeysPressed() -- keys handler
end

-- game loop
local timer = 40*8 -- magik XXX
function LevelX:onEnterFrame(e)
	if not ispaused then
		local dt = e.deltaTime
		if self.player1.restart then
			if g_currlevel > #self.tiledlevels then
				timer -= 1
				if self.camera:getZoom() < 2 then
					self.camera:setZoom(self.camera:getZoom()+0.4*dt)
				end
				if timer <= 0 then
					self:gotoScene(Win.new())
					timer = 40*8
				end
			else
				self:gotoScene(LevelX.new())
			end
			self.camera:setPredictionSmoothing(1) -- 4, smooth prediction
			self.camera:update(dt) -- e.deltaTime, 1/60
		else
			self.camera:update(dt) -- e.deltaTime, 1/60
			self.tiny.tworld:update(dt) -- tiny world (last)
		end
	end
end

-- keys handler
function LevelX:myKeysPressed()
	self:addEventListener(Event.KEY_DOWN, function(e) -- KEY_UP
		if e.keyCode == KeyCode.ESC or e.keyCode == KeyCode.BACK then -- MENU
			self:gotoScene(Menu.new())
		elseif e.keyCode == KeyCode.P then -- PAUSE
			ispaused = not ispaused
		elseif e.keyCode == KeyCode.R then -- RESTART
			self.player1.restart = true
		end
		-- modifier
		local modifier = application:getKeyboardModifiers()
		local alt = (modifier & KeyCode.MODIFIER_ALT) > 0
		if (not alt and e.keyCode == KeyCode.ENTER) then -- nothing here!
		elseif alt and e.keyCode == KeyCode.ENTER then -- SWITCH FULLSCREEN
			ismyappfullscreen = not ismyappfullscreen
			application:setFullScreen(ismyappfullscreen)
		end
	end)
end

-- scenes navigation
function LevelX:gotoScene(xscene)
	switchToScene(xscene) -- next scene
end

LevelX Code comments

LevelX:init

plugins

Here we initialize our plugins. tiny-ecs is declared as a Class variable (self.tiny) because we use it outside the init function. Bump can be local as we only use it in the init function.

It is worth noting that tiny.tworld is attached to self.tiny variable. This makes it easier to access through the rest of the code in our project.

layers

This one is easy :-)

We create several layers which will be laid out on top of each other. The background layer will have all the graphics for the background, etc...

There is a player1inputlayer which will capture the user input to control the player, it won't hold any graphics.

Tiled levels

We use Tiled to build our levels (https://www.mapeditor.org/) and we call the TiledLevels Class to manage the parsing of the Tiled elements in our game.

We dedicate an entire folder for Tiled in our project and we talk more about it down below

The mapdef is a table which has the map definition (dimensions), so we can pass it to functions that will require the size of the map for calculations.

player1

The player is an ECS entity we create in the TiledLevels Class, more on that in the next chapters. We need it here because the HUD and the camera depend on it.

hud

I added a simple head up display to the game, so we can see the player current health, number of lives, ...

The same way we attached tiny.tworld to the self.tiny variable, we attach some more variables to it. This makes it easier to access those variables.

the camera

We use a camera in our game and camfollowoffsety will offset the camera following the player. It is easier to change it being a variable.

We use a slightly modified version of MultiPain's (aka rrraptor on Gideros forum) GCam Class for our camera. We pass it the mainlayer as the content and the player1 to follow.

Using the map definition we set the camera bounds. We also set the soft and the dead size parameters and we tell it to follow the player.

order

This is the order of the layers, from bottom to top. We first add the background layer and the other layers on top of it.

systems

Once all entities are done, we add the ECS systems. We will see those ECS systems in the coming parts of the tutorial.

let's go

Finally we are ready to run the game loop!

We also listen to some key events to pause the game, go fullscreen, ...

LevelX:onEnterFrame THE GAME LOOP

Before the game loop, I declare a local variable timer which is the time before transitioning to the next level.

If we beat the level then we go to the next level or the Win scene.

Otherwise, if the game is not paused we update the camera and the tiny-ecs world. tiny-ecs world runs all the systems (animations, movements, AI, ...). Some systems will be called only once, others every frame per second. More on Systems later on in this tutorial.

LevelX:myKeysPressed

Here we simply listen to some KEY_DOWN Events, that is keys pressed on the keyboard. The key ESC will go back to the menu, the letter P will pause the game and when you press ALT+ENTER you switch the game to fullscreen.

LevelX:gotoScene

This is where we transition to the next scene calling the global function switchToScene.

TiledLevels Class

To better organise the game, I put all files related to Tiled in a tiled folder.

Let's have a look at how we construct our levels.

In Tiled we add many layers, in our case Object Layers. Each layers have a type and a name (eg. physics_ptpfs, physics_coins, bg_deco_images, ...). In each layers we draw the level using shapes (rectangles, ellipses, ...). Shapes are also used to position the actors in the level, like the player, the enemies, moving platforms, ...

Once the level is done in Tiled, we export it as a lua file Gideros will use (read the layers name, the shapes, the color, the position, ...).

TiledLevels Class Code comments

The very first thing we do is include the Tiled lua file. I also set up some variables to check if we are testing a prototype level and if we are in development mode.

Tileset

This is the classic: a tileset with a tilemap image (Tile Layer in Tiled). We don't use tilsets in this game but feel free to do so. The code extracts the tilemap info: number of columns, rows, tile id (gid) and texture.

Tileset images

Instead of tilesets we use single images to make our levels. Here we read the gid and the image path and store them in tables. The tables are then turned into TexturePack so the game runs faster.

Build Level

To build a level, we iterate all the tilemap layers according to their type and their name. We either build shapes the physics world can use or we add images to decorate the level using the parseImage function.

note: we don't use Tiled Tile Layer in this game but you can, Build Level takes care of it for you in layer.type == "tilelayer"

And this is the code that reads the Tiled lua file to build our levels:

TiledLevels = Core.class(Sprite)

function TiledLevels:init(tmpath, xtiny, xbworld, xlayers)
	local tm = require(tmpath) -- eg.: "tiled/test" without ".lua" extension + exclude from execution!
	local proto = false
	if tmpath:find("proto") then
		proto = true
	end
	-- some functions
	local function rgb2hex(r, g, b)
		return (r << 16) + (g << 8) + b
	end
	local dev = false -- true, false
	local tsimgpath -- "tiled/", root path to tileset tilemap images
	if dev then -- in development
		tsimgpath = -- path to external folder
			"C:/X_MOKA/PROJECTS/_gideros/WIKI/_assets/tuto_pf_cbump_tecs/"
	else -- prod
		if g_currlevel == 1 then tsimgpath = "tiled/lvl001/"
		elseif g_currlevel == 2 then tsimgpath = "tiled/lvl001/"
		elseif g_currlevel == 3 then tsimgpath = "tiled/lvl002/"
		end
	end
	-- _______ _____ _      ______  _____ ______ _______ 
	--|__   __|_   _| |    |  ____|/ ____|  ____|__   __|
	--   | |    | | | |    | |__  | (___ | |__     | |   
	--   | |    | | | |    |  __|  \___ \|  __|    | |   
	--   | |   _| |_| |____| |____ ____) | |____   | |   
	--   |_|  |_____|______|______|_____/|______|  |_|   
	-- this is the classic: a tileset with a tilemap image
	for i = 1, #tm.tilesets do -- important
		local tileset = tm.tilesets[i]
		-- add extra values (variables) to a tm.tilesets[i] table
		if tileset.image then -- only tileset tilemap layers
			tileset.numcols = math.floor(
				(tileset.imagewidth-tileset.margin+tileset.spacing)/
				(tileset.tilewidth+tileset.spacing)
			)
			tileset.numrows = math.floor(
				(tileset.imageheight-tileset.margin+tileset.spacing)/
				(tileset.tileheight+tileset.spacing)
			)
			tileset.lastgid = tileset.firstgid+(tileset.numcols*tileset.numrows)-1
			tileset.texture = Texture.new(
				tsimgpath..tileset.image, false,
				{ transparentColor=tonumber(tileset.transparentcolor), }
			)
		end
	end
	-- tileset function
	local function gid2tileset(tm, gid)
		for i = 1, #tm.tilesets do
			local tileset = tm.tilesets[i]
			if tileset.image then -- only valid tileset layers
				if tileset.firstgid <= gid and gid <= tileset.lastgid then
					return tileset
				end
			end
		end
	end
	-- _______ _____ _      ______  _____ ______ _______ 
	--|__   __|_   _| |    |  ____|/ ____|  ____|__   __|
	--   | |    | | | |    | |__  | (___ | |__     | |   
	--   | |    | | | |    |  __|  \___ \|  __|    | |   
	--   | |   _| |_| |____| |____ ____) | |____   | |   
	--   |_|  |_____|______|______|_____/|______|  |_|   
	-- _____ __  __          _____ ______  _____ 
	--|_   _|  \/  |   /\   / ____|  ____|/ ____|
	--  | | | \  / |  /  \ | |  __| |__  | (___  
	--  | | | |\/| | / /\ \| | |_ |  __|  \___ \ 
	-- _| |_| |  | |/ ____ \ |__| | |____ ____) |
	--|_____|_|  |_/_/    \_\_____|______|_____/ 
	-- this one parses individual images stored in a tileset
	local tilesetimages = {} -- table holding all the tileset images info (path, width, height)
	for i = 1, #tm.tilesets do
		local tileset = tm.tilesets[i]
		if not tileset.image then -- filter out tileset tilemap layers, only tileset images
			local tiles = tileset.tiles
			for j = 1, #tiles do
				-- populate the tilesetimages table based on the tile gid and id
				-- note: you may have to adjust the path to point to the image folder
				tilesetimages[tileset.firstgid + tiles[j].id] = {
					path=tsimgpath..tiles[j].image,
					width=tiles[j].width,
					height=tiles[j].height,
				}
			end
		end
	end
	-- pack all the deco images (TexturePack), perfs? XXX
	local bgtpt = {} -- background texture pack table
	local bgtptx = {} -- background texture pack table x
	local fgtpt = {} -- foreground texture pack table
	local fgtptx = {} -- foreground texture pack table x
	for i = 1, #tm.layers do
		local layer = tm.layers[i]
		if layer.name:match("bg_deco_images") then
			if layer.name:match("x$") then -- x$ = string ends with x
				for i = 1, #layer.objects do
					if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
--						print("bg", tilesetimages[layer.objects[i].gid].path)
						bgtptx[#bgtptx+1] = tilesetimages[layer.objects[i].gid].path
					end
				end
			else
				for i = 1, #layer.objects do
					if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
--						print("bg", tilesetimages[layer.objects[i].gid].path)
						bgtpt[#bgtpt+1] = tilesetimages[layer.objects[i].gid].path
					end
				end
			end
		elseif layer.name:match("fg_deco_images") then
			if layer.name:match("x$") then -- x$ = string ends with x
				for i = 1, #layer.objects do
					if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
--						print("fg", tilesetimages[layer.objects[i].gid].path)
						fgtptx[#fgtptx+1] = tilesetimages[layer.objects[i].gid].path
					end
				end
			else
				for i = 1, #layer.objects do
					if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
--						print("fg", tilesetimages[layer.objects[i].gid].path)
						fgtpt[#fgtpt+1] = tilesetimages[layer.objects[i].gid].path
					end
				end
			end
		end
	end
	local bgtp = TexturePack.new(bgtpt, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
	local bgtpx = TexturePack.new(bgtptx, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
	local fgtp = TexturePack.new(fgtpt, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
	local fgtpx = TexturePack.new(fgtptx, nil, nil, { format=TextureBase.RGBA4444, } ) -- perfs
	if #bgtp:getRegionsNames() > 0 then print("bgtp", bgtp:getSize()) end
	if #bgtpx:getRegionsNames() > 0 then print("bgtpx", bgtpx:getSize()) end
	if #fgtp:getRegionsNames() > 0 then print("fgtp", fgtp:getSize()) end
	if #fgtpx:getRegionsNames() > 0 then print("fgtpx", fgtpx:getSize()) end
	-- ____  _    _ _____ _      _____    _      ________      ________ _      
	--|  _ \| |  | |_   _| |    |  __ \  | |    |  ____\ \    / /  ____| |     
	--| |_) | |  | | | | | |    | |  | | | |    | |__   \ \  / /| |__  | |     
	--|  _ <| |  | | | | | |    | |  | | | |    |  __|   \ \/ / |  __| | |     
	--| |_) | |__| |_| |_| |____| |__| | | |____| |____   \  /  | |____| |____ 
	--|____/ \____/|_____|______|_____/  |______|______|   \/   |______|______|
	-- tileset images function
	local function parseImage(xobject, xlayer, xtplayer, xtexpack)
		local bitmap
		if xtplayer == "bg" then
			if xtexpack == "x" then
				bitmap = Bitmap.new(bgtpx:getTextureRegion(tilesetimages[xobject.gid].path))
			else
				bitmap = Bitmap.new(bgtp:getTextureRegion(tilesetimages[xobject.gid].path))
			end
		else -- fg
			if xtexpack == "x" then
				bitmap = Bitmap.new(fgtpx:getTextureRegion(tilesetimages[xobject.gid].path))
			else
				bitmap = Bitmap.new(fgtp:getTextureRegion(tilesetimages[xobject.gid].path))
			end
		end
		bitmap:setAnchorPoint(0, 1) -- because I always forget to modify Tiled objects alignment
		-- supports Tiled image scaling
		local scalex, scaley = xobject.width/bitmap:getWidth(), xobject.height/bitmap:getHeight()
		bitmap:setScale(scalex, scaley)
		bitmap:setRotation(xobject.rotation)
		bitmap:setPosition(xobject.x, xobject.y)
		xlayer:addChild(bitmap)
	end
	for i = 1, #tm.layers do
		local layer = tm.layers[i]
		local tilemaps = {}
		local group -- group = Sprite.new()
		-- _______ _____ _      ______ _           __     ________ _____  
		--|__   __|_   _| |    |  ____| |        /\\ \   / /  ____|  __ \ 
		--   | |    | | | |    | |__  | |       /  \\ \_/ /| |__  | |__) |
		--   | |    | | | |    |  __| | |      / /\ \\   / |  __| |  _  / 
		--   | |   _| |_| |____| |____| |____ / ____ \| |  | |____| | \ \ 
		--   |_|  |_____|______|______|______/_/    \_\_|  |______|_|  \_\
		if layer.type == "tilelayer" and (layer.name:match("bg") or layer.name:match("fg")) then
			if layer.name:match("bg") then group = xlayers["bg"]
			else group = xlayers["fg"]
			end
			for y = 1, layer.height do
				for x = 1, layer.width do
					local index = x + (y - 1) * layer.width
					local gid = layer.data[index]
					local gidtileset = gid2tileset(tm, gid)
					if gidtileset then
						local tilemap
						if tilemaps[gidtileset] then
							tilemap = tilemaps[gidtileset]
						else
							tilemap = TileMap.new(
								layer.width, layer.height,
								gidtileset.texture, gidtileset.tilewidth, gidtileset.tileheight,
								gidtileset.spacing, gidtileset.spacing,
								gidtileset.margin, gidtileset.margin,
								tm.tilewidth, tm.tileheight
							)
							tilemaps[gidtileset] = tilemap
							group:addChild(tilemap)
						end
						local tx = (gid - gidtileset.firstgid) % gidtileset.numcols + 1
						local ty = math.floor((gid - gidtileset.firstgid) / gidtileset.numcols) + 1
						-- set the tile with flip info
						tilemap:setTile(x, y, tx, ty)
					end
				end
			end
			group:setAlpha(layer.opacity)
		--  ____  ____       _ ______ _____ _______ _           __     ________ _____  
		-- / __ \|  _ \     | |  ____/ ____|__   __| |        /\\ \   / /  ____|  __ \ 
		--| |  | | |_) |    | | |__ | |       | |  | |       /  \\ \_/ /| |__  | |__) |
		--| |  | |  _ < _   | |  __|| |       | |  | |      / /\ \\   / |  __| |  _  / 
		--| |__| | |_) | |__| | |___| |____   | |  | |____ / ____ \| |  | |____| | \ \ 
		-- \____/|____/ \____/|______\_____|  |_|  |______/_/    \_\_|  |______|_|  \_\
		elseif layer.type == "objectgroup" then
			local o
			local myshape, mytable
			--                             _       __ _       _ _   _             
			--                            | |     / _(_)     (_) | (_)            
			-- _ __ ___   __ _ _ __     __| | ___| |_ _ _ __  _| |_ _  ___  _ __  
			--| '_ ` _ \ / _` | '_ \   / _` |/ _ \  _| | '_ \| | __| |/ _ \| '_ \ 
			--| | | | | | (_| | |_) | | (_| |  __/ | | | | | | | |_| | (_) | | | |
			--|_| |_| |_|\__,_| .__/   \__,_|\___|_| |_|_| |_|_|\__|_|\___/|_| |_|
			--                | |                                                 
			--                |_|                                                 
			if layer.name == "physics_map_def" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					self.mapdef = {}
					self.mapdef.t = o.y
					self.mapdef.l = o.x
					self.mapdef.r = o.width
					self.mapdef.b = o.height
				end
			--     _                  _                           
			--    | |                | |                          
			--  __| | ___  ___ ___   | | __ _ _   _  ___ _ __ ___ 
			-- / _` |/ _ \/ __/ _ \  | |/ _` | | | |/ _ \ '__/ __|
			--| (_| |  __/ (_| (_) | | | (_| | |_| |  __/ |  \__ \
			-- \__,_|\___|\___\___/  |_|\__,_|\__, |\___|_|  |___/
			--                                 __/ |              
			--                                |___/               
			elseif layer.name == "bg_deco_texts" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					mytable = {
						color=rgb2hex(table.unpack(o.color or { 255, 255, 255 } )),
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["bg"]:addChild(myshape)
				end
			elseif layer.name:match("bg_deco_shapes") then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local texpath
					local color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )) -- math.random(0xffffff)
					local r, g, b
					if o.name == "hint" then
						color = 0xffffff
					elseif o.name == "lava" then
						texpath = "gfx/textures/Water_01_Grey_1.png"
						color = nil
						r, g, b = 124, 205, 70
					elseif o.name == "spikes" then
						color = 0xff5500
					end
					mytable = {
						texpath=texpath, istexpot=true, scalex=0.4,
						color=color, r=r, g=g, b=b,
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["bg"]:addChild(myshape)
				end
			elseif layer.name:match("bg_deco_images") then
				--parseImage(xobject, xlayer, xtplayer, xtexpack)
				for i = 1, #layer.objects do
					if layer.name:match("x$") then
						parseImage(layer.objects[i], xlayers["bg"], "bg", "x")
					else
						parseImage(layer.objects[i], xlayers["bg"], "bg", nil)
					end
				end
			elseif layer.name == "fg_deco_texts" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					mytable = {
						color=rgb2hex(table.unpack(o.color or { 255, 255, 255 } )),
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["fg"]:addChild(myshape)
				end
			elseif layer.name:match("fg_deco_shapes") then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					mytable = {
						color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["fg"]:addChild(myshape)
				end
			elseif layer.name:match("fg_deco_images") then
				for i = 1, #layer.objects do
					if layer.name:match("x$") then
						if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
							parseImage(layer.objects[i], xlayers["fg"], "fg", "x")
						end
					else
						if layer.objects[i].gid then -- if no gid then this is not an image, skip it!
							parseImage(layer.objects[i], xlayers["fg"], "fg", nil)
						end
					end
				end
			--       _               _            _                           
			--      | |             (_)          | |                          
			-- _ __ | |__  _   _ ___ _  ___ ___  | | __ _ _   _  ___ _ __ ___ 
			--| '_ \| '_ \| | | / __| |/ __/ __| | |/ _` | | | |/ _ \ '__/ __|
			--| |_) | | | | |_| \__ \ | (__\__ \ | | (_| | |_| |  __/ |  \__ \
			--| .__/|_| |_|\__, |___/_|\___|___/ |_|\__,_|\__, |\___|_|  |___/
			--| |           __/ |                          __/ |              
			--|_|          |___/                          |___/               
			--                    _     _ 
			--                   | |   | |
			--__      _____  _ __| | __| |
			--\ \ /\ / / _ \| '__| |/ _` |
			-- \ V  V / (_) | |  | | (_| |
			--  \_/\_/ \___/|_|  |_|\__,_|
			elseif layer.name == "physics_exits" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isexit = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_voids" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isvoid = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_spikes" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isspike = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_springs" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local vx, vy
					local index
					index = o.name:find("vx") -- index vx
					vx = tonumber(o.name:sub(index):match("%d+")) -- find vxNNN
					index = o.name:find("vy") -- index vy
					vy = tonumber(o.name:sub(index):match("%d+")) -- find vyNNN
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isspring = true
					o.vx = vx*64 -- some multiplier, magik XXX
					o.vy = vy*64 -- some multiplier, magik XXX
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_sensors" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local xid = o.name -- examples: "mvgrAA", ...
					local opos = vector(o.x, o.y)
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
							alpha=0.25,
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					--ESensor:init(xid, xspritelayer, xpos, w, h, xlayers["bgfx"])
					local e = ESensor.new(
						xid, xlayers["actors"], opos, o.width, o.height, xlayers["bgfx"]
					)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
					-- some cleaning?
					e = nil
				end
			elseif layer.name:match("physics_doors") then -- doors = doorAAdx0dy8vel10dirU
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local xpos = vector(o.x, o.y)
					local dx, dy, vel, xdir
					local index
					index = o.name:find("dx") -- index dx
					local xid = o.name:sub(1, index-1) -- examples: "doorAA", "doorBB", ...
					dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
					index = o.name:find("dy") -- index dy
					dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
					index = o.name:find("vel") -- index vel
					vel = tonumber(o.name:sub(index):match("%d+")) -- find velNN
					index = o.name:find("dir") -- index dir
					xdir = o.name:sub(index+3) -- initial heading direction, examples: "U", "DR", "UL", ...
					local angle = math.atan2(dy, dx)
					local xspeed = vector(vel*math.cos(angle), vel*math.sin(angle))*vector(8, 8)
					local xtexpath
					local xcolor
					if proto then
						xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						xtexpath = "gfx/textures/Concrete_01_Grey_1.png"
						xcolor = 0xa76f53 -- plus a tint color
					end
					--EDoor:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
					local e = EDoor.new(
						xid, xlayers["actors"], xpos, xtexpath, xcolor, o.width, o.height,
						dx, dy, xdir, xspeed, xlayers["bgfx"]
					)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
					e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
					-- some cleaning?
					e = nil
				end
			elseif layer.name:match("physics_ptmvpfs") then -- passthrough moving platform
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local vpos = vector(o.x, o.y)
					local dx, dy, vel, xdir
					local index
					index = o.name:find("dx") -- index dx
					local xid = o.name:sub(1, index-1) -- examples: "idAA", "idBB", ...
					dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
					index = o.name:find("dy") -- index dy
					dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
					index = o.name:find("vel") -- index vel
					vel = tonumber(o.name:sub(index):match("%d+"))*8 -- find velNNN
					index = o.name:find("dir") -- index dir
					xdir = o.name:sub(index+3) -- initial heading direction, examples: "U", "DR", "UL", ...
					local angle = math.atan2(dy, dx)
					local vspeed = vector(vel*math.cos(angle), vel*math.sin(angle))
					local xtexpath
					local xcolor
					if proto then
						xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						xtexpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
						xcolor = 0xffaa7f -- plus a tint color
					end
					--EMvpf:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
					local e = EMvpf.new(
						xid, xlayers["actors"], vpos, xtexpath, xcolor, o.width, o.height,
						dx, dy, xdir, vspeed, true, xlayers["bgfx"]
					)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
					e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
					-- some cleaning?
					e = nil
				end
			elseif layer.name:match("physics_mvpfs") then -- non passthrough moving platform
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local vpos = vector(o.x, o.y)
					local dx, dy, vel, xdir
					local index
					index = o.name:find("dx") -- index dx
					local xid = o.name:sub(1, index-1) -- examples: "idAA", "idBB", ...
					dx = tonumber(o.name:sub(index):match("%d+")) -- find dxNNN
					index = o.name:find("dy") -- index dy
					dy = tonumber(o.name:sub(index):match("%d+")) -- find dyNNN
					index = o.name:find("vel") -- index vel
					vel = tonumber(o.name:sub(index):match("%d+"))*8 -- find velNNN
					index = o.name:find("dir") -- index dir
					xdir = o.name:sub(index+3) -- initial heading direction, examples: "N", "SE", "NW", ...
					local angle = math.atan2(dy, dx)
					local vspeed = vector(vel*math.cos(angle), vel*math.sin(angle))
					local xtexpath
					local xcolor
					if proto then
						xcolor = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						xtexpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
						xcolor = 0xffaa7f -- plus a tint color
					end
					--EMvpf:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
					local e = EMvpf.new(
						xid, xlayers["actors"], vpos, xtexpath, xcolor, o.width, o.height,
						dx, dy, xdir, vspeed, nil, xlayers["bgfx"]
					)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
					e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
					-- some cleaning?
					e = nil
				end
			elseif layer.name == "physics_ladders" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
							alpha=0.5,
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isladder = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_walls" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local texpath
					local color
					local r, g, b
					if proto then
						color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						texpath = "gfx/textures/Rock_Grey_02.png"
						r, g, b =139, 56, 28
					end
					mytable = {
						texpath=texpath, istexpot=true, scalex=0.5,
						color=color,
						r=r, g=g, b=b,
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["bg"]:addChild(myshape)
					o.iswall = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_steps" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					if proto then
						mytable = {
							color=rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } )),
						}
						myshape = self:buildShapes(o, mytable)
						myshape:setPosition(o.x, o.y)
						xlayers["bg"]:addChild(myshape)
					end
					o.isstep = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_ptpfs" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					o.pos = vector(o.x, o.y)
					local texpath
					local color
					local r, g, b
					if proto then
						color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						if o.name == "t" then
							-- use "t" as the object name to make it "transparent"
						else
							texpath = "gfx/textures/1K-brick_wall_17_baseColor.jpg_0001.png"
							r, g, b = 139, 56, 28 -- plus a tint color
						end
					end
					mytable = {
						texpath=texpath, istexpot=true, scalex=0.5,
						color=color,
						r=r, g=g, b=b,
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["bg"]:addChild(myshape)
					o.isptpf = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			elseif layer.name == "physics_grounds" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local texpath
					local color
					local r, g, b
					if proto then
						color = rgb2hex(table.unpack(layer.tintcolor or { 255, 255, 255 } ))
					else
						if o.name == "t" then -- transparent
							-- use "t" as the object name to make it "transparent"
						else
							texpath = "gfx/textures/wdipagu_2K_Albedo.jpg_0008.png"
							r, g, b = 255, 192, 160 -- plus a tint color
						end
					end
					mytable = {
						texpath=texpath, istexpot=true, scalex=1, skewx=3,
						color=color,
						r=r, g=g, b=b,
					}
					myshape = self:buildShapes(o, mytable)
					myshape:setPosition(o.x, o.y)
					xlayers["bg"]:addChild(myshape)
					o.isfloor = true
					xbworld:add(o, o.x, o.y, o.width, o.height)
				end
			--           _ _           _   _ _     _           
			--          | | |         | | (_) |   | |          
			--  ___ ___ | | | ___  ___| |_ _| |__ | | ___  ___ 
			-- / __/ _ \| | |/ _ \/ __| __| | '_ \| |/ _ \/ __|
			--| (_| (_) | | |  __/ (__| |_| | |_) | |  __/\__ \
			-- \___\___/|_|_|\___|\___|\__|_|_.__/|_|\___||___/
			elseif layer.name == "physics_keys" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local xid = o.name -- example "doorAA", "mvpfBB", ...
					xid = "key"..tostring(xid) -- we append the keyword key to tell it is a key XXX
					local opos = vector(o.x, o.y)
					--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
					local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			elseif layer.name == "physics_coins" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					local xid = layer.name:gsub("physics_", "")
					--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
					local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 4*8, 4*8)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			elseif layer.name == "physics_lives" then
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					local xid = layer.name:gsub("physics_", "")
					--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
					local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			--            _                 
			--           | |                
			--  __ _  ___| |_ ___  _ __ ___ 
			-- / _` |/ __| __/ _ \| '__/ __|
			--| (_| | (__| || (_) | |  \__ \
			-- \__,_|\___|\__\___/|_|  |___/
			elseif layer.name == "physics_ground_nmes03" then -- nmes
				-- 300: no move, no jump, shoot all angles
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					local collectible = o.name
					if collectible == "" then collectible = "coins" -- default to coins
					elseif collectible == "x" then collectible = nil
					end
					--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
					local e = EGroundNmes.new(300, xlayers["actors"], opos, xlayers["bgfx"], collectible)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			elseif layer.name == "physics_ground_nmes02" then -- nmes
				-- 200: move, jump, no shoot
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					local collectible = o.name
					if collectible == "" then collectible = "coins" -- default to coins
					elseif collectible == "x" then collectible = nil
					end
					--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
					local e = EGroundNmes.new(200, xlayers["actors"], opos, xlayers["bgfx"], collectible)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			elseif layer.name == "physics_ground_nmes" then
				-- 100: no move, no jump, no shoot
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					local collectible = o.name
					if collectible == "" then collectible = nil -- default to coins
					elseif collectible == "x" then collectible = nil
					end
					--EGroundNmes:init(xid, xspritelayer, xpos, xlayers, xcollectible)
					local e = EGroundNmes.new(100, xlayers["actors"], opos, xlayers["bgfx"], collectible)
					xtiny.tworld:addEntity(e)
					xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
				end
			elseif layer.name == "physics_players" then -- player1
				for i = 1, #layer.objects do
					o = layer.objects[i]
					local opos = vector(o.x, o.y)
					-- EPlayer1:init(xspritelayer, xpos, xbgfxlayer)
					self.player1 = EPlayer1.new(xlayers["actors"], opos, xlayers["bgfx"])
					xtiny.tworld:addEntity(self.player1)
					xbworld:add(
						self.player1,
						self.player1.pos.x, self.player1.pos.y,
						self.player1.collbox.w, self.player1.collbox.h
					)
				end
			end
			-- some cleaning?
			o = nil
			myshape, mytable = nil, nil
		end
	end
end

function TiledLevels:buildShapes(xobject, xlevelsetup)
	local myshape -- Tiled shapes: ellipse, point, polygon, polyline, rectangle, text
	local tablebase = {}
	if xobject.shape == "ellipse" then
		tablebase = {
			x=xobject.x, y=xobject.y,
			w=xobject.width, h=xobject.height,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Ellipse.new(tablebase)
	elseif xobject.shape == "point" then
		tablebase = {
			x=xobject.x, y=xobject.y,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Point.new(tablebase)
	elseif xobject.shape == "polygon" then
		tablebase = {
			x=xobject.x, y=xobject.y,
			coords=xobject.polygon,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Polygon.new(tablebase)
	elseif xobject.shape == "polyline" then -- lines
		tablebase = {
			x=xobject.x, y=xobject.y,
			coords=xobject.polyline,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Polyline.new(tablebase)
	elseif xobject.shape == "rectangle" then
		tablebase = {
			x=xobject.x, y=xobject.y,
			w=xobject.width, h=xobject.height,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Rectangle.new(tablebase)
	elseif xobject.shape == "text" then
		tablebase = {
			x=xobject.x, y=xobject.y,
			text=xobject.text,
			w=xobject.width, h=xobject.height,
			rotation=xobject.rotation,
		}
		for k, v in pairs(xlevelsetup) do tablebase[k] = v end
		myshape = Tiled_Shape_Text.new(tablebase)
	else
		print("*** CANNOT PROCESS THIS SHAPE! ***", xobject.shape, xobject.name)
		return
	end

	return myshape
end

To build the shapes in the game we use the Gideros Shape class. I have written several classes for each one of them: Tiled_Shape_Ellipse, Tiled_Shape_Point, Tiled_Shape_Polygon, ... . When we read the Tiled file we look for the layer name, if the layer contains shapes, we call the appropriate shape class and voilà :-). The buildShapes function is at the very bottom of the class.

Please add the Tiled Deco Shapes classes to your project: Tiled Deco Shapes

That's basically how we build our levels using Tiled. You can read more about Tiled and Gideros here: Tiled_Bump.

Next?

That was quite a lot of work but we coded the heart of our game and we are already nearly done!!

All is left to do is add the actors. Actors will be ECS entites, those entities will have components and systems will control them.

In the next part we deal with our player1 entity.


Prev.: Tuto tiny-ecs 2d platformer Part 3 transitions menu options
Next: Tuto tiny-ecs 2d platformer Part 5 ePlayer1


Tutorial - tiny-ecs 2d platformer