Tuto tiny-ecs 2d platformer Part 11 Systems 3

From GiderosMobile

The Systems 3

A couple more systems to add and we are done. We also add the Debug Systems to help us visualize the collision boxes.

sCollectibles.lua

Let's start with the collectibles, you know those things you can pick up 😊.

Please create a file "sCollectibles.lua" in the "_S" folder and the code:

SCollectibles = Core.class()

function SCollectibles:init(xtiny, xbump, xplayer1) -- tiny function
	self.tiny = xtiny -- make a class ref
	self.tiny.processingSystem(self) -- called once on init and every update
	self.bworld = xbump
	self.player1 = xplayer1
	-- sfx
	self.snd = { sound=Sound.new("audio/sfx/sfx_coin_double1.wav"), time=0, delay=0.2, }
end

function SCollectibles:filter(ent) -- tiny function
	return ent.iscollectible
end

function SCollectibles:onAdd(ent) -- tiny function
end

function SCollectibles:onRemove(ent) -- tiny function
	self.bworld:remove(ent) -- remove collision box from cbump world here!
end

function SCollectibles:process(ent, dt) -- tiny function
	if ent.isdirty then -- hit
		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
		local snd = self.snd -- sfx
		local curr = os.timer()
		local prev = snd.time
		if curr - prev > snd.delay then
			local channel = snd.sound:play()
			if channel then channel:setVolume(g_sfxvolume*0.01) end
			snd.time = curr
		end
		if ent.eid == "coins" then -- coins
			self.tiny.numberofcoins += 1
			self.tiny.hudcoins:setText("COINS: "..self.tiny.numberofcoins)
		elseif ent.eid == "lives" then -- hearts
			self.player1.currhealth += 1
			-- hud
			local hudhealthwidth = map(self.player1.currhealth, 0, self.player1.totalhealth, 0, 100)
			self.tiny.hudhealth:setWidth(hudhealthwidth)
			if self.player1.currhealth < self.player1.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000)
			elseif self.player1.currhealth < self.player1.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500)
			else self.tiny.hudhealth:setColor(0x00ff00)
			end
		elseif ent.eid == "dash" then -- ???
			self.player1.body.candash = true
		end
		self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
	end
end

What it does:

  • depending on the item collected (coin, live, ...) it increases a value
  • the UI is updated as well to reflect the new values
  • the collectible is removed from the scene in the onRemove function

sDoor.lua

Doors will work in tandem with sensors and keys collectible. A door can open in all directions: up, down, left, right and diagonals.

Please create a file "sDoor.lua" in the "_S" folder and the code:

SDoor = Core.class()

function SDoor:init(xtiny, xbworld, xplayer1)
	xtiny.processingSystem(self) -- called once on init and every frames
	self.tiny = xtiny
	self.bworld = xbworld -- cbump world
	self.player1 = xplayer1
end

function SDoor:filter(ent) -- tiny function
	return ent.isdoor
end

function SDoor:onAdd(ent) -- tiny function
--	print("SDoor:onAdd")
end

function SDoor:onRemove(ent) -- tiny function
--	print("SDoor:onRemove")
end

local p1rangetoofarx = myappwidth*1 -- disable systems to save some CPU, magik XXX
local p1rangetoofary = myappheight*1 -- disable systems to save some CPU, magik XXX
function SDoor:process(ent, dt) -- tiny function
	local function fun()
		-- OUTSIDE VISIBLE RANGE
		if (ent.pos.x > self.player1.pos.x + p1rangetoofarx or
			ent.pos.x < self.player1.pos.x - p1rangetoofarx) or
			(ent.pos.y > self.player1.pos.y + p1rangetoofary or
			ent.pos.y < self.player1.pos.y - p1rangetoofary) then
			ent.doanimate = false
			return
		end
		ent.isactive = false
		ent.isup = false ent.isdown = false
		ent.isleft = false ent.isright = false
		if ent.eid == self.player1.insensor then -- player1 inside door sensor, open
			if ent.eid:find("Z") or -- no key needed for eid containing 'Z'
				(self.tiny.player1inventory[ent.eid]) then -- player1 has key in inventory
				-- left/right opening
				if ent.dir:match("L") then
					if ent.pos.x > ent.distance.startpos.x - ent.distance.dx then -- move left
						ent.isleft = true ent.isright = false
						ent.isactive = true
					end
				elseif ent.dir:match("R") then
					if ent.pos.x < ent.distance.startpos.x + ent.distance.dx then -- move right
						ent.isright = true ent.isleft = false
						ent.isactive = true
					end
				end
				-- up/down opening
				if ent.dir:match("U") then
					if ent.pos.y > ent.distance.startpos.y - ent.distance.dy then -- move up
						ent.isup = true ent.isdown = false
						ent.isactive = true
					end
				elseif ent.dir:match("D") then
					if ent.pos.y < ent.distance.startpos.y + ent.distance.dy then -- move down
						ent.isdown = true ent.isup = false
						ent.isactive = true
					end
				end
			end
		else -- player1 outside door sensor, close
			-- left/right closing
			if ent.dir:match("L") then
				if ent.pos.x < ent.distance.startpos.x then -- move right
					ent.isright = true ent.isleft = false
					ent.isactive = true
				end
			elseif ent.dir:match("R") then
				if ent.pos.x > ent.distance.startpos.x then -- move left
					ent.isleft = true ent.isright = false
					ent.isactive = true
				end
			end
			-- up/down closing
			if ent.dir:match("U") then
				if ent.pos.y < ent.distance.startpos.y then -- move down
					ent.isdown = true ent.isup = false
					ent.isactive = true
				end
			elseif ent.dir:match("D") then
				if ent.pos.y > ent.distance.startpos.y - ent.distance.dy then -- move up
					ent.isup = true ent.isdown = false
					ent.isactive = true
				end
			end
		end
		-- if player1 acquires key, illuminate (cutscenes, ...)
		if (ent.eid:find("Z") and not ent.highlight) or -- no key needed for eid containing 'Z'
			(self.tiny.player1inventory[ent.eid] and not ent.highlight) then -- player1 has key
			ent.sprite:setAlpha(1.7) -- 1.6, 2
			ent.highlight = true -- only highlight once
		end
		-- action
		if ent.isactive then
			local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
				if other.isnme or other.isplayer1 then return "slide" end -- ok
				return nil -- "cross"
			end
			--  _____ ____  _    _ __  __ _____  
			-- / ____|  _ \| |  | |  \/  |  __ \ 
			--| |    | |_) | |  | | \  / | |__) |
			--| |    |  _ <| |  | | |\/| |  ___/ 
			--| |____| |_) | |__| | |  | | |     
			-- \_____|____/ \____/|_|  |_|_|     
			local goalx = ent.pos.x + ent.body.vx * dt
			local goaly = ent.pos.y + ent.body.vy * dt
			local nextx, nexty = self.bworld:move(ent, goalx, goaly, collisionfilter)
			--  _____  _    ___     _______ _____ _____  _____ 
			-- |  __ \| |  | \ \   / / ____|_   _/ ____|/ ____|
			-- | |__) | |__| |\ \_/ / (___   | || |    | (___  
			-- |  ___/|  __  | \   / \___ \  | || |     \___ \ 
			-- | |    | |  | |  | |  ____) |_| || |____ ____) |
			-- |_|    |_|  |_|  |_| |_____/|_____\_____|_____/ 
			if ent.isleft and not ent.isright then
				ent.body.vx = -ent.body.speed
			elseif ent.isright and not ent.isleft then
				ent.body.vx = ent.body.speed
			end
			if ent.isup and not ent.isdown then
				ent.body.vy = -ent.body.upspeed
			elseif ent.isdown and not ent.isup then
				ent.body.vy = ent.body.upspeed
			end
			-- move
			ent.pos = vector(nextx, nexty)
			ent.sprite:setPosition(ent.pos + vector(ent.collbox.w/2, -ent.h/2+ent.collbox.h))
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

What it does:

  • first we check if the player1 is in the door sensor by checking the door id and the player1 sensor id
  • then we check if the door needs a key to open or not
  • if the door needs a key, we check the player1 inventory for that key
  • when the player1 is in the door sensor, we open the door in the given direction
  • when the player1 goes outside the door sensor, we close the door
  • finally cBump uses the state the door is in and moves the door open or close

sSensor.lua

Here is the sensor System. It is used for doors but can work for anything.

Please create a file "sSensor.lua" in the "_S" folder and the code:

SSensor = Core.class()

function SSensor:init(xtiny, xbworld, xplayer1)
	xtiny.processingSystem(self) -- called once on init and every frames
	self.bworld = xbworld -- cbump world
	self.player1 = xplayer1
end

function SSensor:filter(ent) -- tiny function
	return ent.issensor
end

function SSensor:onAdd(ent) -- tiny function
--	print("SSensor:onAdd")
end

function SSensor:onRemove(ent) -- tiny function
--	print("SSensor:onRemove")
end

local p1rangetoofarx = myappwidth*1 -- disable systems to save some CPU, magik XXX
local p1rangetoofary = myappheight*1 -- disable systems to save some CPU, magik XXX
function SSensor:process(ent, dt) -- tiny function
	local function fun()
		-- OUTSIDE VISIBLE RANGE
		if (ent.pos.x > self.player1.pos.x + p1rangetoofarx or
			ent.pos.x < self.player1.pos.x - p1rangetoofarx) or
			(ent.pos.y > self.player1.pos.y + p1rangetoofary or
			ent.pos.y < self.player1.pos.y - p1rangetoofary) then
			ent.doanimate = false
			return
		end
		local function collisionfilter2(item) -- only one param: "item", return true, false or nil
			if item.isplayer1 then return true end
		end
		--local items, len = world:queryRect(l,t,w,h, filter)
		local items, len2 = self.bworld:queryRect(
			ent.pos.x, ent.pos.y, ent.w, ent.h, collisionfilter2
		)
		for i = 1, len2 do
			items[i].insensor = ent.eid -- add an extra var to player1
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

What it does:

  • it uses cBump queryRect function to check if the player1 is within the sensor boundaries
  • the sensor collision filter (collisionfilter2) is where we add the player1 as a potential candidate for collision detection with the sensor
  • when the player1 is "in" the sensor, we set the player1 insensor variable to the id of the sensor. We can then querry that variable in the game to trigger events

sMvpf.lua

A quick one, the moving platforms. Please create a file "sMvpf.lua" in the "_S" folder and the code:

SMvpf = Core.class()

function SMvpf:init(xtiny, xbworld)
	xtiny.processingSystem(self) -- called once on init and every frames
	self.bworld = xbworld -- cbump world
end

function SMvpf:filter(ent) -- tiny function
	return ent.ismvpf -- both mvpf and ptmvpf
end

function SMvpf:onAdd(ent) -- tiny function
--	print("SMvpf:onAdd")
end

function SMvpf:onRemove(ent) -- tiny function
--	print("SMvpf:onRemove")
end

function SMvpf:process(ent, dt) -- tiny function
	local function fun()
		local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
			return "cross"
		end
		-- cbump
		local goalx = ent.pos.x + ent.body.vx * dt
		local goaly = ent.pos.y + ent.body.vy * dt
		local nextx, nexty, _collisions, _len = self.bworld:move(ent, goalx, goaly, collisionfilter)
		-- motion ai x
		if ent.pos.x >= ent.distance.startpos.x + ent.distance.dx then
			ent.isleft, ent.isright = true, false
		elseif ent.pos.x <= ent.distance.startpos.x then
			ent.isleft, ent.isright = false, true
		end
		-- motion ai y
		if ent.dir == "SE" then -- initial movement going down to the right
			if ent.pos.y >= ent.distance.startpos.y + ent.distance.dy then
				ent.isup, ent.isdown = true, false
			elseif ent.pos.y <= ent.distance.startpos.y then
				ent.isup, ent.isdown = false, true
			end
		else
			if ent.pos.y <= ent.distance.startpos.y - ent.distance.dy then
				ent.isup, ent.isdown = false, true
			elseif ent.pos.y >= ent.distance.startpos.y then
				ent.isup, ent.isdown = true, false
			end
		end
		-- movement vx
		if ent.isleft and not ent.isright then -- LEFT
			ent.flip = -1
			ent.body.vx = -ent.body.speed
		elseif ent.isright and not ent.isleft then -- RIGHT
			ent.flip = 1
			ent.body.vx = ent.body.speed
		end
		-- movement vy
		if ent.isup and not ent.isdown then -- UP
			ent.body.vy = -ent.body.upspeed
		elseif ent.isdown and not ent.isup then -- DOWN
			ent.body.vy = ent.body.upspeed
		end
		-- move & flip
		ent.pos = vector(nextx, nexty)
		ent.sprite:setPosition(ent.pos + vector(ent.collbox.w/2, -ent.h/2+ent.collbox.h))
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

What it does:

  • we use cBump to move the platform in the physics world
  • we set the default platform collision to cross using collisionfilter
  • we check the position of the platform and set its direction (up, down, left, right) relative to the distance it can travel
  • we set the moving platform velocity (speed)
  • we move the platform body (cBump) along with the platform sprite

sOscillation.lua

Another quick one, the System that moves things up and down like the collectibles. Please create a file "sOscillation.lua" in the "_S" folder and the code:

SOscillation = Core.class()

function SOscillation:init(xtiny, xbworld, xplayer1)
	xtiny.processingSystem(self) -- called once on init and every frames
	self.bworld = xbworld -- cbump world
	self.player1 = xplayer1
end

function SOscillation:filter(ent) -- tiny function
	return ent.oscillation
end

function SOscillation:onAdd(ent) -- tiny function
--	print("SOscillation:onAdd")
	ent.angle = 0 -- add an extra params, important XXX
end

function SOscillation:onRemove(ent) -- tiny function
--	print("SOscillation:onRemove")
end

local p1rangetoofarx = myappwidth*1 -- disable systems to save some CPU, magik XXX
local p1rangetoofary = myappheight*1 -- disable systems to save some CPU, magik XXX
function SOscillation:process(ent, dt) -- tiny function
	local function fun()
		ent.doanimate = true
		-- OUTSIDE VISIBLE RANGE
		if (ent.pos.x > self.player1.pos.x + p1rangetoofarx or
			ent.pos.x < self.player1.pos.x - p1rangetoofarx) or
			(ent.pos.y > self.player1.pos.y + p1rangetoofary or
			ent.pos.y < self.player1.pos.y - p1rangetoofary) then
			ent.doanimate = false
			return
		end
		local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
			return nil -- "cross"
		end
		-- cbump
		local goalx = ent.pos.x + ent.oscillation.vx * dt
		local goaly = ent.pos.y + ent.oscillation.vy * dt
		local nextx, nexty = self.bworld:move(ent, goalx, goaly, collisionfilter)
		-- oscillation
		ent.angle += ent.oscillation.speed
		ent.oscillation.vx = math.cos(^<ent.angle)*ent.oscillation.amplitudex
		ent.oscillation.vy = math.sin(^<ent.angle)*ent.oscillation.amplitudey
		-- move & flip
		ent.pos = vector(nextx, nexty)
		ent.sprite:setPosition(ent.pos + vector(ent.collbox.w/2, -ent.h/2+ent.collbox.h))
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

What it does:

  • this is to give some juice to our game, we move collectibles up and down or in circles
  • we use cBump to move the body in the physics world using sinus and cosinus math functions 😊

sProjectiles.lua

Finally the projectiles everybody can throw. Please create a file "sProjectiles.lua" in the "_S" folder and the code:

SProjectiles = Core.class()

function SProjectiles:init(xtiny, xbworld) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
	self.tiny = xtiny
	self.bworld = xbworld
end

function SProjectiles:filter(ent) -- tiny function
	return ent.isprojectile
end

function SProjectiles:onAdd(ent) -- tiny function
end

function SProjectiles:onRemove(ent) -- tiny function
	self.bworld:remove(ent) -- remove collision box from cbump world here!
end

local col -- cbump perfs?
function SProjectiles:process(ent, dt) -- tiny function
	-- hurt
	if ent.washurt > 0 and ent.wasbadlyhurt <= 0 and ent.currlives > 0 then -- lose 1 nrg
		ent.washurt -= 1
		ent.isdirty = false
		ent.animation.curranim = g_ANIM_HURT_R
		if ent.washurt <= 0 then
			ent.sprite:setColorTransform(1, 1, 1, 1) -- reset color transform
		elseif ent.washurt < ent.recovertimer*0.5 then
			ent.hitfx:setVisible(false)
		end
	elseif ent.wasbadlyhurt > 0 and ent.currlives > 0 then -- lose 1 life
		ent.wasbadlyhurt -= 1
		ent.isdirty = false
		ent.animation.curranim = g_ANIM_LOSE1_R
		if ent.wasbadlyhurt <= 0 then
			ent.sprite:setColorTransform(1, 1, 1, 1) -- reset color transform
		elseif ent.wasbadlyhurt < ent.recoverbadtimer*0.5 then
			ent.hitfx:setVisible(false)
			ent.animation.curranim = g_ANIM_STANDUP_R
			ent.animation.frame = 0 -- start animation at frame 0
		end
	end
	-- hit
	if ent.isdirty then
		ent.isdirty = false
		ent.currlives -= 1
	end
	-- deaded
	if ent.currlives <= 0 or
		ent.pos.x > (ent.dist.startpos.x+ent.dist.dx) or
		ent.pos.x < (ent.dist.startpos.x-ent.dist.dx) or
		ent.pos.y > (ent.dist.startpos.y+ent.dist.dy) or
		ent.pos.y < (ent.dist.startpos.y-ent.dist.dy) then
		-- stop all movements
		ent.isleft = false
		ent.isright = false
		ent.isup = false
		ent.isdown = false
		-- play dead sequence
		ent.isdirty = false
		self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
	end
	-- already deaded
	if ent.currlives <= 0 or
		ent.pos.x > (ent.dist.startpos.x+ent.dist.dx) or
		ent.pos.x < (ent.dist.startpos.x-ent.dist.dx) or
		ent.pos.y > (ent.dist.startpos.y+ent.dist.dy) or
		ent.pos.y < (ent.dist.startpos.y-ent.dist.dy) then
		return
	end
	local function fun()
		--  _____ ____  _      _      _____  _____ _____ ____  _   _ 
		-- / ____/ __ \| |    | |    |_   _|/ ____|_   _/ __ \| \ | |
		--| |   | |  | | |    | |      | | | (___   | || |  | |  \| |
		--| |   | |  | | |    | |      | |  \___ \  | || |  | | . ` |
		--| |___| |__| | |____| |____ _| |_ ____) |_| || |__| | |\  |
		-- \_____\____/|______|______|_____|_____/|_____\____/|_| \_|
		-- ______ _____ _   _______ ______ _____  
		--|  ____|_   _| | |__   __|  ____|  __ \ 
		--| |__    | | | |    | |  | |__  | |__) |
		--|  __|   | | | |    | |  |  __| |  _  / 
		--| |     _| |_| |____| |  | |____| | \ \ 
		--|_|    |_____|______|_|  |______|_|  \_\
		local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
			return "cross"
		end
		--  _____ ____  _    _ __  __ _____  
		-- / ____|  _ \| |  | |  \/  |  __ \ 
		--| |    | |_) | |  | | \  / | |__) |
		--| |    |  _ <| |  | | |\/| |  ___/ 
		--| |____| |_) | |__| | |  | | |     
		-- \_____|____/ \____/|_|  |_|_|     
		local goalx = ent.pos.x + ent.body.vx * dt
		local goaly = ent.pos.y + ent.body.vy * dt
		local nextx, nexty, collisions, len = self.bworld:move(ent, goalx, goaly, collisionfilter)
		--  _____ ____  _      _      _____  _____ _____ ____  _   _  _____ 
		-- / ____/ __ \| |    | |    |_   _|/ ____|_   _/ __ \| \ | |/ ____|
		--| |   | |  | | |    | |      | | | (___   | || |  | |  \| | (___  
		--| |   | |  | | |    | |      | |  \___ \  | || |  | | . ` |\___ \ 
		--| |___| |__| | |____| |____ _| |_ ____) |_| || |__| | |\  |____) |
		-- \_____\____/|______|______|_____|_____/|_____\____/|_| \_|_____/ 
		-- we handle ALL collisions here!
		for i = 1, len do
			col = collisions[i]
			if col.other.isplayer1 and col.item.eid > 1 then -- nme bullet vs player1
				if col.other.body.currdashtimer <= 0 then
					col.other.damage = 1
					col.other.isdirty = true
					col.item.damage = 1
					col.item.isdirty = true
					-- don't destroy bullet if it is persistent
					if col.item.ispersistant then col.item.isdirty = false end
				end
			elseif col.other.isnme and col.item.eid == 1 then -- player1 bullet vs nmes
				if col.other.body.currdashtimer <= 0 then
					col.item.damage = 1
					col.other.damage = 1
					col.item.isdirty = true
					if col.item.ispersistant then col.item.isdirty = false end
					col.other.isdirty = true
				end
			elseif col.other.isdoor then -- bullets vs door
				col.item.damage = 1
				col.item.isdirty = true
--			elseif col.other.isfloor then -- bullets vs floor
--				col.item.damage = 1
--				col.item.isdirty = true
			end
		end
		--  _____  _    ___     _______ _____ _____  _____ 
		-- |  __ \| |  | \ \   / / ____|_   _/ ____|/ ____|
		-- | |__) | |__| |\ \_/ / (___   | || |    | (___  
		-- |  ___/|  __  | \   / \___ \  | || |     \___ \ 
		-- | |    | |  | |  | |  ____) |_| || |____ ____) |
		-- |_|    |_|  |_|  |_| |_____/|_____\_____|_____/ 
		-- gravity
--		ent.body.vy += 1*8 * ent.body.currmass -- 3*8, gravity, magik XXX
		-- move & flip
		ent.pos = vector(nextx, nexty)
		ent.sprite:setPosition(ent.pos + vector(ent.collbox.w/2, -ent.h/2+ent.collbox.h))
		if ent.animation then
			ent.animation.bmp:setScale(ent.sx*ent.flip, ent.sy)
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

What it does:

  • first we check if the projectile has hit its target and remove the projectile from the scene
  • else we use cBump to move the projectile and detect collisions
  • on collision we inflict damage to the appropriate actor (player1, enemies, ...)
  • as usual we move the projectile sprite along its body in the cBump physics world

I think we have all our systems 👍.

DEBUG

To make it easy to debug collisions in the physics world and actually see them, let's add some quick debug systems.

To see the collision boxes attached to an Entity, please create a file "sDebugCollision.lua" in the "_S" folder and the code:

SDebugCollision = Core.class()

function SDebugCollision:init(xtiny) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
end

function SDebugCollision:filter(ent) -- tiny function
	return ent.collbox
end

function SDebugCollision:onAdd(ent) -- tiny function
	local debugcolor = 0x9b009b
	if ent.isplayer1 then debugcolor = 0xff00ff end
	ent.debug = Pixel.new(debugcolor, 0.5, ent.collbox.w, ent.collbox.h)
	ent.spritelayer:addChild(ent.debug)
end

function SDebugCollision:onRemove(ent) -- tiny function
	ent.debug:removeFromParent()
end

function SDebugCollision:process(ent, dt) -- tiny function
	local function fun()
		ent.debug:setPosition(ent.pos)
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

To see the origin position of collision boxes attached to an Entity, please create a file "sDebugPosition.lua" in the "_S" folder and the code:

SDebugPosition = Core.class()

function SDebugPosition:init(xtiny) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
end

function SDebugPosition:filter(ent) -- tiny function
	return ent.pos
end

function SDebugPosition:onAdd(ent) -- tiny function
	local debugcolor = 0x0000a5
	if ent.isplayer1 then debugcolor = 0x0000ff end
	ent.debug = Pixel.new(debugcolor, 2, 5, 5)
	ent.spritelayer:addChild(ent.debug)
end

function SDebugPosition:onRemove(ent) -- tiny function
	ent.debug:removeFromParent()
end

function SDebugPosition:process(ent, dt) -- tiny function
	local function fun()
		ent.debug:setPosition(ent.pos)
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

To see the shield collision boxes attached to an Entity, please create a file "sDebugShield.lua" in the "_S" folder and the code:

SDebugShield = Core.class()

function SDebugShield:init(xtiny) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
end

function SDebugShield:filter(ent) -- tiny function
	return ent.shield
end

function SDebugShield:onAdd(ent) -- tiny function
	--ent.pos + vector(ent.collbox.w/2, 0) + ent.shield.offset*vector(ent.flip, 1)
	local pw, ph = ent.shield.sprite:getWidth(), ent.shield.sprite:getHeight()
	ent.debug = Pixel.new(0x5500ff, 0.2, pw, ph)
	ent.debug:setAnchorPoint(0.5, 0.5)
	ent.spritelayer:addChild(ent.debug)
	-- querry rect
	ent.debugqr = Pixel.new(0xaaff00, 0.3, pw, ph) -- querry rectangle
	ent.debugqr:setAnchorPoint(0, 0.01) -- a little offset
	ent.spritelayer:addChild(ent.debugqr)
end

function SDebugShield:onRemove(ent) -- tiny function
	ent.debug:removeFromParent()
	ent.debugqr:removeFromParent()
end

function SDebugShield:process(ent, dt) -- tiny function
	local function fun()
		if ent.currlives > 0 then
			ent.debug:setPosition(
				ent.pos +
				vector(ent.collbox.w/2, 0) +
				ent.shield.offset*vector(ent.shield.sprite.sx*ent.flip, ent.shield.sprite.sy)
			)
			local pw, ph = ent.shield.sprite:getWidth(), ent.shield.sprite:getHeight()
			ent.debugqr:setPosition(
				ent.pos +
				ent.shield.offset*vector(ent.shield.sprite.sx*ent.flip, ent.shield.sprite.sy) -
				vector(pw*0.5, ph*0.5) + vector(ent.collbox.w*0.5, 0)
			)
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

Next?

Congratulations! We have finished our game.

Let's add the final scene: YOU WIN!


Prev.: Tuto tiny-ecs 2d platformer Part 10 Collision System
Next: Tuto tiny-ecs 2d platformer Part 12 You Win


Tutorial - tiny-ecs 2d platformer