Tuto tiny-ecs 2d platformer Part 10 Collision System

From GiderosMobile
Revision as of 14:46, 6 November 2025 by MoKaLux (talk | contribs) (→‎Some code comments)
(diff) ← Older revision | Latest revision (diff) | Newer revision → (diff)

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 parse 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). Some other entities are moved using their own systems
  • cBump uses a collisionfilter to process collisions between entities in the physics world
  • then cBump resolves the Entity collision ("touch", "cross", "slide" or "bounce") using the 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)

We will explain some more after we create the file.

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

Code comments

  • runs once on init and every game loop (process)
  • affects only entities which have a collision box id (collbox) and a body id (we exclude some other entities)
  • when an enemy Entity is off screen we don't process it to save some processing
  • the collisionfilter tells cBump how entities interact with each other when they collide: "touch", "cross", "slide", "bounce"
  • then we let cBump resolve the collision between the entities:
	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)
  • once the collisions are resolved, we can instruct the Entity to perform certain tasks. For example, we can reset the Entity velocity, change the Entity state (isonfloor, isonwall, ...), ...
  • cBump allows us to detect collisions from the top, left, right, bottom or from any directions
  • now we can apply the "forces": the gravity and the entities movement (up, down, left, right)
  • finally, depending on the state the Entity is in (isstepcontacts, isfloorcontacts, isladdercontacts, ...) we process the input (keyboard for the player1 and AI for the enemies) to control our player
  • and we update the sprite position on the screen

Next?

That was a big one but it needed to be done. The last systems we will add are for the collectibles, doors, moving platforms and a couple more 😉. Don't worry they should be pretty short!


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


Tutorial - tiny-ecs 2d platformer