Difference between revisions of "Tuto tiny-ecs beatemup Part 11 Systems 3"

From GiderosMobile
(wip)
 
 
(5 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
__TOC__
 
__TOC__
  
== The Systems 2 ==
+
== The Systems 3 ==
We continue adding systems to our game.
+
A couple more systems to add and we are done. We also add the '''Debug Systems''' to help us visualize all the boxes (hitbox, hurtbox, collsion box, ...).
A System is a wrapper around function callbacks for manipulating Entities. Systems are implemented as tables that contain at least one method; an update function that takes parameters like so:
 
 
'''function system:update(dt)'''
 
 
There are also a few other optional callbacks:
 
*'''function system:filter(entity)''' Returns true if this System should include this Entity, otherwise should return false. If this isn't specified, no Entities are included in the System.
 
*'''function system:onAdd(entity)''' Called when an Entity is added to the System.
 
*'''function system:onRemove(entity)''' Called when an Entity is removed from the System.
 
*'''function system:onModify(dt)''' Called when the System is modified by adding or removing Entities from the System.
 
*'''function system:onAddToWorld(world)''' Called when the System is added to the World, before any entities are added to the system.
 
*'''function system:onRemoveFromWorld(world)''' Called when the System is removed from the world, after all Entities are removed from the System.
 
*'''function system:preWrap(dt)''' Called on each system before update is called on any system.
 
*'''function system:postWrap(dt)''' Called on each system in reverse order after update is called on each system.
 
 
'''Please see [[Tiny-ecs#System_functions]] for more information'''
 
  
== sAnimation.lua ==
+
== sDestructibleObjects.lua ==
Time to animate our actors. Please create a file "'''sAnimation.lua'''" in the '''"_S"''' folder and the code:
+
Please create a file "'''sDestructibleObjects.lua'''" in the '''"_S"''' folder and the code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SAnimation = Core.class()
+
SDestructibleObjects = Core.class()
  
function SAnimation:init(xtiny)
+
local random, cos, sin = math.random, math.cos, math.sin
xtiny.processingSystem(self) -- called once on init and every frames
+
local insert = table.insert
self.sndstepgrass = Sound.new("audio/sfx/footstep/Grass02.wav")
+
 
self.channel = self.sndstepgrass:play(0, false, true)
+
function SDestructibleObjects:init(xtiny, xbworld) -- tiny function
 +
self.tiny = xtiny -- ref so we can remove entities from tiny system
 +
self.tiny.processingSystem(self) -- called once on init and every update
 +
self.bworld = xbworld
 +
-- sfx
 +
self.snd = Sound.new("audio/sfx/footstep/Forest02.wav")
 +
self.channel = self.snd:play(0, false, true)
 
end
 
end
  
function SAnimation:filter(ent) -- tiny function
+
function SDestructibleObjects:filter(ent) -- tiny function
return ent.animation
+
return ent.isdestructibleobject
 
end
 
end
  
function SAnimation:onAdd(ent) -- tiny function
+
function SDestructibleObjects:onAdd(ent) -- tiny function
 
end
 
end
  
function SAnimation:onRemove(ent) -- tiny function
+
function SDestructibleObjects:onRemove(ent) -- tiny function
ent.animation = nil -- free some memory?
+
-- spawn a random collectible: ECollectible:init(xspritelayer, xpos)
 +
local function fun()
 +
local el = ECollectible.new(ent.spritelayer, ent.pos+vector(ent.collbox.w/4, -1*ent.collbox.h))
 +
self.tiny.tworld:addEntity(el)
 +
self.bworld:add(el, el.pos.x, el.pos.y, el.collbox.w, el.collbox.h)
 +
Core.yield(1)
 +
end
 +
Core.asyncCall(fun)
 +
self.bworld:remove(ent) -- remove collision box from cbump world here!
 
end
 
end
  
local checkanim
+
function SDestructibleObjects:process(ent, dt) -- tiny function
function SAnimation:process(ent, dt) -- tiny function
+
local function EffectExplode(s, scale, pos, r, speed, texture)
-- a little boost?
+
local p = Particles.new()
local anim = ent.animation
+
p:setPosition(pos)
 
+
p:setTexture(texture)
-- checkanim = anim.curranim -- if you are sure all animations are set else use below ternary operator code
+
p:setScale(scale)
-- luau ternary operator (no end at the end), it's a 1 liner and seems fast?
+
s:addChild(p)
checkanim = if anim.anims[ent.animation.curranim] then anim.curranim else g_ANIM_DEFAULT
+
local parts = {}
-- print(#anim.anims[checkanim])
+
for i = 1, 6 do -- 8
 
+
local a = random()*6.3
if not ent.doanimate then return end
+
local dx, dy = cos(a), sin(a)
 
+
local sr = random()*r
anim.animtimer -= dt
+
local px, py = dx*sr, dy*sr
if anim.animtimer < 0 then
+
local ss = (random()+0.5) * (speed or 1)
anim.frame += 1
+
insert(parts,
anim.animtimer = anim.animspeed
+
{
if checkanim == g_ANIM_DEFAULT then
+
x = px, y = py,
if anim.frame > #anim.anims[checkanim] then
+
speedX = dx * ss,
anim.frame = 1
+
speedY = dy * ss,
end
+
speedAngular = random()*4 - 2,
elseif checkanim == g_ANIM_LOSE1_R or checkanim == g_ANIM_STANDUP_R then
+
decayAlpha = random()*0.04 + 0.95,
if anim.frame >= #anim.anims[checkanim] then
+
ttl = 32, -- 500
anim.frame = #anim.anims[checkanim]
+
size = random()*10 + 20,
end
+
}
elseif checkanim == g_ANIM_PUNCH_ATTACK1_R then
+
)
ent.headhitbox = ent.headhitboxattack1
+
end
if #anim.anims[checkanim] == 1 then -- 1 frame animation
+
p:addParticles(parts)
anim.frame = 1
+
Core.yield(1)
ent.headhitboxattack1.isactive = true
+
p:removeFromParent()
ent.isactionpunch1 = false
+
end
else -- multi frames animation
+
-- hurt fx
if anim.frame > #anim.anims[checkanim] then
+
if ent.washurt and ent.washurt > 0 then
-- anim.frame = 1
+
ent.washurt -= 1
anim.frame = #anim.anims[checkanim]
+
if ent.washurt < ent.recovertimer/2 then
ent.headhitboxattack1.isactive = false
+
if ent.hitfx then ent.hitfx:setVisible(false) end
ent.isactionpunch1 = false
+
end
elseif anim.frame > ent.headhitbox.hitendframe then
+
if ent.washurt <= 0 then
ent.headhitboxattack1.isactive = false
+
ent.sprite:setColorTransform(1, 1, 1, 1)
elseif anim.frame >= ent.headhitbox.hitstartframe then
+
end
ent.headhitboxattack1.isactive = true
+
end
end
+
if ent.isdirty then -- hit
end
+
self.channel = self.snd:play()
elseif checkanim == g_ANIM_PUNCH_ATTACK2_R then
+
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
ent.headhitbox = ent.headhitboxattack2
+
ent.hitfx:setVisible(true)
if anim.frame > #anim.anims[checkanim] then
+
ent.hitfx:setPosition(ent.pos + vector(ent.headhurtbox.x+4, ent.headhurtbox.y))
-- anim.frame = 1
+
ent.spritelayer:addChild(ent.hitfx)
anim.frame = #anim.anims[checkanim]
+
ent.currhealth -= ent.damage
ent.headhitboxattack2.isactive = false
+
ent.washurt = ent.recovertimer -- timer for a flash effect
ent.isactionpunch2 = false
+
ent.sprite:setColorTransform(1, 1, 1, 3) -- a flash effect
elseif anim.frame > ent.headhitbox.hitendframe then
+
ent.isdirty = false
ent.headhitboxattack2.isactive = false
+
if ent.currhealth <= 0 then
elseif anim.frame >= ent.headhitbox.hitstartframe then
+
--EffectExplode(s, scale, pos, r, speed, texture)
ent.headhitboxattack2.isactive = true
+
Core.asyncCall(EffectExplode, ent.spritelayer, 2,
end
+
ent.pos+vector(ent.collbox.w/2, -ent.h/2), 4, 2,
elseif checkanim == g_ANIM_KICK_ATTACK1_R then
+
Texture.new("gfx/fx/fxBarrel_02_0011.png"))
ent.spinehitbox = ent.spinehitboxattack1
+
ent.spritelayer:removeChild(ent.hitfx)
if #anim.anims[checkanim] == 1 then -- 1 frame animation
+
self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
anim.frame = 1
+
self.tiny.numberofdestructibleobjects -= 1
ent.spinehitboxattack1.isactive = true
 
ent.isactionkick1 = false
 
else -- multi frames animation
 
if anim.frame > #anim.anims[checkanim] then
 
-- anim.frame = 1
 
anim.frame = #anim.anims[checkanim]
 
ent.spinehitboxattack1.isactive = false
 
ent.isactionkick1 = false
 
elseif anim.frame > ent.spinehitbox.hitendframe then
 
ent.spinehitboxattack1.isactive = false
 
elseif anim.frame >= ent.spinehitbox.hitstartframe then
 
ent.spinehitboxattack1.isactive = true
 
end
 
end
 
elseif checkanim == g_ANIM_KICK_ATTACK2_R then
 
ent.spinehitbox = ent.spinehitboxattack2
 
if anim.frame > #anim.anims[checkanim] then
 
-- anim.frame = 1
 
anim.frame = #anim.anims[checkanim]
 
ent.spinehitboxattack2.isactive = false
 
ent.isactionkick2 = false
 
elseif anim.frame > ent.spinehitbox.hitendframe then
 
ent.spinehitboxattack2.isactive = false
 
elseif anim.frame >= ent.spinehitbox.hitstartframe then
 
ent.spinehitboxattack2.isactive = true
 
end
 
elseif checkanim == g_ANIM_JUMP1_R then -- only jump, no attacks
 
if anim.frame > #anim.anims[checkanim] then
 
anim.frame = #anim.anims[checkanim]
 
end
 
elseif checkanim == g_ANIM_PUNCHJUMP_ATTACK1_R then
 
ent.headhitbox = ent.headhitboxjattack1
 
if anim.frame > #anim.anims[checkanim] then
 
anim.frame = #anim.anims[checkanim]
 
ent.headhitboxjattack1.isactive = false
 
-- ent.isactionjumppunch1 = false -- don't set to false here otherwise BUGGGZZZZ!!!
 
else
 
ent.headhitboxjattack1.isactive = true
 
end
 
elseif checkanim == g_ANIM_KICKJUMP_ATTACK1_R then
 
ent.spinehitbox = ent.spinehitboxjattack1
 
if anim.frame > #anim.anims[checkanim] then
 
anim.frame = #anim.anims[checkanim]
 
ent.spinehitboxjattack1.isactive = false
 
-- ent.isactionjumpkick1 = false -- don't set to false here otherwise BUGGGZZZZ!!!
 
else
 
ent.spinehitboxjattack1.isactive = true
 
end
 
else
 
-- player1 steps sound fx
 
if ent.isplayer1 and
 
(anim.curranim == g_ANIM_WALK_R or anim.curranim == g_ANIM_RUN_R) and
 
(anim.frame == 4 or anim.frame == 9) then
 
self.channel = self.sndstepgrass:play()
 
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
 
end
 
-- loop animations
 
if anim.frame > #anim.anims[checkanim] then
 
anim.frame = 1
 
end
 
 
end
 
end
anim.bmp:setTextureRegion(anim.anims[checkanim][anim.frame])
 
 
end
 
end
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
What the System does:
+
The System removes a breakable object when destroyed and spawn a collectible in the '''onRemove''' function:
 
* it runs every frame
 
* it runs every frame
* it affects only entities with an ''animation'' id
+
* it affects only entities with an ''isdestructibleobject'' id
* it checks which animation to play
+
* on hurt adds an hit fx
* it decreases the ''animtimer''
+
* on destroyed adds particles
* and increases the current frame in the animation by 1
+
* '''onRemove''' spawns a collectible
* if an attack animation is playing it activates/deactivates the corresponding actor '''hit boxes'''
 
* it eventually plays a sound (player1 steps sound fx)
 
* it finally sets the current frame in the animation as the actor Bitmap
 
  
== sDynamicBodies.lua ==
+
== sCollectible.lua ==
"'''sDynamicBodies.lua'''" in the '''"_S"''' folder. The code:
+
"'''sCollectible.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SDynamicBodies = Core.class()
+
SCollectible = Core.class()
  
 
local random = math.random
 
local random = math.random
  
function SDynamicBodies:init(xtiny, xmapdef) -- tiny function
+
function SCollectible:init(xtiny, xbump, xplayer1) -- tiny function
self.tiny = xtiny -- to access self.tiny variables
+
self.tiny = xtiny -- make a class ref
 
self.tiny.processingSystem(self) -- called once on init and every update
 
self.tiny.processingSystem(self) -- called once on init and every update
self.mapdef = xmapdef
+
self.bworld = xbump
 +
self.player1 = xplayer1
 +
-- sfx
 +
self.snd = Sound.new("audio/sfx/sfx_coin_double1.wav")
 +
self.channel = self.snd:play(0, false, true)
 
end
 
end
  
function SDynamicBodies:filter(ent) -- tiny function
+
function SCollectible:filter(ent) -- tiny function
return ent.body -- only actors with body component
+
return ent.iscollectible
 
end
 
end
  
function SDynamicBodies:onAdd(ent) -- tiny function
+
function SCollectible:onAdd(ent) -- tiny function
 
end
 
end
  
function SDynamicBodies:onRemove(ent) -- tiny function
+
function SCollectible:onRemove(ent) -- tiny function
 +
self.bworld:remove(ent) -- remove collision box from cbump world here!
 
end
 
end
  
local randomdir = 0 -- for nmes
+
function SCollectible:process(ent, dt) -- tiny function
function SDynamicBodies:process(ent, dt) -- tiny function
+
if ent.isdirty then -- hit
-- is dead or hurt?
+
local function map(v, minSrc, maxSrc, minDst, maxDst, clampValue)
if (ent.washurt and ent.washurt > 0) or (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) or ent.currlives <= 0 then
+
local newV = (v - minSrc) / (maxSrc - minSrc) * (maxDst - minDst) + minDst
ent.body.vx = 0
+
return not clampValue and newV or clamp(newV, minDst >< maxDst, minDst <> maxDst)
ent.body.vy = 0
 
return
 
-- TO REVISIT
 
end
 
-- movements
 
if ent.isleft and not ent.isright and ent.pos.x > self.mapdef.l then -- LEFT
 
ent.animation.curranim = g_ANIM_WALK_R
 
ent.body.vx = -ent.body.currspeed*dt
 
ent.flip = -1
 
elseif ent.isright and not ent.isleft and ent.pos.x < self.mapdef.r - ent.w*0.5 then -- RIGHT
 
ent.animation.curranim = g_ANIM_WALK_R
 
ent.body.vx = ent.body.currspeed*dt
 
ent.flip = 1
 
else -- IDLE
 
ent.animation.curranim = g_ANIM_IDLE_R
 
ent.body.vx = 0
 
end
 
if ent.isup and not ent.isdown and ent.body.isonfloor and ent.pos.y > self.mapdef.t then -- UP
 
ent.animation.curranim = g_ANIM_WALK_R
 
ent.body.vy = -ent.body.currspeed*0.015*dt -- 0.01, you choose
 
elseif ent.isdown and not ent.isup and ent.body.isonfloor and ent.pos.y < self.mapdef.b then -- DOWN
 
ent.animation.curranim = g_ANIM_WALK_R
 
ent.body.vy = ent.body.currspeed*0.015*dt -- 0.01, you choose
 
else
 
if ent.body.isonfloor then
 
ent.body.vy = 0
 
 
end
 
end
end
+
self.channel = self.snd:play()
-- actions
+
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
if ent.body.isonfloor then -- GROUND
+
-- ent.hitfx:setVisible(true)
if ent.isactionpunch1 then
+
-- ent.hitfx:setPosition(ent.pos.x+ent.headhurtbox.x, ent.y+ent.headhurtbox.y)
ent.animation.curranim = g_ANIM_PUNCH_ATTACK1_R
+
-- ent.spritelayer:addChild(ent.hitfx)
ent.body.vx = 0 -- *= 0.1*dt, you choose
+
-- ent.currhealth -= ent.damage
ent.body.vy = 0 -- *= 0.1*dt, you choose
+
-- ent.washurt = ent.recovertimer -- timer for a flash effect
elseif ent.isactionpunch2 then
+
if random(100) > 50 then
ent.animation.curranim = g_ANIM_PUNCH_ATTACK2_R
+
if self.player1.currjumps < 0 then self.player1.currjumps = 0 end
ent.body.vx = 0 -- *= 0.1*dt, you choose
+
self.player1.currjumps += 3
ent.body.vy = 0 -- *= 0.1*dt, you choose
+
self.tiny.hudcurrjumps:setText("JUMPS: "..self.player1.currjumps)
elseif ent.isactionkick1 then
+
else
ent.animation.curranim = g_ANIM_KICK_ATTACK1_R
+
self.player1.currhealth += 1
ent.body.vx = 0 -- *= 0.1*dt, you choose
+
-- hud
ent.body.vy = 0 -- *= 0.1*dt, you choose
+
local hudhealthwidth = map(self.player1.currhealth, 0, self.player1.totalhealth, 0, 100)
elseif ent.isactionkick2 then
+
self.tiny.hudhealth:setWidth(hudhealthwidth)
ent.animation.curranim = g_ANIM_KICK_ATTACK2_R
+
if self.player1.currhealth < self.player1.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000)
ent.body.vx = 0 -- *= 0.1*dt, you choose
+
elseif self.player1.currhealth < self.player1.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500)
ent.body.vy = 0 -- *= 0.1*dt, you choose
+
else self.tiny.hudhealth:setColor(0x00ff00)
end
 
else -- AIR
 
if ent.isactionpunch1 then
 
if ent.isplayer1 and ent.currjumps > 0 then
 
ent.isactionjumppunch1 = true
 
end
 
elseif ent.isactionkick1 then
 
if ent.isplayer1 and ent.currjumps > 0 then
 
ent.isactionjumpkick1 = true
 
 
end
 
end
 
end
 
end
if ent.isactionjump1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then -- JUMP ONLY
+
ent.sprite:setColorTransform(0, 2, 0, 3) -- the flash effect (a bright color)
ent.animation.curranim = g_ANIM_JUMP1_R
+
ent.isdirty = false
if ent.isplayer1 then ent.body.vx *= 2 end -- 3, acceleration, you choose
+
--[[
ent.body.currjumpspeed = ent.body.jumpspeed*1.1 -- higher jump
+
if ent.currhealth <= 0 then
if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
+
ent.hitfx:setColorTransform(3, 0, 0, random(1, 3)/10)
if ent.pos.y < ent.positionystart - ent.h*0.5 then -- higher apex, you choose
+
ent.hitfx:setY(ent.hitfx:getY()+ent.h/1.1) -- magik XXX
-- ent.body.vx = ent.body.currspeed*dt*4*ent.flip -- acceleration? you choose
+
ent.hitfx:setRotation(random(360))
ent.body.vy += ent.body.currjumpspeed*1.2 -- falling
+
ent.hitfx:setScale(random(5, 10)/10)
ent.body.isgoingup = false
+
ent.bgfxlayer:addChild(ent.hitfx)
end
+
self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
if not ent.body.isgoingup and ent.pos.y >= ent.positionystart then -- grounded
+
-- self.tiny.numberofnmes -= 1
ent.body.vy = 0
 
-- ent.pos.y = ent.positionystart
 
ent.pos = vector(ent.pos.x, ent.positionystart)
 
ent.body.isonfloor = true
 
ent.isactionjump1 = false -- sometimes bug! XXX
 
end
 
 
end
 
end
if ent.isactionjumppunch1 then -- JUMP PUNCH
+
]]
ent.animation.curranim = g_ANIM_PUNCHJUMP_ATTACK1_R
+
self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
+
end
ent.body.currjumpspeed = ent.body.jumpspeed
+
end
if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
+
</syntaxhighlight>
if ent.pos.y < ent.positionystart - ent.h*0.45 then -- apex, you choose
+
 
-- ent.body.vx = ent.body.currspeed*dt*3*ent.flip -- acceleration? you choose
+
This System spawns a collectible:
ent.body.vy += ent.body.currjumpspeed*1.2 -- falling
+
* runs once on init and '''every game loop''' (''process'')
ent.body.isgoingup = false
+
* there are two kind of collectibles: health and jump attacks (updated in the HUD)
 +
 
 +
'''There are quite a bit of commented code you can delete as this Entity is immediately removed'''
 +
 
 +
== sSpritesSorting.lua ==
 +
"'''sSpritesSorting.lua'''" in the '''"_S"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
SSpritesSorting = Core.class()
 +
 
 +
function SSpritesSorting:init(xtiny) -- tiny function
 +
xtiny.processingSystem(self) -- called once on init and every update
 +
self.spriteslist = xtiny.spriteslist
 +
end
 +
 
 +
function SSpritesSorting:filter(ent) -- tiny function
 +
return ent.sprite
 +
end
 +
 
 +
function SSpritesSorting:onAdd(ent) -- tiny function
 +
-- print("SSpritesSorting added", ent)
 +
self.spriteslist[ent] = true
 +
end
 +
 
 +
function SSpritesSorting:onRemove(ent) -- tiny function
 +
-- print("SSpritesSorting removed", ent)
 +
self.spriteslist[ent] = nil
 +
end
 +
 
 +
local p1rangetoofar = myappwidth*0.5 -- save some CPU
 +
function SSpritesSorting:process(ent, dt) -- tiny function
 +
local function fun()
 +
for k, _ in pairs(self.spriteslist) do
 +
if ent.currlives <= 0 or k.currlives <= 0 then -- don't sort if dead
 +
return
 
end
 
end
if not ent.body.isgoingup and ent.pos.y >= ent.positionystart then -- grounded
+
if k.isplayer1 then -- don't sort out of range actors to save frames
ent.body.vy = 0
+
if -(k.pos.x-ent.pos.x)<>(k.pos.x-ent.pos.x) > p1rangetoofar then
-- ent.pos.y = ent.positionystart
+
return
ent.pos = vector(ent.pos.x, ent.positionystart)
 
if ent.isnme then
 
randomdir = random(100)
 
if randomdir < 50 then ent.isleft = false ent.isright = true
 
else ent.isleft = true ent.isright = false
 
end
 
 
end
 
end
if ent.isplayer1 then
 
ent.currjumps -= 1
 
if ent.currjumps < 0 then ent.currjumps = 0 end
 
self.tiny.hudcurrjumps:setText("JUMPS: "..ent.currjumps)
 
end
 
ent.body.isonfloor = true
 
ent.isactionjump1 = false
 
ent.isactionpunch1 = false -- TEST
 
ent.isactionjumppunch1 = false -- sometimes bug! XXX
 
 
end
 
end
elseif ent.isactionjumpkick1 then -- JUMP KICK
+
if not ent.body.isonfloor then
ent.animation.curranim = g_ANIM_KICKJUMP_ATTACK1_R
+
if ent.positionystart < k.positionystart and -- ent is behind
if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
+
ent.spritelayer:getChildIndex(ent.sprite) > k.spritelayer:getChildIndex(k.sprite) then -- sprite is in front
ent.body.currjumpspeed = ent.body.jumpspeed
+
ent.spritelayer:swapChildren(ent.sprite, k.sprite)
if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
 
if ent.pos.y < ent.positionystart - ent.h*0.5 then -- apex, you choose
 
-- ent.body.vx = ent.body.currspeed*dt*3*ent.flip -- acceleration? you choose
 
ent.body.vy += ent.body.currjumpspeed*1.25 -- falling
 
ent.body.isgoingup = false
 
end
 
if not ent.body.isgoingup and ent.pos.y >= ent.positionystart then -- grounded
 
ent.body.vy = 0
 
-- ent.pos.y = ent.positionystart
 
ent.pos = vector(ent.pos.x, ent.positionystart)
 
if ent.isnme then
 
randomdir = random(100)
 
if randomdir < 50 then ent.isleft = false ent.isright = true
 
else ent.isleft = true ent.isright = false
 
end
 
 
end
 
end
if ent.isplayer1 then
+
else
ent.currjumps -= 1
+
if ent.pos.y < k.pos.y and -- ent is behind
if ent.currjumps < 0 then ent.currjumps = 0 end
+
ent.spritelayer:getChildIndex(ent.sprite) > k.spritelayer:getChildIndex(k.sprite) then -- sprite is in front
self.tiny.hudcurrjumps:setText("JUMPS: "..ent.currjumps)
+
ent.spritelayer:swapChildren(ent.sprite, k.sprite)
 
end
 
end
ent.body.isonfloor = true
 
ent.isactionjump1 = false
 
ent.isactionkick1 = false -- TEST
 
ent.isactionjumpkick1 = false -- sometimes bug!
 
 
end
 
end
 
end
 
end
 +
Core.yield(0.5)
 
end
 
end
-- catches the sometimes bug!
+
Core.asyncCall(fun) -- profiler seems to be faster without asyncCall (because of pairs traversing?)
if not ent.body.isonfloor and ent.body.vy == 0 then
 
print("bug", dt)
 
ent.body.vy += ent.body.currjumpspeed*16 -- makes the actor touch the ground
 
end
 
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This System is reponsible for moving the actors:
+
Finally this System sorts the actors on the y axis:
 
* runs once on init and '''every game loop''' (''process'')
 
* runs once on init and '''every game loop''' (''process'')
* in ''init'' we add the map definition to constraint the actors movement
+
* there is a distinction between the actor being on floor and jumping
* if an actor is hurt we stop its movement
+
 
* if an actor is within the map definition we move it (player with keyboard, enemies with AI)
+
== '''DEBUG SYSTEMS''' ==
* then there are the attacks: ground attacks and jump attacks
+
It would be difficult to accurately position the hit and hurt boxes, the collision boxes, ... without visual cues.
* finally sometimes an actor doesn't land after a jump attack so we force it to do so
 
  
== sHitboxHurtboxCollision.lua ==
+
Debug systems help us with the above. Fortunately they are small and quite simple.
"'''sHitboxHurtboxCollision.lua'''" in the '''"_S"''' folder. The code:
+
 
 +
=== sDebugCollision.lua ===
 +
"'''sDebugCollision.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SHitboxHurtboxCollision = Core.class()
+
SDebugCollision = Core.class()
  
function SHitboxHurtboxCollision:init(xtiny) -- tiny function
+
function SDebugCollision:init(xtiny) -- tiny function
 
xtiny.processingSystem(self) -- called once on init and every update
 
xtiny.processingSystem(self) -- called once on init and every update
self.spriteslist = xtiny.spriteslist
 
 
end
 
end
  
function SHitboxHurtboxCollision:filter(ent) -- tiny function
+
function SDebugCollision:filter(ent) -- tiny function
return ent.headhitbox or ent.spinehitbox or ent.headhurtbox or ent.spinehurtbox
+
return ent.collbox
 +
end
 +
 
 +
function SDebugCollision:onAdd(ent) -- tiny function
 +
local debugcolor = 0xff00ff
 +
if ent.isplayer1 then debugcolor = 0x00ffff end
 +
ent.debug = Pixel.new(debugcolor, 0.25, ent.collbox.w, ent.collbox.h)
 +
ent.spritelayer:addChild(ent.debug)
 
end
 
end
  
function SHitboxHurtboxCollision:onAdd(ent) -- tiny function
+
function SDebugCollision:onRemove(ent) -- tiny function
 +
ent.spritelayer:removeChild(ent.debug)
 
end
 
end
  
function SHitboxHurtboxCollision:onRemove(ent) -- tiny function
+
function SDebugCollision:process(ent, dt) -- tiny function
 +
-- ent.debug:setPosition(ent.x, ent.y)
 +
ent.debug:setPosition(ent.pos)
 
end
 
end
 +
</syntaxhighlight>
  
function SHitboxHurtboxCollision:process(ent, dt) -- tiny function
+
This System displays the actors collision box.
local function checkY(actor1y, actor2y, prange)
+
 
return (-(actor1y-actor2y)<>(actor1y-actor2y)) < prange
+
=== sDebugHitBoxNme.lua ===
 +
"'''sDebugHitBoxNme.lua'''" in the '''"_S"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
SDebugHitBoxNme = Core.class()
 +
 
 +
function SDebugHitBoxNme:init(xtiny, xshowbox) -- tiny function
 +
xtiny.processingSystem(self) -- called once on init and every update
 +
self.showbox = xshowbox
 +
end
 +
 
 +
function SDebugHitBoxNme:filter(ent) -- tiny function
 +
return (ent.headhitboxattack1 or ent.headhitboxattack2 or ent.spinehitboxattack1 or ent.spinehitboxattack2 or
 +
ent.headhitboxjattack1 or ent.spinehitboxjattack1) and not ent.isplayer1
 +
end
 +
 
 +
function SDebugHitBoxNme:onAdd(ent) -- tiny function
 +
-- debugheadhitboxp1
 +
if ent.headhitboxattack1 then
 +
ent.debugheadhitboxp1 = Pixel.new(0xaa0000, 0.5, ent.headhitboxattack1.w, ent.headhitboxattack1.h)
 +
ent.debugheadhitboxp1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhitboxp1)
 +
ent.debugheadhitboxp1:setVisible(self.showbox[1]) -- granular debugging
 +
end
 +
-- debugheadhitboxp2
 +
if ent.headhitboxattack2 then
 +
ent.debugheadhitboxp2 = Pixel.new(0xaa5500, 0.5, ent.headhitboxattack2.w, ent.headhitboxattack2.h)
 +
ent.debugheadhitboxp2:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhitboxp2)
 +
ent.debugheadhitboxp2:setVisible(self.showbox[2]) -- granular debugging
 +
end
 +
-- debugspinehitboxk1
 +
if ent.spinehitboxattack1 then
 +
ent.debugspinehitboxk1 = Pixel.new(0x005500, 0.5, ent.spinehitboxattack1.w, ent.spinehitboxattack1.h)
 +
ent.debugspinehitboxk1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehitboxk1)
 +
ent.debugspinehitboxk1:setVisible(self.showbox[3]) -- granular debugging
 +
end
 +
-- debugspinehitboxk2
 +
if ent.spinehitboxattack2 then
 +
ent.debugspinehitboxk2 = Pixel.new(0x00aa00, 0.5, ent.spinehitboxattack2.w, ent.spinehitboxattack2.h)
 +
ent.debugspinehitboxk2:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehitboxk2)
 +
ent.debugspinehitboxk2:setVisible(self.showbox[4]) -- granular debugging
 
end
 
end
local function checkCollision(box1x, box1y, box1w, box1h, box2x, box2y, box2w, box2h)
+
-- debugheadhitboxjp1
return not (box1x - box1w/2 > box2x + box2w/2 or -- is box1 on the right side of box2?
+
if ent.headhitboxjattack1 then
  box1y - box1h/2 > box2y + box2h/2 or -- is box1 under box2?
+
ent.debugheadhitboxjp1 = Pixel.new(0xaaaa00, 0.5, ent.headhitboxjattack1.w, ent.headhitboxjattack1.h)
  box1x + box1w/2 < box2x - box2w/2 or -- is box1 on the left side of box2?
+
ent.debugheadhitboxjp1:setAnchorPoint(0.5, 0.5)
  box1y + box1h/2 < box2y - box2h/2) -- is box1 above box2?
+
ent.spritelayer:addChild(ent.debugheadhitboxjp1)
 +
ent.debugheadhitboxjp1:setVisible(self.showbox[5]) -- granular debugging
 
end
 
end
if ent.headhitbox and ent.headhitbox.isactive then -- HEAD
+
-- debugspinehitboxjk1
for k, _ in pairs(self.spriteslist) do -- k = entity, v = true
+
if ent.spinehitboxjattack1 then
-- filter out unwanted collisions (player1 vs player1, nme vs nme, ... (you choose!)
+
ent.debugspinehitboxjk1 = Pixel.new(0x00ff00, 0.5, ent.spinehitboxjattack1.w, ent.spinehitboxjattack1.h)
if (ent.isplayer1 and k.isplayer1) or
+
ent.debugspinehitboxjk1:setAnchorPoint(0.5, 0.5)
ent.isdestructibleobject or -- destructible objects only receive damage
+
ent.spritelayer:addChild(ent.debugspinehitboxjk1)
(ent.iscollectible or k.iscollectible) or -- don't check for collectibles
+
ent.debugspinehitboxjk1:setVisible(self.showbox[6]) -- granular debugging
(ent.isnme and k.isnme) or
+
end
(ent.isnme and k.isdestructibleobject) then -- nmes don't destroy objects, you choose
+
end
-- nothing here!
+
 
-- check head collisions according to actors action and use actor y or actor positionystart
+
function SDebugHitBoxNme:onRemove(ent) -- tiny function
-- this prevents collisions between wrong hitbox and hurtbox (actors too far away on y axis)
+
if ent.headhitboxattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxp1) end
elseif (ent.isactionjumppunch1 or ent.isactionjump1) and (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
+
if ent.headhitboxattack2 then ent.spritelayer:removeChild(ent.debugheadhitboxp2) end
if checkY(ent.positionystart, k.positionystart, ent.collbox.h+k.collbox.h) and checkCollision(
+
if ent.spinehitboxattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxk1) end
ent.pos.x+ent.collbox.w/2+(ent.headhitbox.x*ent.flip), ent.pos.y+ent.headhitbox.y,
+
if ent.spinehitboxattack2 then ent.spritelayer:removeChild(ent.debugspinehitboxk2) end
ent.headhitbox.w, ent.headhitbox.h,
+
if ent.headhitboxjattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxjp1) end
k.pos.x+k.collbox.w/2+(k.headhurtbox.x*k.flip), k.pos.y+k.headhurtbox.y,
+
if ent.spinehitboxjattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxjk1) end
k.headhurtbox.w, k.headhurtbox.h) and
+
end
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
+
 
k.isdirty = true
+
function SDebugHitBoxNme:process(ent, dt) -- tiny function
k.damage = ent.headhitbox.damage
+
local function fun()
if k.animation then k.animation.frame = 0 end
+
if ent.headhitboxattack1 then
ent.headhitbox.isactive = false
+
ent.debugheadhitboxp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack1.x*ent.flip), ent.headhitboxattack1.y))
else
+
end
k.isdirty = false
+
if ent.headhitboxattack2 then
ent.headhitbox.isactive = false
+
ent.debugheadhitboxp2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack2.x*ent.flip), ent.headhitboxattack2.y))
end
+
end
elseif (ent.isactionjumppunch1 or ent.isactionjump1) and not (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
+
if ent.spinehitboxattack1 then
if checkY(ent.positionystart, k.pos.y, ent.collbox.h + k.collbox.h) and checkCollision(
+
ent.debugspinehitboxk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack1.x*ent.flip), ent.spinehitboxattack1.y))
ent.pos.x+ent.collbox.w/2+(ent.headhitbox.x*ent.flip), ent.pos.y+ent.headhitbox.y,
+
end
ent.headhitbox.w, ent.headhitbox.h,
+
if ent.spinehitboxattack2 then
k.pos.x+k.collbox.w/2+(k.headhurtbox.x*k.flip), k.pos.y+k.headhurtbox.y,
+
ent.debugspinehitboxk2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack2.x*ent.flip), ent.spinehitboxattack2.y))
k.headhurtbox.w, k.headhurtbox.h) and
+
end
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
+
if ent.headhitboxjattack1 then
k.isdirty = true
+
ent.debugheadhitboxjp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxjattack1.x*ent.flip), ent.headhitboxjattack1.y))
k.damage = ent.headhitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.headhitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.headhitbox.isactive = false
 
end
 
elseif not (ent.isactionjumppunch1 or ent.isactionjump1) and (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
 
if checkY(ent.pos.y, k.positionystart, ent.collbox.h + k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.headhitbox.x*ent.flip), ent.pos.y+ent.headhitbox.y,
 
ent.headhitbox.w, ent.headhitbox.h,
 
k.pos.x+k.collbox.w/2+(k.headhurtbox.x*k.flip), k.pos.y+k.headhurtbox.y,
 
k.headhurtbox.w, k.headhurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.headhitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.headhitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.headhitbox.isactive = false
 
end
 
else
 
if checkY(ent.pos.y, k.pos.y, ent.collbox.h+k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.headhitbox.x*ent.flip), ent.pos.y+ent.headhitbox.y,
 
ent.headhitbox.w, ent.headhitbox.h,
 
k.pos.x+k.collbox.w/2+(k.headhurtbox.x*k.flip), k.pos.y+k.headhurtbox.y,
 
k.headhurtbox.w, k.headhurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.headhitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.headhitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.headhitbox.isactive = false
 
end
 
end
 
 
end
 
end
elseif ent.spinehitbox and ent.spinehitbox.isactive then -- SPINE
+
if ent.spinehitboxjattack1 then
for k, _ in pairs(self.spriteslist) do
+
ent.debugspinehitboxjk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxjattack1.x*ent.flip), ent.spinehitboxjattack1.y))
-- filter out unwanted collisions (player1 vs player1, nme vs nme, ... (you choose!)
 
if (ent.isplayer1 and k.isplayer1) or
 
ent.isdestructibleobject or -- destructible objects only receive damage
 
(ent.iscollectible or k.iscollectible) or -- don't check for collectibles
 
(ent.isnme and k.isnme) or
 
(ent.isnme and k.isdestructibleobject) then -- nmes don't destroy objects, you choose
 
-- nothing here!
 
-- check spine collisions according to actors action and use actor y or actor positionystart
 
-- this prevents collisions between wrong hitbox and hurtbox (actors too far away on y axis)
 
elseif (ent.isactionjumpkick1 or ent.isactionjump1) and (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
 
if checkY(ent.positionystart, k.positionystart, ent.collbox.h + k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.spinehitbox.x*ent.flip), ent.pos.y+ent.spinehitbox.y,
 
ent.spinehitbox.w, ent.spinehitbox.h,
 
k.pos.x+k.collbox.w/2+(k.spinehurtbox.x*k.flip), k.pos.y+k.spinehurtbox.y,
 
k.spinehurtbox.w, k.spinehurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.spinehitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.spinehitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.spinehitbox.isactive = false
 
end
 
elseif (ent.isactionjumpkick1 or ent.isactionjump1) and not (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
 
if checkY(ent.positionystart, k.pos.y, ent.collbox.h + k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.spinehitbox.x*ent.flip), ent.pos.y+ent.spinehitbox.y,
 
ent.spinehitbox.w, ent.spinehitbox.h,
 
k.pos.x+k.collbox.w/2+(k.spinehurtbox.x*k.flip), k.pos.y+k.spinehurtbox.y,
 
k.spinehurtbox.w, k.spinehurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.spinehitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.spinehitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.spinehitbox.isactive = false
 
end
 
elseif not (ent.isactionjumpkick1 or ent.isactionjump1) and (k.isactionjumppunch1 or k.isactionjumpkick1 or k.isactionjump1) then
 
if checkY(ent.pos.y, k.positionystart, ent.collbox.h + k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.spinehitbox.x*ent.flip), ent.pos.y+ent.spinehitbox.y,
 
ent.spinehitbox.w, ent.spinehitbox.h,
 
k.pos.x+k.collbox.w/2+(k.spinehurtbox.x*k.flip), k.pos.y+k.spinehurtbox.y,
 
k.spinehurtbox.w, k.spinehurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.spinehitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.spinehitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.spinehitbox.isactive = false
 
end
 
else
 
if checkY(ent.pos.y, k.pos.y, ent.collbox.h + k.collbox.h) and checkCollision(
 
ent.pos.x+ent.collbox.w/2+(ent.spinehitbox.x*ent.flip), ent.pos.y+ent.spinehitbox.y,
 
ent.spinehitbox.w, ent.spinehitbox.h,
 
k.pos.x+k.collbox.w/2+(k.spinehurtbox.x*k.flip), k.pos.y+k.spinehurtbox.y,
 
k.spinehurtbox.w, k.spinehurtbox.h) and
 
k.washurt <= 0 and k.wasbadlyhurt <= 0 then -- <= here!
 
k.isdirty = true
 
k.damage = ent.spinehitbox.damage
 
if k.animation then k.animation.frame = 0 end
 
ent.spinehitbox.isactive = false
 
else
 
k.isdirty = false
 
ent.spinehitbox.isactive = false
 
end
 
end
 
 
end
 
end
 +
Core.yield(1)
 
end
 
end
 +
Core.asyncCall(fun)
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This System checks if a hitbox and a hurtbox overlap then deals damages to the hurt actor:
+
This System displays the enemies hit boxes when available.
* runs once on init and '''every game loop''' (''process'')
 
* in ''init'' we pass the list of all the actors (''spriteslist'' declared in '''LevelX''')
 
* first we check if the actors are within range on the y axis with '''checkY'''
 
* then we check if a hitbox and a hurtbox collide with '''checkCollision'''
 
* when a hitbox is active, meaning an actor is in attack mode, we check if it's a head hit or a body hit
 
* we also separate the ground attacks from the jump attacks
 
  
== sCollision.lua ==
+
=== sDebugHitBoxPlayer.lua ===
"'''sCollision.lua'''" in the '''"_S"''' folder. The code:
+
"'''sDebugHitBoxPlayer.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SCollision = Core.class()
+
SDebugHitBoxPlayer = Core.class()
  
function SCollision:init(xtiny, xbworld) -- tiny function
+
function SDebugHitBoxPlayer:init(xtiny, xshowbox) -- tiny function
 
xtiny.processingSystem(self) -- called once on init and every update
 
xtiny.processingSystem(self) -- called once on init and every update
self.bworld = xbworld
+
self.showbox = xshowbox
 
end
 
end
  
function SCollision:filter(ent) -- tiny function
+
function SDebugHitBoxPlayer:filter(ent) -- tiny function
return ent.collbox and ent.body
+
return (ent.headhitboxattack1 or ent.headhitboxattack2 or ent.spinehitboxattack1 or ent.spinehitboxattack2 or
 +
ent.headhitboxjattack1 or ent.spinehitboxjattack1) and not ent.isnme
 
end
 
end
  
function SCollision:onAdd(ent) -- tiny function
+
function SDebugHitBoxPlayer:onAdd(ent) -- tiny function
 +
-- debugheadhitboxp1
 +
if ent.headhitboxattack1 then
 +
ent.debugheadhitboxp1 = Pixel.new(0xaa0000, 0.5, ent.headhitboxattack1.w, ent.headhitboxattack1.h)
 +
ent.debugheadhitboxp1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhitboxp1)
 +
ent.debugheadhitboxp1:setVisible(self.showbox[1]) -- granular debugging
 +
end
 +
-- debugheadhitboxp2
 +
if ent.headhitboxattack2 then
 +
ent.debugheadhitboxp2 = Pixel.new(0xaa5500, 0.5, ent.headhitboxattack2.w, ent.headhitboxattack2.h)
 +
ent.debugheadhitboxp2:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhitboxp2)
 +
ent.debugheadhitboxp2:setVisible(self.showbox[2]) -- granular debugging
 +
end
 +
-- debugspinehitboxk1
 +
if ent.spinehitboxattack1 then
 +
ent.debugspinehitboxk1 = Pixel.new(0x005500, 0.5, ent.spinehitboxattack1.w, ent.spinehitboxattack1.h)
 +
ent.debugspinehitboxk1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehitboxk1)
 +
ent.debugspinehitboxk1:setVisible(self.showbox[3]) -- granular debugging
 +
end
 +
-- debugspinehitboxk2
 +
if ent.spinehitboxattack2 then
 +
ent.debugspinehitboxk2 = Pixel.new(0x00aa00, 0.5, ent.spinehitboxattack2.w, ent.spinehitboxattack2.h)
 +
ent.debugspinehitboxk2:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehitboxk2)
 +
ent.debugspinehitboxk2:setVisible(self.showbox[4]) -- granular debugging
 +
end
 +
-- debugheadhitboxjp1
 +
if ent.headhitboxjattack1 then
 +
ent.debugheadhitboxjp1 = Pixel.new(0xaaaa00, 0.5, ent.headhitboxjattack1.w, ent.headhitboxjattack1.h)
 +
ent.debugheadhitboxjp1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhitboxjp1)
 +
ent.debugheadhitboxjp1:setVisible(self.showbox[5]) -- granular debugging
 +
end
 +
-- debugspinehitboxjk1
 +
if ent.spinehitboxjattack1 then
 +
ent.debugspinehitboxjk1 = Pixel.new(0x00ff00, 0.5, ent.spinehitboxjattack1.w, ent.spinehitboxjattack1.h)
 +
ent.debugspinehitboxjk1:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehitboxjk1)
 +
ent.debugspinehitboxjk1:setVisible(self.showbox[6]) -- granular debugging
 +
end
 
end
 
end
  
function SCollision:onRemove(ent) -- tiny function
+
function SDebugHitBoxPlayer:onRemove(ent) -- tiny function
 +
if ent.headhitboxattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxp1) end
 +
if ent.headhitboxattack2 then ent.spritelayer:removeChild(ent.debugheadhitboxp2) end
 +
if ent.spinehitboxattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxk1) end
 +
if ent.spinehitboxattack2 then ent.spritelayer:removeChild(ent.debugspinehitboxk2) end
 +
if ent.headhitboxjattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxjp1) end
 +
if ent.spinehitboxjattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxjk1) end
 
end
 
end
  
local col -- cbump perfs?
+
function SDebugHitBoxPlayer:process(ent, dt) -- tiny function
function SCollision:process(ent, dt) -- tiny function
 
 
local function fun()
 
local function fun()
-- collision filter
+
if ent.headhitboxattack1 then
local function collisionfilter(item, other) -- "touch", "cross", "slide", "bounce"
+
ent.debugheadhitboxp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack1.x*ent.flip), ent.headhitboxattack1.y))
if item.isactionjump1 or other.isactionjump1 or
 
item.isactionjumppunch1 or other.isactionjumppunch1 or
 
item.isactionjumpkick1 or other.isactionjumpkick1 or
 
item.isdead or other.isdead then return nil
 
elseif item.iscollectible or other.iscollectible then return "cross"
 
end return "slide"
 
 
end
 
end
-- cbump
+
if ent.headhitboxattack2 then
local goalx = ent.pos.x + ent.body.vx * dt
+
ent.debugheadhitboxp2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack2.x*ent.flip), ent.headhitboxattack2.y))
local goaly = ent.pos.y + ent.body.vy
+
end
local nextx, nexty, collisions, len = self.bworld:move(ent, goalx, goaly, collisionfilter)
+
if ent.spinehitboxattack1 then
-- collisions
+
ent.debugspinehitboxk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack1.x*ent.flip), ent.spinehitboxattack1.y))
for i = 1, len do
+
end
col = collisions[i]
+
if ent.spinehitboxattack2 then
if col.item.iscollectible and col.other.isplayer1 then col.item.isdirty = true
+
ent.debugspinehitboxk2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack2.x*ent.flip), ent.spinehitboxattack2.y))
elseif col.item.isplayer1 and col.other.iscollectible then col.other.isdirty = true
+
end
end
+
if ent.headhitboxjattack1 then
 +
ent.debugheadhitboxjp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxjattack1.x*ent.flip), ent.headhitboxjattack1.y))
 +
end
 +
if ent.spinehitboxjattack1 then
 +
ent.debugspinehitboxjk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxjattack1.x*ent.flip), ent.spinehitboxjattack1.y))
 
end
 
end
-- move & flip
+
Core.yield(1)
ent.pos = vector(nextx, nexty)
+
end
ent.sprite:setPosition(ent.pos + vector(ent.collbox.w/2, -ent.h/2+ent.collbox.h/2))
+
Core.asyncCall(fun)
ent.animation.bmp:setScale(ent.sx * ent.flip, ent.sy)
+
end
Core.yield(0.1)
+
</syntaxhighlight>
 +
 
 +
This System displays the player hit boxes when available.
 +
 
 +
=== sDebugHurtBoxNme.lua ===
 +
"'''sDebugHurtBoxNme.lua'''" in the '''"_S"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
SDebugHurtBoxNme = Core.class()
 +
 
 +
function SDebugHurtBoxNme:init(xtiny) -- tiny function
 +
xtiny.processingSystem(self) -- called once on init and every update
 +
end
 +
 
 +
function SDebugHurtBoxNme:filter(ent) -- tiny function
 +
return (ent.headhurtbox or ent.spinehurtbox) and not ent.isplayer1
 +
end
 +
 
 +
function SDebugHurtBoxNme:onAdd(ent) -- tiny function
 +
-- debugheadhurtbox
 +
ent.debugheadhurtbox = Pixel.new(0x5500ff, 0.5, ent.headhurtbox.w, ent.headhurtbox.h)
 +
ent.debugheadhurtbox:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhurtbox)
 +
-- debugspinehurtbox
 +
ent.debugspinehurtbox = Pixel.new(0xff00ff, 0.5, ent.spinehurtbox.w, ent.spinehurtbox.h)
 +
ent.debugspinehurtbox:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehurtbox)
 +
end
 +
 
 +
function SDebugHurtBoxNme:onRemove(ent) -- tiny function
 +
if not ent.isdestructibleobject then -- isdestructibleobject is somehow already removed!
 +
ent.spritelayer:removeChild(ent.debugheadhurtbox)
 +
ent.spritelayer:removeChild(ent.debugspinehurtbox)
 +
end
 +
end
 +
 
 +
function SDebugHurtBoxNme:process(ent, dt) -- tiny function
 +
local function fun()
 +
ent.debugheadhurtbox:setPosition(ent.pos+vector(ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.headhurtbox.y))
 +
ent.debugspinehurtbox:setPosition(ent.pos+vector(ent.collbox.w/2+(ent.spinehurtbox.x*ent.flip), ent.spinehurtbox.y))
 +
Core.yield(1)
 
end
 
end
 
Core.asyncCall(fun)
 
Core.asyncCall(fun)
Line 597: Line 519:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
This System uses the '''Bump''' plugin to check for collisions between the actors collision boxes:
+
This System displays the enemies and breakable objects hurt boxes.
* runs once on init and '''every game loop''' (''process'')
+
 
* in ''init'' we pass the Bump '''world''' the actors live in
+
=== sDebugHurtBoxPlayer.lua ===
* we define Bump collision filter
+
"'''sDebugHurtBoxPlayer.lua'''" in the '''"_S"''' folder. The code:
* we check if player1 collides with a collectible actor and tag the collectible as dirty
+
<syntaxhighlight lang="lua">
* finally we set the actor position and flip its bitmap in the direction it is going
+
SDebugHurtBoxPlayer = Core.class()
* I experimented with '''asyncCall''' to test if we could gain some frames per second ;-)
+
 
 +
function SDebugHurtBoxPlayer:init(xtiny) -- tiny function
 +
xtiny.processingSystem(self) -- called once on init and every update
 +
end
 +
 
 +
function SDebugHurtBoxPlayer:filter(ent) -- tiny function
 +
return (ent.headhurtbox or ent.spinehurtbox) and not ent.isnme
 +
end
 +
 
 +
function SDebugHurtBoxPlayer:onAdd(ent) -- tiny function
 +
-- debugheadhurtbox
 +
ent.debugheadhurtbox = Pixel.new(0x5500ff, 0.5, ent.headhurtbox.w, ent.headhurtbox.h)
 +
ent.debugheadhurtbox:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugheadhurtbox)
 +
-- debugspinehurtbox
 +
ent.debugspinehurtbox = Pixel.new(0xff00ff, 0.5, ent.spinehurtbox.w, ent.spinehurtbox.h)
 +
ent.debugspinehurtbox:setAnchorPoint(0.5, 0.5)
 +
ent.spritelayer:addChild(ent.debugspinehurtbox)
 +
end
 +
 
 +
function SDebugHurtBoxPlayer:onRemove(ent) -- tiny function
 +
ent.spritelayer:removeChild(ent.debugheadhurtbox)
 +
ent.spritelayer:removeChild(ent.debugspinehurtbox)
 +
end
 +
 
 +
function SDebugHurtBoxPlayer:process(ent, dt) -- tiny function
 +
ent.debugheadhurtbox:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.headhurtbox.y))
 +
ent.debugspinehurtbox:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehurtbox.x*ent.flip), ent.spinehurtbox.y))
 +
end
 +
</syntaxhighlight>
 +
 
 +
This System displays the player hurt boxes.
 +
 
 +
=== sDebugSpriteSorting.lua ===
 +
"'''sDebugSpriteSorting.lua'''" in the '''"_S"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
SDebugSpriteSorting = Core.class()
 +
 
 +
function SDebugSpriteSorting:init(xtiny) -- tiny function
 +
xtiny.processingSystem(self) -- called once on init and every update
 +
end
 +
 
 +
function SDebugSpriteSorting:filter(ent) -- tiny function
 +
return ent.sprite
 +
end
 +
 
 +
function SDebugSpriteSorting:onAdd(ent) -- tiny function
 +
-- debugposition
 +
ent.debugposition = Pixel.new(0xaa5500, 1, 16, 16)
 +
ent.debugposition:setAnchorPoint(0.5, 0.5)
 +
if ent.isplayer1 then ent.debugposition:setColor(0xaa5500, 1) end
 +
ent.spritelayer:addChild(ent.debugposition)
 +
-- debugstartyposition
 +
ent.debugstartyposition = Pixel.new(0xaa557f, 1, 16, 16)
 +
ent.debugstartyposition:setAnchorPoint(0.5, 0.5)
 +
if ent.isplayer1 then ent.debugstartyposition:setColor(0xaa557f, 1) end
 +
ent.spritelayer:addChild(ent.debugstartyposition)
 +
end
 +
 
 +
function SDebugSpriteSorting:onRemove(ent) -- tiny function
 +
ent.spritelayer:removeChild(ent.debugposition)
 +
ent.spritelayer:removeChild(ent.debugstartyposition)
 +
end
 +
 
 +
function SDebugSpriteSorting:process(ent, dt) -- tiny function
 +
ent.debugposition:setPosition(ent.pos)
 +
ent.debugstartyposition:setPosition(ent.pos.x, ent.positionystart)
 +
end
 +
</syntaxhighlight>
 +
 
 +
This System displays the actors y position.
  
 
== Next? ==
 
== Next? ==
Next we add the systems for the breakable objects and the collectibles and we are almost done with the game!
+
'''Congratulations'''! We have finished our game.
 +
 
 +
Let's add the final scene: '''YOU WIN'''!
  
  
 
Prev.: [[Tuto tiny-ecs beatemup Part 10 Systems 2]]</br>
 
Prev.: [[Tuto tiny-ecs beatemup Part 10 Systems 2]]</br>
'''Next: [[Tuto tiny-ecs beatemup Part 12 XXX]]'''
+
'''Next: [[Tuto tiny-ecs beatemup Part 12 You Win]]'''
  
  
 
'''[[Tutorial - tiny-ecs beatemup]]'''
 
'''[[Tutorial - tiny-ecs beatemup]]'''
 
{{GIDEROS IMPORTANT LINKS}}
 
{{GIDEROS IMPORTANT LINKS}}

Latest revision as of 02:32, 25 November 2024

The Systems 3

A couple more systems to add and we are done. We also add the Debug Systems to help us visualize all the boxes (hitbox, hurtbox, collsion box, ...).

sDestructibleObjects.lua

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

SDestructibleObjects = Core.class()

local random, cos, sin = math.random, math.cos, math.sin
local insert = table.insert

function SDestructibleObjects:init(xtiny, xbworld) -- tiny function
	self.tiny = xtiny -- ref so we can remove entities from tiny system
	self.tiny.processingSystem(self) -- called once on init and every update
	self.bworld = xbworld
	-- sfx
	self.snd = Sound.new("audio/sfx/footstep/Forest02.wav")
	self.channel = self.snd:play(0, false, true)
end

function SDestructibleObjects:filter(ent) -- tiny function
	return ent.isdestructibleobject
end

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

function SDestructibleObjects:onRemove(ent) -- tiny function
	-- spawn a random collectible: ECollectible:init(xspritelayer, xpos)
	local function fun()
		local el = ECollectible.new(ent.spritelayer, ent.pos+vector(ent.collbox.w/4, -1*ent.collbox.h))
		self.tiny.tworld:addEntity(el)
		self.bworld:add(el, el.pos.x, el.pos.y, el.collbox.w, el.collbox.h)
		Core.yield(1)
	end
	Core.asyncCall(fun)
	self.bworld:remove(ent) -- remove collision box from cbump world here!
end

function SDestructibleObjects:process(ent, dt) -- tiny function
	local function EffectExplode(s, scale, pos, r, speed, texture)
		local p = Particles.new()
		p:setPosition(pos)
		p:setTexture(texture)
		p:setScale(scale)
		s:addChild(p)
		local parts = {}
		for i = 1, 6 do -- 8
			local a = random()*6.3
			local dx, dy = cos(a), sin(a)
			local sr = random()*r
			local px, py = dx*sr, dy*sr
			local ss = (random()+0.5) * (speed or 1)
			insert(parts,
				{
					x = px, y = py,
					speedX = dx * ss,
					speedY = dy * ss,
					speedAngular = random()*4 - 2,
					decayAlpha = random()*0.04 + 0.95,
					ttl = 32, -- 500
					size = random()*10 + 20,
				}
			)
		end
		p:addParticles(parts)
		Core.yield(1)
		p:removeFromParent()
	end
	-- hurt fx
	if ent.washurt and ent.washurt > 0 then
		ent.washurt -= 1
		if ent.washurt < ent.recovertimer/2 then
			if ent.hitfx then ent.hitfx:setVisible(false) end
		end
		if ent.washurt <= 0 then
			ent.sprite:setColorTransform(1, 1, 1, 1)
		end
	end
	if ent.isdirty then -- hit
		self.channel = self.snd:play()
		if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
		ent.hitfx:setVisible(true)
		ent.hitfx:setPosition(ent.pos + vector(ent.headhurtbox.x+4, ent.headhurtbox.y))
		ent.spritelayer:addChild(ent.hitfx)
		ent.currhealth -= ent.damage
		ent.washurt = ent.recovertimer -- timer for a flash effect
		ent.sprite:setColorTransform(1, 1, 1, 3) -- a flash effect
		ent.isdirty = false
		if ent.currhealth <= 0 then
			--EffectExplode(s, scale, pos, r, speed, texture)
			Core.asyncCall(EffectExplode, ent.spritelayer, 2,
				ent.pos+vector(ent.collbox.w/2, -ent.h/2), 4, 2,
				Texture.new("gfx/fx/fxBarrel_02_0011.png"))
			ent.spritelayer:removeChild(ent.hitfx)
			self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
			self.tiny.numberofdestructibleobjects -= 1
		end
	end
end

The System removes a breakable object when destroyed and spawn a collectible in the onRemove function:

  • it runs every frame
  • it affects only entities with an isdestructibleobject id
  • on hurt adds an hit fx
  • on destroyed adds particles
  • onRemove spawns a collectible

sCollectible.lua

"sCollectible.lua" in the "_S" folder. The code:

SCollectible = Core.class()

local random = math.random

function SCollectible: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.new("audio/sfx/sfx_coin_double1.wav")
	self.channel = self.snd:play(0, false, true)
end

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

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

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

function SCollectible: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
		self.channel = self.snd:play()
		if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
--		ent.hitfx:setVisible(true)
--		ent.hitfx:setPosition(ent.pos.x+ent.headhurtbox.x, ent.y+ent.headhurtbox.y)
--		ent.spritelayer:addChild(ent.hitfx)
--		ent.currhealth -= ent.damage
--		ent.washurt = ent.recovertimer -- timer for a flash effect
		if random(100) > 50 then
			if self.player1.currjumps < 0 then self.player1.currjumps = 0 end
			self.player1.currjumps += 3
			self.tiny.hudcurrjumps:setText("JUMPS: "..self.player1.currjumps)
		else
			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
		end
		ent.sprite:setColorTransform(0, 2, 0, 3) -- the flash effect (a bright color)
		ent.isdirty = false
--[[
		if ent.currhealth <= 0 then
			ent.hitfx:setColorTransform(3, 0, 0, random(1, 3)/10)
			ent.hitfx:setY(ent.hitfx:getY()+ent.h/1.1) -- magik XXX
			ent.hitfx:setRotation(random(360))
			ent.hitfx:setScale(random(5, 10)/10)
			ent.bgfxlayer:addChild(ent.hitfx)
			self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
--			self.tiny.numberofnmes -= 1
		end
]]
		self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
	end
end

This System spawns a collectible:

  • runs once on init and every game loop (process)
  • there are two kind of collectibles: health and jump attacks (updated in the HUD)
There are quite a bit of commented code you can delete as this Entity is immediately removed

sSpritesSorting.lua

"sSpritesSorting.lua" in the "_S" folder. The code:

SSpritesSorting = Core.class()

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

function SSpritesSorting:filter(ent) -- tiny function
	return ent.sprite
end

function SSpritesSorting:onAdd(ent) -- tiny function
--	print("SSpritesSorting added", ent)
	self.spriteslist[ent] = true
end

function SSpritesSorting:onRemove(ent) -- tiny function
--	print("SSpritesSorting removed", ent)
	self.spriteslist[ent] = nil
end

local p1rangetoofar = myappwidth*0.5 -- save some CPU
function SSpritesSorting:process(ent, dt) -- tiny function
	local function fun()
		for k, _ in pairs(self.spriteslist) do
			if ent.currlives <= 0 or k.currlives <= 0 then -- don't sort if dead
				return
			end
			if k.isplayer1 then -- don't sort out of range actors to save frames
				if -(k.pos.x-ent.pos.x)<>(k.pos.x-ent.pos.x) > p1rangetoofar then
					return
				end
			end
			if not ent.body.isonfloor then
				if ent.positionystart < k.positionystart and -- ent is behind
					ent.spritelayer:getChildIndex(ent.sprite) > k.spritelayer:getChildIndex(k.sprite) then -- sprite is in front
					ent.spritelayer:swapChildren(ent.sprite, k.sprite)
				end
			else
				if ent.pos.y < k.pos.y and -- ent is behind
					ent.spritelayer:getChildIndex(ent.sprite) > k.spritelayer:getChildIndex(k.sprite) then -- sprite is in front
					ent.spritelayer:swapChildren(ent.sprite, k.sprite)
				end
			end
		end
		Core.yield(0.5)
	end
	Core.asyncCall(fun) -- profiler seems to be faster without asyncCall (because of pairs traversing?)
end

Finally this System sorts the actors on the y axis:

  • runs once on init and every game loop (process)
  • there is a distinction between the actor being on floor and jumping

DEBUG SYSTEMS

It would be difficult to accurately position the hit and hurt boxes, the collision boxes, ... without visual cues.

Debug systems help us with the above. Fortunately they are small and quite simple.

sDebugCollision.lua

"sDebugCollision.lua" in the "_S" folder. 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 = 0xff00ff
	if ent.isplayer1 then debugcolor = 0x00ffff end
	ent.debug = Pixel.new(debugcolor, 0.25, ent.collbox.w, ent.collbox.h)
	ent.spritelayer:addChild(ent.debug)
end

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

function SDebugCollision:process(ent, dt) -- tiny function
--	ent.debug:setPosition(ent.x, ent.y)
	ent.debug:setPosition(ent.pos)
end

This System displays the actors collision box.

sDebugHitBoxNme.lua

"sDebugHitBoxNme.lua" in the "_S" folder. The code:

SDebugHitBoxNme = Core.class()

function SDebugHitBoxNme:init(xtiny, xshowbox) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
	self.showbox = xshowbox
end

function SDebugHitBoxNme:filter(ent) -- tiny function
	return (ent.headhitboxattack1 or ent.headhitboxattack2 or ent.spinehitboxattack1 or ent.spinehitboxattack2 or
		ent.headhitboxjattack1 or ent.spinehitboxjattack1) and not ent.isplayer1
end

function SDebugHitBoxNme:onAdd(ent) -- tiny function
	-- debugheadhitboxp1
	if ent.headhitboxattack1 then
		ent.debugheadhitboxp1 = Pixel.new(0xaa0000, 0.5, ent.headhitboxattack1.w, ent.headhitboxattack1.h)
		ent.debugheadhitboxp1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxp1)
		ent.debugheadhitboxp1:setVisible(self.showbox[1]) -- granular debugging
	end
	-- debugheadhitboxp2
	if ent.headhitboxattack2 then
		ent.debugheadhitboxp2 = Pixel.new(0xaa5500, 0.5, ent.headhitboxattack2.w, ent.headhitboxattack2.h)
		ent.debugheadhitboxp2:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxp2)
		ent.debugheadhitboxp2:setVisible(self.showbox[2]) -- granular debugging
	end
	-- debugspinehitboxk1
	if ent.spinehitboxattack1 then
		ent.debugspinehitboxk1 = Pixel.new(0x005500, 0.5, ent.spinehitboxattack1.w, ent.spinehitboxattack1.h)
		ent.debugspinehitboxk1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxk1)
		ent.debugspinehitboxk1:setVisible(self.showbox[3]) -- granular debugging
	end
	-- debugspinehitboxk2
	if ent.spinehitboxattack2 then
		ent.debugspinehitboxk2 = Pixel.new(0x00aa00, 0.5, ent.spinehitboxattack2.w, ent.spinehitboxattack2.h)
		ent.debugspinehitboxk2:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxk2)
		ent.debugspinehitboxk2:setVisible(self.showbox[4]) -- granular debugging
	end
	-- debugheadhitboxjp1
	if ent.headhitboxjattack1 then
		ent.debugheadhitboxjp1 = Pixel.new(0xaaaa00, 0.5, ent.headhitboxjattack1.w, ent.headhitboxjattack1.h)
		ent.debugheadhitboxjp1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxjp1)
		ent.debugheadhitboxjp1:setVisible(self.showbox[5]) -- granular debugging
	end
	-- debugspinehitboxjk1
	if ent.spinehitboxjattack1 then
		ent.debugspinehitboxjk1 = Pixel.new(0x00ff00, 0.5, ent.spinehitboxjattack1.w, ent.spinehitboxjattack1.h)
		ent.debugspinehitboxjk1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxjk1)
		ent.debugspinehitboxjk1:setVisible(self.showbox[6]) -- granular debugging
	end
end

function SDebugHitBoxNme:onRemove(ent) -- tiny function
	if ent.headhitboxattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxp1) end
	if ent.headhitboxattack2 then ent.spritelayer:removeChild(ent.debugheadhitboxp2) end
	if ent.spinehitboxattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxk1) end
	if ent.spinehitboxattack2 then ent.spritelayer:removeChild(ent.debugspinehitboxk2) end
	if ent.headhitboxjattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxjp1) end
	if ent.spinehitboxjattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxjk1) end
end

function SDebugHitBoxNme:process(ent, dt) -- tiny function
	local function fun()
		if ent.headhitboxattack1 then
			ent.debugheadhitboxp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack1.x*ent.flip), ent.headhitboxattack1.y))
		end
		if ent.headhitboxattack2 then
			ent.debugheadhitboxp2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack2.x*ent.flip), ent.headhitboxattack2.y))
		end
		if ent.spinehitboxattack1 then
			ent.debugspinehitboxk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack1.x*ent.flip), ent.spinehitboxattack1.y))
		end
		if ent.spinehitboxattack2 then
			ent.debugspinehitboxk2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack2.x*ent.flip), ent.spinehitboxattack2.y))
		end
		if ent.headhitboxjattack1 then
			ent.debugheadhitboxjp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxjattack1.x*ent.flip), ent.headhitboxjattack1.y))
		end
		if ent.spinehitboxjattack1 then
			ent.debugspinehitboxjk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxjattack1.x*ent.flip), ent.spinehitboxjattack1.y))
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

This System displays the enemies hit boxes when available.

sDebugHitBoxPlayer.lua

"sDebugHitBoxPlayer.lua" in the "_S" folder. The code:

SDebugHitBoxPlayer = Core.class()

function SDebugHitBoxPlayer:init(xtiny, xshowbox) -- tiny function
	xtiny.processingSystem(self) -- called once on init and every update
	self.showbox = xshowbox
end

function SDebugHitBoxPlayer:filter(ent) -- tiny function
	return (ent.headhitboxattack1 or ent.headhitboxattack2 or ent.spinehitboxattack1 or ent.spinehitboxattack2 or
		ent.headhitboxjattack1 or ent.spinehitboxjattack1) and not ent.isnme
end

function SDebugHitBoxPlayer:onAdd(ent) -- tiny function
	-- debugheadhitboxp1
	if ent.headhitboxattack1 then
		ent.debugheadhitboxp1 = Pixel.new(0xaa0000, 0.5, ent.headhitboxattack1.w, ent.headhitboxattack1.h)
		ent.debugheadhitboxp1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxp1)
		ent.debugheadhitboxp1:setVisible(self.showbox[1]) -- granular debugging
	end
	-- debugheadhitboxp2
	if ent.headhitboxattack2 then
		ent.debugheadhitboxp2 = Pixel.new(0xaa5500, 0.5, ent.headhitboxattack2.w, ent.headhitboxattack2.h)
		ent.debugheadhitboxp2:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxp2)
		ent.debugheadhitboxp2:setVisible(self.showbox[2]) -- granular debugging
	end
	-- debugspinehitboxk1
	if ent.spinehitboxattack1 then
		ent.debugspinehitboxk1 = Pixel.new(0x005500, 0.5, ent.spinehitboxattack1.w, ent.spinehitboxattack1.h)
		ent.debugspinehitboxk1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxk1)
		ent.debugspinehitboxk1:setVisible(self.showbox[3]) -- granular debugging
	end
	-- debugspinehitboxk2
	if ent.spinehitboxattack2 then
		ent.debugspinehitboxk2 = Pixel.new(0x00aa00, 0.5, ent.spinehitboxattack2.w, ent.spinehitboxattack2.h)
		ent.debugspinehitboxk2:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxk2)
		ent.debugspinehitboxk2:setVisible(self.showbox[4]) -- granular debugging
	end
	-- debugheadhitboxjp1
	if ent.headhitboxjattack1 then
		ent.debugheadhitboxjp1 = Pixel.new(0xaaaa00, 0.5, ent.headhitboxjattack1.w, ent.headhitboxjattack1.h)
		ent.debugheadhitboxjp1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugheadhitboxjp1)
		ent.debugheadhitboxjp1:setVisible(self.showbox[5]) -- granular debugging
	end
	-- debugspinehitboxjk1
	if ent.spinehitboxjattack1 then
		ent.debugspinehitboxjk1 = Pixel.new(0x00ff00, 0.5, ent.spinehitboxjattack1.w, ent.spinehitboxjattack1.h)
		ent.debugspinehitboxjk1:setAnchorPoint(0.5, 0.5)
		ent.spritelayer:addChild(ent.debugspinehitboxjk1)
		ent.debugspinehitboxjk1:setVisible(self.showbox[6]) -- granular debugging
	end
end

function SDebugHitBoxPlayer:onRemove(ent) -- tiny function
	if ent.headhitboxattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxp1) end
	if ent.headhitboxattack2 then ent.spritelayer:removeChild(ent.debugheadhitboxp2) end
	if ent.spinehitboxattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxk1) end
	if ent.spinehitboxattack2 then ent.spritelayer:removeChild(ent.debugspinehitboxk2) end
	if ent.headhitboxjattack1 then ent.spritelayer:removeChild(ent.debugheadhitboxjp1) end
	if ent.spinehitboxjattack1 then ent.spritelayer:removeChild(ent.debugspinehitboxjk1) end
end

function SDebugHitBoxPlayer:process(ent, dt) -- tiny function
	local function fun()
		if ent.headhitboxattack1 then
			ent.debugheadhitboxp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack1.x*ent.flip), ent.headhitboxattack1.y))
		end
		if ent.headhitboxattack2 then
			ent.debugheadhitboxp2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxattack2.x*ent.flip), ent.headhitboxattack2.y))
		end
		if ent.spinehitboxattack1 then
			ent.debugspinehitboxk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack1.x*ent.flip), ent.spinehitboxattack1.y))
		end
		if ent.spinehitboxattack2 then
			ent.debugspinehitboxk2:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxattack2.x*ent.flip), ent.spinehitboxattack2.y))
		end
		if ent.headhitboxjattack1 then
			ent.debugheadhitboxjp1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhitboxjattack1.x*ent.flip), ent.headhitboxjattack1.y))
		end
		if ent.spinehitboxjattack1 then
			ent.debugspinehitboxjk1:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehitboxjattack1.x*ent.flip), ent.spinehitboxjattack1.y))
		end
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

This System displays the player hit boxes when available.

sDebugHurtBoxNme.lua

"sDebugHurtBoxNme.lua" in the "_S" folder. The code:

SDebugHurtBoxNme = Core.class()

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

function SDebugHurtBoxNme:filter(ent) -- tiny function
	return (ent.headhurtbox or ent.spinehurtbox) and not ent.isplayer1
end

function SDebugHurtBoxNme:onAdd(ent) -- tiny function
	-- debugheadhurtbox
	ent.debugheadhurtbox = Pixel.new(0x5500ff, 0.5, ent.headhurtbox.w, ent.headhurtbox.h)
	ent.debugheadhurtbox:setAnchorPoint(0.5, 0.5)
	ent.spritelayer:addChild(ent.debugheadhurtbox)
	-- debugspinehurtbox
	ent.debugspinehurtbox = Pixel.new(0xff00ff, 0.5, ent.spinehurtbox.w, ent.spinehurtbox.h)
	ent.debugspinehurtbox:setAnchorPoint(0.5, 0.5)
	ent.spritelayer:addChild(ent.debugspinehurtbox)
end

function SDebugHurtBoxNme:onRemove(ent) -- tiny function
	if not ent.isdestructibleobject then -- isdestructibleobject is somehow already removed!
		ent.spritelayer:removeChild(ent.debugheadhurtbox)
		ent.spritelayer:removeChild(ent.debugspinehurtbox)
	end
end

function SDebugHurtBoxNme:process(ent, dt) -- tiny function
	local function fun()
		ent.debugheadhurtbox:setPosition(ent.pos+vector(ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.headhurtbox.y))
		ent.debugspinehurtbox:setPosition(ent.pos+vector(ent.collbox.w/2+(ent.spinehurtbox.x*ent.flip), ent.spinehurtbox.y))
		Core.yield(1)
	end
	Core.asyncCall(fun)
end

This System displays the enemies and breakable objects hurt boxes.

sDebugHurtBoxPlayer.lua

"sDebugHurtBoxPlayer.lua" in the "_S" folder. The code:

SDebugHurtBoxPlayer = Core.class()

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

function SDebugHurtBoxPlayer:filter(ent) -- tiny function
	return (ent.headhurtbox or ent.spinehurtbox) and not ent.isnme
end

function SDebugHurtBoxPlayer:onAdd(ent) -- tiny function
	-- debugheadhurtbox
	ent.debugheadhurtbox = Pixel.new(0x5500ff, 0.5, ent.headhurtbox.w, ent.headhurtbox.h)
	ent.debugheadhurtbox:setAnchorPoint(0.5, 0.5)
	ent.spritelayer:addChild(ent.debugheadhurtbox)
	-- debugspinehurtbox
	ent.debugspinehurtbox = Pixel.new(0xff00ff, 0.5, ent.spinehurtbox.w, ent.spinehurtbox.h)
	ent.debugspinehurtbox:setAnchorPoint(0.5, 0.5)
	ent.spritelayer:addChild(ent.debugspinehurtbox)
end

function SDebugHurtBoxPlayer:onRemove(ent) -- tiny function
	ent.spritelayer:removeChild(ent.debugheadhurtbox)
	ent.spritelayer:removeChild(ent.debugspinehurtbox)
end

function SDebugHurtBoxPlayer:process(ent, dt) -- tiny function
	ent.debugheadhurtbox:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.headhurtbox.y))
	ent.debugspinehurtbox:setPosition(ent.pos + vector(ent.collbox.w/2+(ent.spinehurtbox.x*ent.flip), ent.spinehurtbox.y))
end

This System displays the player hurt boxes.

sDebugSpriteSorting.lua

"sDebugSpriteSorting.lua" in the "_S" folder. The code:

SDebugSpriteSorting = Core.class()

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

function SDebugSpriteSorting:filter(ent) -- tiny function
	return ent.sprite
end

function SDebugSpriteSorting:onAdd(ent) -- tiny function
	-- debugposition
	ent.debugposition = Pixel.new(0xaa5500, 1, 16, 16)
	ent.debugposition:setAnchorPoint(0.5, 0.5)
	if ent.isplayer1 then ent.debugposition:setColor(0xaa5500, 1) end
	ent.spritelayer:addChild(ent.debugposition)
	-- debugstartyposition
	ent.debugstartyposition = Pixel.new(0xaa557f, 1, 16, 16)
	ent.debugstartyposition:setAnchorPoint(0.5, 0.5)
	if ent.isplayer1 then ent.debugstartyposition:setColor(0xaa557f, 1) end
	ent.spritelayer:addChild(ent.debugstartyposition)
end

function SDebugSpriteSorting:onRemove(ent) -- tiny function
	ent.spritelayer:removeChild(ent.debugposition)
	ent.spritelayer:removeChild(ent.debugstartyposition)
end

function SDebugSpriteSorting:process(ent, dt) -- tiny function
	ent.debugposition:setPosition(ent.pos)
	ent.debugstartyposition:setPosition(ent.pos.x, ent.positionystart)
end

This System displays the actors y position.

Next?

Congratulations! We have finished our game.

Let's add the final scene: YOU WIN!


Prev.: Tuto tiny-ecs beatemup Part 10 Systems 2
Next: Tuto tiny-ecs beatemup Part 12 You Win


Tutorial - tiny-ecs beatemup