Tuto tiny-ecs 2d platformer Part 10 Collision System

From GiderosMobile
Revision as of 08:02, 6 November 2025 by MoKaLux (talk | contribs) (wip)

The Collision System

This System is where all the fun happens: collisions. Here we deal with cBump for the collisions, cBump is the physics engine we use. We also implement gravity, coyote timer, input buffering, ...

The list of collisions:

	-- physics flags
	ent.isstepcontacts = false
	ent.isfloorcontacts = false
	ent.isladdercontacts = false
	ent.isptpfcontacts = false -- pass through platform
	ent.ismvpfcontacts = false -- moving platform
	ent.iswallcontacts = false
	ent.isspringcontacts = false

Let's go!

sCollision.lua

The cBump physics engine is a Gideros plugin. I cannot explain in details how it works but I will do my best!

  • when we create an Entity, we add it to tiny-ecs world, Gideros stage and also cBump world. This is done when we load the Tiled.lua file. For example, here we add a moving platform:
	--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) -- cBump world
	e.sprite:setPosition(e.pos + vector(e.collbox.w/2, -e.h/2+e.collbox.h))
  • once an Entity exists in cBump world we can code how it collides in the physics world
  • the player1 Entity is moved by the player using the keyboard. An enemy Entity is moved using the AI System (isup, isdown, isleft, isright, ...)
  • cBump uses a collisionfilter to process collisions between entities in the physics world. That is what entity collides with what
  • then cBump resolves the Entity collision ("touch", "cross", "slide" or "bounce")

The cBump move function:

	--  _____ ____  _    _ __  __ _____  
	-- / ____|  _ \| |  | |  \/  |  __ \ 
	--| |    | |_) | |  | | \  / | |__) |
	--| |    |  _ <| |  | | |\/| |  ___/ 
	--| |____| |_) | |__| | |  | | |     
	-- \_____|____/ \____/|_|  |_|_|     
	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)

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

SCollision = Core.class()

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

function SCollision:filter(ent) -- tiny function
	return ent.collbox and ent.body and
		not (ent.isprojectile or ent.ismvpf or ent.isdoor)
end

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

function SCollision:onRemove(ent) -- tiny function
end

local p1rangetoofarx = myappwidth*2 -- disable systems to save some CPU, magik XXX
local p1rangetoofary = myappheight*2 -- disable systems to save some CPU, magik XXX
function SCollision:process(ent, dt) -- tiny function
	-- OUTSIDE VISIBLE RANGE
	if ent.isnme then
		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
	end
	-- physics flags
	ent.isstepcontacts = false
	ent.isfloorcontacts = false
	ent.isladdercontacts = false
	ent.isptpfcontacts = false
	ent.ismvpfcontacts = false
	ent.iswallcontacts = false
	ent.isspringcontacts = false
	--  _____ ____  _      _      _____  _____ _____ ____  _   _ 
	-- / ____/ __ \| |    | |    |_   _|/ ____|_   _/ __ \| \ | |
	--| |   | |  | | |    | |      | | | (___   | || |  | |  \| |
	--| |   | |  | | |    | |      | |  \___ \  | || |  | | . ` |
	--| |___| |__| | |____| |____ _| |_ ____) |_| || |__| | |\  |
	-- \_____\____/|______|______|_____|_____/|_____\____/|_| \_|
	-- ______ _____ _   _______ ______ _____  
	--|  ____|_   _| | |__   __|  ____|  __ \ 
	--| |__    | | | |    | |  | |__  | |__) |
	--|  __|   | | | |    | |  |  __| |  _  / 
	--| |     _| |_| |____| |  | |____| | \ \ 
	--|_|    |_____|______|_|  |______|_|  \_\
	local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
		if other.isnpc then return "cross"
		elseif other.isdoor then return "slide" -- needed 20250720
		elseif other.iswall then return "slide" -- "touch"
		elseif other.isfloor then return "slide"
		elseif other.isptpf then
			if item.isdown and item.isup then -- prevents ptpf while holding both up and down keys
				item.wasdown = true
				item.wasup = true
			end
			if item.isdown and not item.wasdown then
				return "cross"
			end
			if item.body.vy > 0 then -- actor going down
				local itembottom = item.pos.y + item.collbox.h
				local otherbottom = other.pos.y -- don't add margin here!
				if itembottom <= otherbottom then return "slide" end
			end
			if item.iswalkingnme and item.breed == "C" then
				return "slide"
			end
		elseif other.ismvpf then
			if item.isdown and item.isup then -- prevents pt mvpf while holding both up and down keys
				item.wasdown = true
				item.wasup = true
			end
			if item.isdown and not item.wasdown then
				if other.isptmvpf then return "cross" end -- passthrough moving pf
			end
			if item.body.vy > 0 then -- actor going down
				local itembottom = item.pos.y + item.collbox.h
				local otherbottom = other.pos.y + 1
				if other.body.vy < 0 then -- mvpf going up
					otherbottom += 1 -- margin prevents player from falling when the pf is going up
				end
				if itembottom <= otherbottom then return "slide" end
			end
		elseif other.isspring then return "slide"
		elseif other.isspike then return "slide"
		else return "cross"
		end
	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)
	--  _____ ____  _      _      _____  _____ _____ ____  _   _  _____ 
	-- / ____/ __ \| |    | |    |_   _|/ ____|_   _/ __ \| \ | |/ ____|
	--| |   | |  | | |    | |      | | | (___   | || |  | |  \| | (___  
	--| |   | |  | | |    | |      | |  \___ \  | || |  | | . ` |\___ \ 
	--| |___| |__| | |____| |____ _| |_ ____) |_| || |__| | |\  |____) |
	-- \_____\____/|______|______|_____|_____/|_____\____/|_| \_|_____/ 
	-- COLLISION FROM ANY SIDES
	for i = 1, len do
		local item = collisions[i].item
		local other = collisions[i].other
		local normal = collisions[i].normal
		--
		if other.isvoid then
			if item.isplayer1 then
				item.restart = true -- load lose scene?
			else
				item.currlives = 0
				item.readytoremove = true
			end
		elseif item.isplayer1 and other.isexit then
			g_currlevel += 1
			item.restart = true
		elseif other.isladder then
			item.body.currcoyotetimer = item.body.coyotetimer
			item.isladdercontacts = true
			if item.currlives <= 0 then
				item.readytoremove = true -- only remove actor when 'grounded'
			end
		elseif other.isptpf then
			item.isptpfcontacts = true
		elseif other.isstep then -- CBump stairs effect ;-)
			item.isstepcontacts = true
		elseif other.ismvpf then
			if item.body.vy > 0 then -- actor going down
				local itembottom = item.pos.y + item.collbox.h
				local otherbottom = other.pos.y + 1 -- some margin prevent player from sliding/falling
				if itembottom <= otherbottom then -- actor above mvpf
					item.ismvpfcontacts = true
					item.body.currjumpcount = item.body.jumpcount
					item.body.currcoyotetimer = item.body.coyotetimer
--					item.body.vy = 0 -- don't reset velocity y here because prevents ptpf!
				end
				if item.currlives <= 0 then
					item.readytoremove = true -- only remove actor when 'grounded'
				end
			end
			if item.isleft and not item.isright and other.body.vx < 0 then
				item.body.vx = -item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif item.isright and not item.isleft and other.body.vx < 0 then
				item.body.vx = item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif (item.isleft and item.isright) and other.body.vx < 0 then
				item.body.vx = other.body.vx
			elseif not(item.isleft and item.isright) and other.body.vx < 0 then
				item.body.vx = other.body.vx
			elseif item.isleft and not item.isright and other.body.vx > 0 then
				item.body.vx = -item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif item.isright and not item.isleft and other.body.vx > 0 then
				item.body.vx = item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif (item.isleft and item.isright) and other.body.vx > 0 then
				item.body.vx = other.body.vx
			elseif not (item.isleft and item.isright) and other.body.vx > 0 then
				item.body.vx = other.body.vx
			elseif item.isleft and not item.isright and other.body.vx == 0 then
				item.body.vx = -item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif item.isright and not item.isleft and other.body.vx == 0 then
				item.body.vx = item.body.currspeed*0.9 -- slow down speed on mvpf, you choose!
			elseif (item.isleft and item.isright) and other.body.vx == 0 then
				item.body.vx = 0
			elseif not (item.isleft and item.isright) and other.body.vx == 0 then
				item.body.vx = 0
			end
		elseif other.iswall then
			item.body.currjumpcount = item.body.jumpcount
			item.body.currcoyotetimer = item.body.coyotetimer + 1*8 -- increase timer for fun, magik XXX
			item.iswallcontacts = true
			item.wasonwall = true
--			item.wasup = false -- you can uncomment this line, GAMEPLAY: walls jumping too permissive
		elseif other.isspring then
			item.body.vy = -other.vy -- reset velocity y (don't accumulate gravity)
			item.isspringcontacts = true
			if item.currlives <= 0 then
				item.readytoremove = true -- only remove actor when 'grounded'
			end
		elseif other.isspike then
			item.body.vx = item.body.currspeed*5*-item.flip -- 3, magik XXX
			item.body.vy = -item.body.currupspeed*0.7 -- magik XXX
			if item.isplayer1 then
				item.isdirty = true
				item.damage = 1
			end
			if item.currlives <= 0 then
				item.readytoremove = true -- only remove actor when 'grounded'
			end
			item.wasup = false
		end
		-- COLLISION FROM TOP
		if normal.y == -1 then
			if other.isfloor then
				item.body.vy = 0 -- reset velocity y (don't accumulate gravity)
				item.isfloorcontacts = true
				item.body.currjumpcount = item.body.jumpcount
				item.body.currcoyotetimer = item.body.coyotetimer
				if item.currlives <= 0 then
					item.readytoremove = true -- only remove actor when 'grounded'
				end
			elseif other.isptpf then
				item.body.vy = 0 -- reset velocity y (don't accumulate gravity)
				item.isptpfcontacts = true
				item.body.currjumpcount = item.body.jumpcount
				item.body.currcoyotetimer = item.body.coyotetimer
				if item.currlives <= 0 then
					item.readytoremove = true -- only remove actor when 'grounded'
				end
			elseif other.isnme and item.isplayer1 then -- player1 on top of nme
				other.isdirty = true
				other.damage = 1
				item.body.currjumpcount = item.body.jumpcount
				if item.isup then
					item.body.vy = -ent.body.currupspeed*1.1
				else
					item.body.vy = -item.body.currupspeed*0.7 -- magik XXX, linked to 'stomp'
				end
			elseif other.isnme and item.isnme then -- nme on top of nme
				item.body.currjumpcount = item.body.jumpcount
				if item.isup then
					item.body.vy = -ent.body.currupspeed*1.1
				else
					item.body.vy = -item.body.currupspeed*0.7 -- magik XXX, linked to 'stomp'
				end
				if item.currlives <= 0 then
					item.readytoremove = true -- only remove actor when 'grounded'
				end
			elseif other.isplayer1 and item.isnme then -- nme on top of player1
				other.isdirty = true
				other.damage = 1
				item.wasup = false -- ??? XXX
				item.body.currjumpcount = item.body.jumpcount
				item.body.currinputbuffer = item.body.inputbuffer
				item.body.currcoyotetimer = item.body.coyotetimer
			end
		-- COLLISION FROM BOTTOM
		elseif normal.y == 1 then
			if other.isfloor then -- cancel body gravity when hitting from below (can adjust)
				item.body.vy *= 0.5 -- = 0
			end
		end
		-- COLLISION FROM SIDES, -1 collision from item right, 1 collision from item left
		if normal.x == -1 or normal.x == 1 then
			if item.isplayer1 and other.isfloor then
				item.body.vx = -item.flip
			elseif item.isnme and other.isdoor then
				item.isleft, item.isright = not item.isleft, not item.isright
			elseif item.isnme and other.isfloor then
				if normal.x == -1 and not item.isleftofplayer then
					item.isleft, item.isright = true, false
				elseif normal.x == 1 and item.isleftofplayer then
					item.isleft, item.isright = false, true
				end
			end
		end
		-- PLAYER1
		if item.isplayer1 and other.iscollectible then
			other.isdirty = true
			if other.eid:find("key") then -- add key to inventory
				self.tiny.player1inventory[other.eid:sub(4)] = true -- eid with "key" truncated
--				print("key", other.eid, dt)
			end
		end
	end
	--  _____ _____       __      _______ _________     __
	-- / ____|  __ \     /\ \    / /_   _|__   __\ \   / /
	--| |  __| |__) |   /  \ \  / /  | |    | |   \ \_/ / 
	--| | |_ |  _  /   / /\ \ \/ /   | |    | |    \   /  
	--| |__| | | \ \  / ____ \  /   _| |_   | |     | |   
	-- \_____|_|  \_\/_/    \_\/   |_____|  |_|     |_|   
	-- gravity after collisions because ptpfs may modify actor body vy
	if ent.body.vy < 0 then -- going up
		ent.body.vy += 3*8 * ent.body.currmass -- gravity, magik XXX
	else -- going down
		ent.body.vy += 3*8 * ent.body.currmass -- 3*8, gravity, magik XXX
		if ent.body.vy > 500 then -- 500, cap falling speed
			ent.body.vy = 500
		end
		if ent.wasonmvpf then -- prevent fast fall when going off pf
			ent.body.vy = ent.body.currupspeed*0.25 -- *0.5, ent going down mvpf, magik XXX
			ent.wasonmvpf = false
		elseif ent.wasonwall then -- ent wall sliding
			ent.body.vy = ent.body.currupspeed*0.01 -- *0.25, magik XXX
			ent.wasonwall = false
		end
	end
	--  _____  _    ___     _______ _____ _____  _____ 
	-- |  __ \| |  | \ \   / / ____|_   _/ ____|/ ____|
	-- | |__) | |__| |\ \_/ / (___   | || |    | (___  
	-- |  ___/|  __  | \   / \___ \  | || |     \___ \ 
	-- | |    | |  | |  | |  ____) |_| || |____ ____) |
	-- |_|    |_|  |_|  |_| |_____/|_____\_____|_____/ 
	if ent.body.currinputbuffer > 0 then -- floor input buffer
		ent.body.currinputbuffer -= 1
	end
	if ent.body.currcoyotetimer > 0 then -- coyote time
		ent.body.currcoyotetimer -= 1
	end
	if ent.body.currdashtimer > 0 then -- dash
		ent.body.currdashtimer -= 1
	end
	if ent.body.currdashcooldown > 0 then -- dash cooldown
		ent.body.currdashcooldown -= 1
	end
	-- IS ON STEP
	if ent.isstepcontacts then
--		if ent.isplayer1 then print("isstepcontacts", dt) end
		if ent.isleft and not ent.isright then -- LEFT
			ent.animation.curranim = g_ANIM_RUN_R
			ent.flip = -1
			ent.body.vx = -ent.body.currspeed
			ent.body.vy = -ent.body.currupspeed*0.4 -- 0.5, step climb speed here XXX
		elseif ent.isright and not ent.isleft then -- RIGHT
			ent.animation.curranim = g_ANIM_RUN_R
			ent.flip = 1
			ent.body.vx = ent.body.currspeed
			ent.body.vy = -ent.body.currupspeed*0.4 -- 0.5, step climb speed here XXX
		else
			ent.animation.curranim = g_ANIM_IDLE_R
			ent.body.vx *= 0.75 -- 0.8, 0.9, = 0
			if (-ent.body.vx<>ent.body.vx) < 0.001 then ent.body.vx = 0 end
		end
	-- IS ON FLOOR
	elseif (ent.isfloorcontacts or
			(ent.isfloorcontacts and ent.isladdercontacts)) and
			not ent.isptpfcontacts and
			not ent.ismvpfcontacts and
			not ent.iswallcontacts
			then
--		if ent.isplayer1 then print("isonfloor", dt) end
		if ent.isleft and not ent.isright then -- LEFT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = -1
				if ent.body.currdashtimer > 0 then 
					ent.body.vx -= ent.body.currspeed*ent.body.dashmultiplier
				else
					ent.body.vx = -ent.body.currspeed
				end
			end
		elseif ent.isright and not ent.isleft then -- RIGHT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = 1
				if ent.body.currdashtimer > 0 then 
					ent.body.vx += ent.body.currspeed*ent.body.dashmultiplier
				else
					ent.body.vx = ent.body.currspeed
				end
			end
		else
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_IDLE_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.body.vx *= 0.75 -- 0.8, 0.9, = 0
			end
			if (-ent.body.vx<>ent.body.vx) < 0.001 then ent.body.vx = 0 end
		end
		if ent.body.currinputbuffer > 0 and not ent.isdown and not ent.wasup then -- UP
			if ent.wasbadlyhurt > 0 then
				-- cannot jump
			else
				ent.body.vy = -ent.body.currupspeed
				ent.wasup = true
				ent.body.currjumpcount = ent.body.jumpcount
				ent.body.currinputbuffer = 0 -- prevents double jump when releasing up key
			end
--		elseif ent.isdown and not ent.isup then -- DOWN
		end
	-- IS ON LADDER
	elseif not ent.isfloorcontacts and
			ent.isladdercontacts and
			not ent.isptpfcontacts and
			not ent.ismvpfcontacts and
			not ent.iswallcontacts
			then
--		if ent.isplayer1 then print("isladdercontacts", dt) end
		if ent.isflyingnme then return end -- flying nmes don't collide with ladders
		if ent.isleft and not ent.isright then -- LEFT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = -1
				ent.body.vx = -ent.body.currspeed*0.5
			end
		elseif ent.isright and not ent.isleft then -- RIGHT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = 1
				ent.body.vx = ent.body.currspeed*0.5
			end
		else
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_IDLE_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.body.vx *= 0.75 -- 0.8, 0.9, = 0
			end
			if (-ent.body.vx<>ent.body.vx) < 0.001 then ent.body.vx = 0 end
		end
		if ent.isup and not ent.isdown then -- UP
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 or ent.currlives <= 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 or ent.currlives <= 0 then
				-- cannot move
			else
				ent.body.vy = -ent.body.currupspeed*0.2 -- magik XXX
				ent.wasup = false -- allows jumping up off ladder
			end
		elseif ent.isdown and not ent.isup then -- DOWN
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 or ent.currlives <= 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 or ent.currlives <= 0 then
				-- cannot move
			else
				ent.body.vy = ent.body.currupspeed*0.2 -- magik XXX
			end
		else
			if ent.wasbadlyhurt > 0 or ent.currlives <= 0 then
				ent.body.vy = ent.body.currupspeed -- fall off ladder
			else
				ent.body.vy = 0
			end
		end
		ent.wasonladder = true
	-- IS ON PTPF
	elseif not ent.isfloorcontacts and
			not ent.isladdercontacts and
			ent.isptpfcontacts and
			not ent.ismvpfcontacts and
			not ent.iswallcontacts
			then
--		if ent.isplayer1 then print("isptpfcontacts", dt) end
		if ent.isleft and not ent.isright then -- LEFT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = -1
				if ent.body.currdashtimer > 0 then 
					ent.body.vx -= ent.body.currspeed*ent.body.dashmultiplier
				else
					ent.body.vx = -ent.body.currspeed
				end
			end
		elseif ent.isright and not ent.isleft then -- RIGHT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = 1
				if ent.body.currdashtimer > 0 then 
					ent.body.vx += ent.body.currspeed*ent.body.dashmultiplier
				else
					ent.body.vx = ent.body.currspeed
				end
			end
		else
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_IDLE_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.body.vx *= 0.75 -- 0.8, 0.9, = 0
			end
			if (-ent.body.vx<>ent.body.vx) < 0.001 then ent.body.vx = 0 end
		end
		if ent.body.currinputbuffer > 0 and not ent.isdown and not ent.wasup then -- UP
			if ent.wasbadlyhurt > 0 then
				-- cannot jump
			else
				ent.body.vy = -ent.body.currupspeed
				ent.wasup = true
				ent.body.currinputbuffer = 0 -- prevents double jump when releasing up key
			end
		elseif ent.isdown and not ent.isup and not ent.wasdown then -- DOWN
			if ent.wasbadlyhurt > 0 then
				-- cannot jump down
			else
				ent.body.vy = ent.body.currupspeed*0.1
				ent.wasdown = true
			end
		end
	-- IS ON MVPF
	elseif not ent.isfloorcontacts and
			not ent.isladdercontacts and
			not ent.isptpfcontacts and
			ent.ismvpfcontacts and
			not ent.iswallcontacts
			then -- also controlled in collisions
--		if ent.isplayer1 then print("ismvpfcontacts", dt) end
		if ent.isleft and not ent.isright then -- LEFT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = -1
			end
		elseif ent.isright and not ent.isleft then -- RIGHT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_RUN_R
			end
			if ent.wasbadlyhurt > 0 then
				ent.body.vx *= 0.1 -- cancel move! magik XXX
			else
				ent.flip = 1
			end
		else
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_IDLE_R
			end
		end
		if ent.body.currinputbuffer > 0 and not ent.isdown and not ent.wasup then -- UP
			if ent.wasbadlyhurt > 0 then
				-- cannot jump
			else
				ent.body.vy = -ent.body.currupspeed
				ent.wasup = true -- if set to true cannot jump!
				ent.body.currinputbuffer = 0 -- prevents double jump when releasing up key
			end
		elseif ent.isdown and not ent.isup and not ent.wasdown then -- DOWN
			if ent.wasbadlyhurt > 0 then
				-- cannot jump down
			else
				ent.body.vy = ent.body.currupspeed*0.1
				ent.wasdown = true
			end
		end
		ent.wasonmvpf = true
	-- IS ON WALL
	elseif not ent.isfloorcontacts and
			not ent.isladdercontacts and
			not ent.isptpfcontacts and
			not ent.ismvpfcontacts and
			ent.iswallcontacts
			then
--		if ent.isplayer1 then print("isonwall", dt) end
		if ent.isleft and not ent.isright then -- LEFT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_WALL_IDLE_R
			end
			ent.flip = -1
			ent.body.vx = -ent.body.currspeed
		elseif ent.isright and not ent.isleft then -- RIGHT
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_WALL_IDLE_R
			end
			ent.flip = 1
			ent.body.vx = ent.body.currspeed
		else
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_WALL_DOWN_R
			end
			ent.body.vy = ent.body.currupspeed*0.25
		end
		if ent.isup and not ent.isdown then -- UP
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_WALL_UP_R
			end
			ent.body.vy = -ent.body.currupspeed*0.01
		elseif ent.isdown and not ent.isup then -- DOWN
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			else ent.animation.curranim = g_ANIM_WALL_DOWN_R
			end
			ent.body.vy = ent.body.currupspeed*0.05
		end
		ent.wasonwall = true
	-- IS ON SPRING
	elseif ent.isspringcontacts then
--		if ent.isplayer1 then print("isspringcontacts", dt) end
	-- IS IN THE AIR
	else
--		if ent.isplayer1 then print("isintheair", dt) end
		-- anims
		if ent.isplayer1 and ent.currlives > 0 then
			if ent.washurt > 0 or ent.wasbadlyhurt > 0 then -- animations in ent system
			elseif ent.body.vy < 0 then ent.animation.curranim = g_ANIM_JUMPUP_R -- going UP
			else ent.animation.curranim = g_ANIM_JUMPDOWN_R -- going DOWN
			end
		end
		-- movements
		if ent.isleft and not ent.isright then -- LEFT
			ent.flip = -1
			if ent.body.currdashtimer > 0 then 
				ent.body.vx -= ent.body.currspeed*ent.body.dashmultiplier
			else
				if ent.wasonwall then -- vx acceleration
					ent.body.vx = -ent.body.currspeed*1.5 -- 2, magik XXX
				else
					ent.body.vx = -ent.body.currspeed
				end
			end
		elseif ent.isright and not ent.isleft then -- RIGHT
			ent.flip = 1
			if ent.body.currdashtimer > 0 then 
				ent.body.vx += ent.body.currspeed*ent.body.dashmultiplier
			else
				if ent.wasonwall then -- vx acceleration
					ent.body.vx = ent.body.currspeed*1.5 -- 2, magik XXX
				else
					ent.body.vx = ent.body.currspeed
				end
			end
		else
			ent.body.vx *= 0.9 -- 0.99, [0, 1], GAMEPLAY, some air friction, you choose!
		end
		if ent.isup and not ent.isdown and not ent.wasup
			and ent.body.currcoyotetimer > 0 and ent.body.currjumpcount > 0 then -- UP
			ent.wasup = true -- you can comment this line, GAMEPLAY WALLS JUMPING
			ent.body.currcoyotetimer = 0
			ent.body.currjumpcount -= 1
			if ent.body.vy >= 0 then -- prevent double jump when fast pressing up!
				ent.body.vy = -ent.body.currupspeed
			end
			if ent.wasonladder then -- attenuate ent jumping off on top of ladder
				ent.body.vy *= 0.5
				ent.wasonladder = false
			end
		elseif ent.isdown and not ent.isup and not ent.wasdown then -- DOWN
			ent.wasdown = true -- ok
		end
	end
	-- 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
end

What it does:

  • runs only once when it is called
  • affects only entities which have a spritelayer variable (id) and a sprite variable (id)
  • when an Entity is added to tiny-ecs World, the System adds the Entity to its Sprite layer
  • when an Entity is removed from tiny-ecs World, the System removes the Entity from its Sprite layer unless it has a ispersistent id

Next?

That was a big one, the last systems will be much smaller 😊 Systems are fairly straight forward and fairly short for what they achieve.

We have covered the first set of systems, the next System is the heart of the game: the collision System.


Prev.: Tuto tiny-ecs 2d platformer Part 9 Systems
Next: Tuto tiny-ecs 2d platformer Part 11 Systems 3


Tutorial - tiny-ecs 2d platformer