Difference between revisions of "Tuto tiny-ecs beatemup Part 10 Systems 2"

From GiderosMobile
(wip)
 
(wip)
Line 1: Line 1:
 
__TOC__
 
__TOC__
  
== The Systems ==
+
== The Systems 2 ==
We have our entities, we have our components, now the systems. What is an ECS '''System'''?
+
We continue adding systems to our game.
 
 
 
  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:
 
  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:
 
   
 
   
Line 20: Line 19:
 
  '''Please see [[Tiny-ecs#System_functions]] for more information'''
 
  '''Please see [[Tiny-ecs#System_functions]] for more information'''
  
That looks scary but worry not we won't use all the callback functions :-)
+
== sAnimation.lua ==
 
+
Time to animate our actors. Please create a file "'''sAnimation.lua'''" in the '''"_S"''' folder and the code:
To put it simple a '''System''' manipulates entities. Let's see our first '''System'''.
 
 
 
== sDrawable.lua ==
 
Please create a file "'''sDrawable.lua'''" in the '''"_S"''' folder and the code:
 
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SDrawable = Core.class()
+
SAnimation = Core.class()
  
function SDrawable:init(xtiny) -- tiny function
+
function SAnimation:init(xtiny)
xtiny.system(self) -- called only once on init (no update)
+
xtiny.processingSystem(self) -- called once on init and every frames
 +
self.sndstepgrass = Sound.new("audio/sfx/footstep/Grass02.wav")
 +
self.channel = self.sndstepgrass:play(0, false, true)
 
end
 
end
  
function SDrawable:filter(ent) -- tiny function
+
function SAnimation:filter(ent) -- tiny function
return ent.spritelayer and ent.sprite
+
return ent.animation
 
end
 
end
  
function SDrawable:onAdd(ent) -- tiny function
+
function SAnimation:onAdd(ent) -- tiny function
-- print("SDrawable:onAdd(ent)")
 
ent.spritelayer:addChild(ent.sprite)
 
 
end
 
end
  
function SDrawable:onRemove(ent) -- tiny function
+
function SAnimation:onRemove(ent) -- tiny function
-- print("SDrawable:onRemove(ent)")
+
ent.animation = nil -- free some memory?
ent.spritelayer:removeChild(ent.sprite)
 
-- cleaning?
 
ent.sprite = nil
 
ent = nil
 
 
end
 
end
</syntaxhighlight>
 
  
What it does:
+
local checkanim
* runs only once when it is called
+
function SAnimation:process(ent, dt) -- tiny function
* affects only entities which have a spritelayer variable (id) '''and''' a sprite variable (id)
+
-- a little boost?
* when an Entity is added to tiny-ecs '''World''', the '''System''' adds the Entity to its Sprite layer
+
local anim = ent.animation
* when an Entity is removed from tiny-ecs '''World''', the '''System''' removes the Entity from its Sprite layer
 
  
In other words, the '''System''' adds an Entity to a Sprite layer when the Entity is added to tiny-ecs World, and removes it from that Sprite layer when the Entity is destroyed.
+
-- checkanim = anim.curranim -- if you are sure all animations are set else use below ternary operator code
 +
-- luau ternary operator (no end at the end), it's a 1 liner and seems fast?
 +
checkanim = if anim.anims[ent.animation.curranim] then anim.curranim else g_ANIM_DEFAULT
 +
-- print(#anim.anims[checkanim])
  
== sPlayer1Control.lua ==
+
if not ent.doanimate then return end
I am adding systems in an order that seems logical and helps in understanding the making of the game. The next '''System''' we add is the player1 controller.
 
  
"'''sPlayer1Control.lua'''" in the '''"_S"''' folder. The code:
+
anim.animtimer -= dt
<syntaxhighlight lang="lua">
+
if anim.animtimer < 0 then
SPlayer1Control = Core.class()
+
anim.frame += 1
 
+
anim.animtimer = anim.animspeed
function SPlayer1Control:init(xtiny, xplayer1inputlayer) -- tiny function
+
if checkanim == g_ANIM_DEFAULT then
xtiny.system(self) -- called only once on init (no update)
+
if anim.frame > #anim.anims[checkanim] then
self.player1inputlayer = xplayer1inputlayer
+
anim.frame = 1
end
+
end
 
+
elseif checkanim == g_ANIM_LOSE1_R or checkanim == g_ANIM_STANDUP_R then
function SPlayer1Control:filter(ent) -- tiny function
+
if anim.frame >= #anim.anims[checkanim] then
return ent.isplayer1
+
anim.frame = #anim.anims[checkanim]
end
+
end
 
+
elseif checkanim == g_ANIM_PUNCH_ATTACK1_R then
function SPlayer1Control:onAdd(ent) -- tiny function
+
ent.headhitbox = ent.headhitboxattack1
self.player1inputlayer:addEventListener(Event.KEY_DOWN, function(e)
+
if #anim.anims[checkanim] == 1 then -- 1 frame animation
if ent.currlives > 0 then
+
anim.frame = 1
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = true end
+
ent.headhitboxattack1.isactive = true
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = true end
+
ent.isactionpunch1 = false
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = true end
+
else -- multi frames animation
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = true end
+
if anim.frame > #anim.anims[checkanim] then
-- ACTIONS:
+
-- anim.frame = 1
-- isactionpunch1, isactionpunch2, isactionjumppunch1,
+
anim.frame = #anim.anims[checkanim]
-- isactionkick1, isactionkick2, isactionjumpkick1,
+
ent.headhitboxattack1.isactive = false
-- isactionjump1
+
ent.isactionpunch1 = false
if e.keyCode == g_keyaction1 then
+
elseif anim.frame > ent.headhitbox.hitendframe then
ent.animation.frame = 0
+
ent.headhitboxattack1.isactive = false
ent.isactionpunch1 = true
+
elseif anim.frame >= ent.headhitbox.hitstartframe then
elseif e.keyCode == g_keyaction2 then
+
ent.headhitboxattack1.isactive = true
ent.animation.frame = 0
+
end
ent.isactionkick1 = true
+
end
 +
elseif checkanim == g_ANIM_PUNCH_ATTACK2_R then
 +
ent.headhitbox = ent.headhitboxattack2
 +
if anim.frame > #anim.anims[checkanim] then
 +
-- anim.frame = 1
 +
anim.frame = #anim.anims[checkanim]
 +
ent.headhitboxattack2.isactive = false
 +
ent.isactionpunch2 = false
 +
elseif anim.frame > ent.headhitbox.hitendframe then
 +
ent.headhitboxattack2.isactive = false
 +
elseif anim.frame >= ent.headhitbox.hitstartframe then
 +
ent.headhitboxattack2.isactive = true
 
end
 
end
if e.keyCode == g_keyaction3 then
+
elseif checkanim == g_ANIM_KICK_ATTACK1_R then
if ent.body.isonfloor then
+
ent.spinehitbox = ent.spinehitboxattack1
ent.animation.frame = 0
+
if #anim.anims[checkanim] == 1 then -- 1 frame animation
ent.positionystart = ent.pos.y
+
anim.frame = 1
ent.body.isonfloor = false
+
ent.spinehitboxattack1.isactive = true
ent.body.isgoingup = true
+
ent.isactionkick1 = false
ent.isactionjump1 = true
+
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
 +
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
 
end
end)
+
anim.bmp:setTextureRegion(anim.anims[checkanim][anim.frame])
self.player1inputlayer:addEventListener(Event.KEY_UP, function(e)
+
end
if ent.currlives > 0 then
 
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = false end
 
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = false end
 
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = false end
 
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = false end
 
-- if e.keyCode == g_keyaction1 then ent.isactionpunch1 = false end
 
-- if e.keyCode == g_keyaction2 then ent.isactionkick1 = false end
 
-- if e.keyCode == g_keyaction3 then end
 
end
 
end)
 
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
What it does:
+
What the System does:
* runs only once when it is called
+
* it runs every frame
* affects only entities with the ''isplayer1'' id
+
* it affects only entities with an ''animation'' id
* when the player1 Entity is added to tiny-ecs '''World''', the System registers KEY_DOWN and KEY_UP events
+
* it checks which animation to play
 +
* it decreases the ''animtimer''
 +
* and increases the current frame in the animation by 1
 +
* 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
  
The System processes the user keys input and sets various flags to be processed in a future ''sDynamicBodies'' System we will add.
+
== sDynamicBodies.lua ==
 +
"'''sDynamicBodies.lua'''" in the '''"_S"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
SDynamicBodies = Core.class()
  
== sPlayer1.lua ==
+
local random = math.random
"'''sPlayer1.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
SPlayer1 = Core.class()
 
  
function SPlayer1:init(xtiny, xcamera) -- tiny function
+
function SDynamicBodies:init(xtiny, xmapdef) -- tiny function
self.tiny = xtiny -- ref so we can remove entities from tiny system
+
self.tiny = xtiny -- to access self.tiny variables
 
self.tiny.processingSystem(self) -- called once on init and every update
 
self.tiny.processingSystem(self) -- called once on init and every update
-- fx
+
self.mapdef = xmapdef
self.camera = xcamera -- camera shake
 
-- sfx
 
self.snd = Sound.new("audio/sfx/sfx_deathscream_human14.wav")
 
self.channel = self.snd:play(0, false, true)
 
 
end
 
end
  
function SPlayer1:filter(ent) -- tiny function
+
function SDynamicBodies:filter(ent) -- tiny function
return ent.isplayer1
+
return ent.body -- only actors with body component
 
end
 
end
  
function SPlayer1:onAdd(ent) -- tiny function
+
function SDynamicBodies:onAdd(ent) -- tiny function
 
end
 
end
  
function SPlayer1:onRemove(ent) -- tiny function
+
function SDynamicBodies:onRemove(ent) -- tiny function
 
end
 
end
  
local resetanim = true
+
local randomdir = 0 -- for nmes
function SPlayer1:process(ent, dt) -- tiny function
+
function SDynamicBodies:process(ent, dt) -- tiny function
-- hurt fx
+
-- is dead or hurt?
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) then
+
if (ent.washurt and ent.washurt > 0) or (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) or ent.currlives <= 0 then
ent.washurt -= 1
+
ent.body.vx = 0
ent.animation.curranim = g_ANIM_HURT_R
+
ent.body.vy = 0
if ent.washurt < ent.recovertimer*0.5 then ent.hitfx:setVisible(false) end
+
return
if ent.washurt <= 0 then
+
-- TO REVISIT
ent.sprite:setColorTransform(1, 1, 1, 1)
+
end
self.camera:setZoom(1) -- zoom
+
-- 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
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 then
+
end
ent.hitfx:setVisible(false)
+
-- actions
ent.wasbadlyhurt -= 1
+
if ent.body.isonfloor then -- GROUND
ent.animation.curranim = g_ANIM_LOSE1_R
+
if ent.isactionpunch1 then
if ent.wasbadlyhurt < ent.recoverbadtimer/2 then
+
ent.animation.curranim = g_ANIM_PUNCH_ATTACK1_R
if resetanim then
+
ent.body.vx = 0 -- *= 0.1*dt, you choose
resetanim = false
+
ent.body.vy = 0 -- *= 0.1*dt, you choose
ent.animation.frame = 0
+
elseif ent.isactionpunch2 then
 +
ent.animation.curranim = g_ANIM_PUNCH_ATTACK2_R
 +
ent.body.vx = 0 -- *= 0.1*dt, you choose
 +
ent.body.vy = 0 -- *= 0.1*dt, you choose
 +
elseif ent.isactionkick1 then
 +
ent.animation.curranim = g_ANIM_KICK_ATTACK1_R
 +
ent.body.vx = 0 -- *= 0.1*dt, you choose
 +
ent.body.vy = 0 -- *= 0.1*dt, you choose
 +
elseif ent.isactionkick2 then
 +
ent.animation.curranim = g_ANIM_KICK_ATTACK2_R
 +
ent.body.vx = 0 -- *= 0.1*dt, you choose
 +
ent.body.vy = 0 -- *= 0.1*dt, you choose
 +
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
ent.animation.curranim = g_ANIM_STANDUP_R
 
 
end
 
end
if ent.wasbadlyhurt <= 0 then
+
if ent.isactionjump1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then -- JUMP ONLY
ent.sprite:setColorTransform(1, 1, 1, 1)
+
ent.animation.curranim = g_ANIM_JUMP1_R
self.camera:setZoom(1) -- zoom
+
if ent.isplayer1 then ent.body.vx *= 2 end -- 3, acceleration, you choose
resetanim = true
+
ent.body.currjumpspeed = ent.body.jumpspeed*1.1 -- higher jump
 +
if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
 +
if ent.pos.y < ent.positionystart - ent.h*0.5 then -- higher apex, you choose
 +
-- ent.body.vx = ent.body.currspeed*dt*4*ent.flip -- acceleration? you choose
 +
ent.body.vy += ent.body.currjumpspeed*1.2 -- 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)
 +
ent.body.isonfloor = true
 +
ent.isactionjump1 = false -- sometimes bug! XXX
 +
end
 
end
 
end
end
+
if ent.isactionjumppunch1 then -- JUMP PUNCH
if ent.isdirty then -- hit
+
ent.animation.curranim = g_ANIM_PUNCHJUMP_ATTACK1_R
local function map(v, minSrc, maxSrc, minDst, maxDst, clampValue)
+
if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
local newV = (v - minSrc) / (maxSrc - minSrc) * (maxDst - minDst) + minDst
+
ent.body.currjumpspeed = ent.body.jumpspeed
return not clampValue and newV or clamp(newV, minDst><maxDst, minDst<>maxDst)
+
if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
end
+
if ent.pos.y < ent.positionystart - ent.h*0.45 then -- apex, you choose
self.channel = self.snd:play()
+
-- ent.body.vx = ent.body.currspeed*dt*3*ent.flip -- acceleration? you choose
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
+
ent.body.vy += ent.body.currjumpspeed*1.2 -- falling
ent.hitfx:setVisible(true)
+
ent.body.isgoingup = false
ent.hitfx:setPosition(ent.pos.x+ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.pos.y+ent.headhurtbox.y-ent.headhurtbox.h/2)
+
end
ent.spritelayer:addChild(ent.hitfx)
+
if not ent.body.isgoingup and ent.pos.y >= ent.positionystart then -- grounded
ent.currhealth -= ent.damage
+
ent.body.vy = 0
local hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
+
-- ent.pos.y = ent.positionystart
self.tiny.hudhealth:setWidth(hudhealthwidth)
+
ent.pos = vector(ent.pos.x, ent.positionystart)
if ent.currhealth < ent.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000)
+
if ent.isnme then
elseif ent.currhealth < ent.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500)
+
randomdir = random(100)
else self.tiny.hudhealth:setColor(0x00ff00)
+
if randomdir < 50 then ent.isleft = false ent.isright = true
end
+
else ent.isleft = true ent.isright = false
ent.washurt = ent.recovertimer -- timer for a flash effect
+
end
ent.sprite:setColorTransform(2, 0, 0, 2) -- the flash effect (a bright red color)
+
end
ent.isdirty = false
+
if ent.isplayer1 then
self.camera:shake(0.6, 16) -- (duration, distance), you choose
+
ent.currjumps -= 1
self.camera:setZoom(1.2) -- zoom
+
if ent.currjumps < 0 then ent.currjumps = 0 end
if ent.currhealth <= 0 then
+
self.tiny.hudcurrjumps:setText("JUMPS: "..ent.currjumps)
ent.wasbadlyhurt = ent.recoverbadtimer -- timer for player1 to stand back up
+
end
self.camera:shake(0.8, 64) -- (duration, distance), you choose
+
ent.body.isonfloor = true
ent.currlives -= 1
+
ent.isactionjump1 = false
for i = 1, ent.totallives do self.tiny.hudlives[i]:setVisible(false) end -- dirty but easy XXX
+
ent.isactionpunch1 = false -- TEST
for i = 1, ent.currlives do self.tiny.hudlives[i]:setVisible(true) end -- dirty but easy XXX
+
ent.isactionjumppunch1 = false -- sometimes bug! XXX
if ent.currlives > 0 then
+
end
ent.currhealth = ent.totalhealth
+
elseif ent.isactionjumpkick1 then -- JUMP KICK
hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
+
ent.animation.curranim = g_ANIM_KICKJUMP_ATTACK1_R
self.tiny.hudhealth:setWidth(hudhealthwidth)
+
if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
self.tiny.hudhealth:setColor(0x00ff00)
+
ent.body.currjumpspeed = ent.body.jumpspeed
if ent.currlives == 1 then self.tiny.hudlives[1]:setColor(0xff0000) end
+
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
 +
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.isactionkick1 = false -- TEST
 +
ent.isactionjumpkick1 = false -- sometimes bug!
 
end
 
end
 
end
 
end
 
end
 
end
if ent.currlives <= 0 then -- deaded
+
-- catches the sometimes bug!
-- stop all movements
+
if not ent.body.isonfloor and ent.body.vy == 0 then
ent.isleft = false
+
print("bug", dt)
ent.isright = false
+
ent.body.vy += ent.body.currjumpspeed*16 -- makes the actor touch the ground
ent.isup = false
 
ent.isdown = false
 
-- play dead sequence
 
ent.isdirty = false
 
resetanim = false
 
ent.washurt = ent.recovertimer
 
ent.wasbadlyhurt = ent.recoverbadtimer
 
ent.animation.curranim = g_ANIM_LOSE1_R
 
ent.sprite:setColorTransform(255*0.5/255, 255*0.5/255, 255*0.5/255, 1)
 
self.camera:setZoom(1) -- zoom
 
ent.animation.bmp:setY(ent.animation.bmp:getY()-1)
 
if ent.animation.bmp:getY() < -200 then -- you choose
 
switchToScene(LevelX.new())
 
end
 
 
end
 
end
 
end
 
end
Line 248: Line 359:
 
"'''sNmes.lua'''" in the '''"_S"''' folder. The code:
 
"'''sNmes.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SNmes = Core.class()
 
 
local random = math.random
 
 
function SNmes:init(xtiny, xbump) -- tiny function
 
self.tiny = xtiny -- class ref so we can remove entities from tiny world
 
self.tiny.processingSystem(self) -- called once on init and every update
 
self.bworld = xbump
 
-- sfx
 
self.snd = Sound.new("audio/sfx/sfx_deathscream_human14.wav")
 
self.channel = self.snd:play(0, false, true)
 
end
 
 
function SNmes:filter(ent) -- tiny function
 
return ent.isnme
 
end
 
 
function SNmes:onAdd(ent) -- tiny function
 
ent.flip = math.random(100)
 
if ent.flip > 50 then ent.flip = 1 else ent.flip = -1 end
 
ent.currlives = ent.totallives
 
ent.currhealth = ent.totalhealth
 
ent.washurt = 0
 
ent.wasbadlyhurt = 0
 
ent.isdead = false
 
ent.curractiontimer = ent.actiontimer
 
ent.positionystart = 0
 
-- abilities
 
ent.abilities = {}
 
if ent.headhitboxattack1 then ent.abilities[#ent.abilities+1] = 1 end -- punch1
 
if ent.headhitboxattack2 then ent.abilities[#ent.abilities+1] = 2 end -- punch2
 
if ent.spinehitboxattack1 then ent.abilities[#ent.abilities+1] = 3 end -- kick1
 
if ent.spinehitboxattack2 then ent.abilities[#ent.abilities+1] = 4 end -- kick2
 
if ent.headhitboxjattack1 then ent.abilities[#ent.abilities+1] = 5 end -- jumppunch1
 
if ent.spinehitboxjattack1 then ent.abilities[#ent.abilities+1] = 6 end -- jumpkick1
 
-- for k, v in pairs(ent.abilities) do print(k, v) end
 
end
 
 
function SNmes:onRemove(ent) -- tiny function
 
self.bworld:remove(ent) -- remove collision box from cbump world here!
 
end
 
 
local resetanim = true
 
function SNmes:process(ent, dt) -- tiny function
 
-- hurt fx
 
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) and not ent.isdead then
 
ent.washurt -= 1
 
ent.animation.curranim = g_ANIM_HURT_R
 
if ent.washurt < ent.recovertimer*0.5 then ent.hitfx:setVisible(false) end
 
if ent.washurt <= 0 then ent.sprite:setColorTransform(1, 1, 1, 1) end
 
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 and not ent.isdead then
 
ent.wasbadlyhurt -= 1
 
ent.animation.curranim = g_ANIM_LOSE1_R
 
if ent.wasbadlyhurt < ent.recoverbadtimer*0.5 then
 
ent.hitfx:setVisible(false)
 
if resetanim then
 
resetanim = false
 
ent.animation.frame = 0
 
end
 
ent.animation.curranim = g_ANIM_STANDUP_R
 
end
 
if ent.wasbadlyhurt <= 0 then
 
ent.sprite:setColorTransform(1, 1, 1, 1)
 
resetanim = true
 
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.x+ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.pos.y+ent.headhurtbox.y-ent.headhurtbox.h/2)
 
ent.spritelayer:addChild(ent.hitfx)
 
ent.currhealth -= ent.damage
 
ent.washurt = ent.recovertimer -- timer for a flash effect
 
-- ent.sprite:setColorTransform(0, 0, 2, 3) -- the flash effect (a bright red color)
 
ent.isdirty = false
 
if ent.currhealth <= 0 then
 
ent.wasbadlyhurt = ent.recoverbadtimer -- timer for actor to stand back up
 
ent.currlives -= 1
 
if ent.currlives > 0 then ent.currhealth = ent.totalhealth end
 
end
 
end
 
if ent.currlives <= 0 then -- deaded
 
-- stop all movements
 
ent.isleft = false
 
ent.isright = false
 
ent.isup = false
 
ent.isdown = false
 
-- play dead sequence
 
ent.isdirty = false
 
ent.washurt = ent.recovertimer
 
ent.wasbadlyhurt = ent.recoverbadtimer
 
-- blood
 
if not ent.isdead then
 
ent.hitfx:setVisible(true)
 
ent.hitfx:setColorTransform(3, 0, 0, random(1, 3)/10) -- blood stain
 
ent.hitfx:setPosition(ent.pos.x+ent.collbox.w/2, ent.pos.y)
 
ent.hitfx:setRotation(random(360))
 
ent.hitfx:setScale(random(5, 8)/10)
 
ent.bgfxlayer:addChild(ent.hitfx)
 
ent.isdead = true
 
end
 
ent.animation.curranim = g_ANIM_LOSE1_R
 
resetanim = false -- ??? XXX
 
ent.sprite:setColorTransform((-ent.pos.y<>ent.pos.y)/255, (-ent.pos.y<>ent.pos.y)/255, 0, 1)
 
ent.shadow.sprite:setVisible(false)
 
ent.pos -= vector(8*ent.flip, 8)
 
ent.sprite:setPosition(ent.pos)
 
ent.sprite:setScale(ent.sprite:getScale()+0.07)
 
if ent.pos.y < -256 then
 
self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
 
self.tiny.numberofnmes -= 1
 
end
 
end
 
end
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 378: Line 374:
 
"'''sAI.lua'''" in the '''"_S"''' folder. The code:
 
"'''sAI.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SAI = Core.class()
 
 
local random = math.random
 
 
function SAI:init(xtiny, xplayer1) -- tiny function
 
xtiny.processingSystem(self) -- called once on init and every update
 
self.player1 = xplayer1
 
end
 
 
function SAI:filter(ent) -- tiny function
 
return ent.ai
 
end
 
 
function SAI:onAdd(ent) -- tiny function
 
end
 
 
function SAI:onRemove(ent) -- tiny function
 
end
 
 
local p1rangex = 192 -- 192 -- magik XXX
 
local p1hitrangex = 64 -- magik XXX
 
local p1rangey = 96 -- 96 -- magik XXX
 
local p1hitrangey = 16 -- magik XXX
 
local rndaction = 0 -- random punch/kick action
 
local p1rangetoofar = myappwidth -- disable system to save some CPU, magik XXX
 
function SAI:process(ent, dt) -- tiny function
 
if ent.isdead then return end
 
local function fun()
 
-- some flags
 
ent.doanimate = true -- to save some cpu
 
ent.readytohit = false
 
if (ent.pos.x > self.player1.pos.x + p1rangetoofar or ent.pos.x < self.player1.pos.x - p1rangetoofar) then
 
ent.doanimate = false
 
return
 
end
 
if (ent.pos.x > self.player1.pos.x + p1rangex or ent.pos.x < self.player1.pos.x - p1rangex) or
 
(ent.pos.y > self.player1.pos.y + p1rangey or ent.pos.y < self.player1.pos.y - p1rangey) then -- OUTSIDE ATTACK RANGE
 
-- idle
 
ent.isleft, ent.isright = false, false
 
ent.isup, ent.isdown = false, false
 
ent.body.currspeed = ent.body.speed
 
ent.body.currjumpspeed = ent.body.jumpspeed
 
else -- INSIDE ATTACK RANGE
 
-- x
 
if ent.pos.x > random(self.player1.pos.x+p1hitrangex, self.player1.pos.x+p1rangex) then
 
ent.isleft, ent.isright = true, false
 
ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX
 
elseif ent.pos.x < random(self.player1.pos.x-p1rangex, self.player1.pos.x-p1hitrangex) then
 
ent.isleft, ent.isright = false, true
 
ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX
 
end
 
-- y
 
if ent.pos.y > random(self.player1.pos.y, self.player1.pos.y+p1hitrangey) then
 
ent.isup, ent.isdown = true, false
 
ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX
 
ent.readytohit = true
 
elseif ent.pos.y < random(self.player1.pos.y-p1hitrangey, self.player1.pos.y) then
 
ent.isup, ent.isdown = false, true
 
ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX
 
ent.readytohit = true
 
end
 
-- nmes always face player1
 
if not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
if ent.pos.x > self.player1.pos.x then ent.flip = -1
 
else ent.flip = 1
 
end
 
end
 
end
 
-- ATTACK
 
if ent.readytohit then
 
ent.curractiontimer -= 1
 
if ent.curractiontimer < 0 then
 
ent.animation.frame = 0
 
rndaction = ent.abilities[random(#ent.abilities)] -- pick a random attack
 
if rndaction == 1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.isactionpunch1 = true
 
elseif rndaction == 2 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.isactionpunch2 = true
 
elseif rndaction == 3 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.isactionkick1 = true
 
elseif rndaction == 4 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.isactionkick2 = true
 
elseif rndaction == 5 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.positionystart = ent.pos.y
 
ent.body.isonfloor = false
 
ent.body.isgoingup = true
 
ent.isactionjumppunch1 = true
 
ent.body.currspeed *= 1+random(4)*0.1 -- randomize speed, you choose to add it and the params
 
-- jump in the direction of the flip
 
if ent.flip == 1 then ent.isleft = false ent.isright = true
 
else ent.isleft = true ent.isright = false
 
end
 
elseif rndaction == 6 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
 
ent.positionystart = ent.pos.y
 
ent.body.isonfloor = false
 
ent.body.isgoingup = true
 
ent.isactionjumpkick1 = true
 
ent.body.currspeed *= 1+random(3)*0.1 -- randomize speed, you choose to add it and the params
 
-- jump in the direction of the flip
 
if ent.flip == 1 then ent.isleft = false ent.isright = true
 
else ent.isleft = true ent.isright = false
 
end
 
end
 
ent.curractiontimer = ent.actiontimer
 
end
 
end
 
Core.yield(1)
 
end
 
Core.asyncCall(fun) -- profiler seems to be faster without asyncCall (because of pairs traversing?)
 
end
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  
Line 495: Line 381:
 
Let's quickly add the Shadow System. "'''sShadow.lua'''" in the '''"_S"''' folder. The code:
 
Let's quickly add the Shadow System. "'''sShadow.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
SShadow = Core.class()
 
 
function SShadow:init(xtiny) -- tiny function
 
xtiny.processingSystem(self) -- called once on init and every update
 
end
 
 
function SShadow:filter(ent) -- tiny function
 
return ent.shadow
 
end
 
 
function SShadow:onAdd(ent) -- tiny function
 
ent.spritelayer:addChildAt(ent.shadow.sprite, 1) -- add shadow behind ent
 
end
 
 
function SShadow:onRemove(ent) -- tiny function
 
ent.spritelayer:removeChild(ent.shadow.sprite)
 
end
 
 
function SShadow:process(ent, dt) -- tiny function
 
local function fun()
 
ent.shadow.sprite:setPosition(ent.pos+vector(ent.collbox.w/2, ent.collbox.h/2))
 
if ent.body and not ent.body.isonfloor then
 
ent.shadow.sprite:setPosition(ent.pos.x+ent.collbox.w/2, ent.positionystart+ent.collbox.h/2)
 
end
 
Core.yield(1)
 
end
 
Core.asyncCall(fun)
 
end
 
 
</syntaxhighlight>
 
</syntaxhighlight>
  

Revision as of 23:22, 22 November 2024

The Systems 2

We continue adding systems to our game.

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

Time to animate our actors. Please create a file "sAnimation.lua" in the "_S" folder and the code:

SAnimation = Core.class()

function SAnimation:init(xtiny)
	xtiny.processingSystem(self) -- called once on init and every frames
	self.sndstepgrass = Sound.new("audio/sfx/footstep/Grass02.wav")
	self.channel = self.sndstepgrass:play(0, false, true)
end

function SAnimation:filter(ent) -- tiny function
	return ent.animation
end

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

function SAnimation:onRemove(ent) -- tiny function
	ent.animation = nil -- free some memory?
end

local checkanim
function SAnimation:process(ent, dt) -- tiny function
	-- a little boost?
	local anim = ent.animation

--	checkanim = anim.curranim -- if you are sure all animations are set else use below ternary operator code
	-- luau ternary operator (no end at the end), it's a 1 liner and seems fast?
	checkanim = if anim.anims[ent.animation.curranim] then anim.curranim else g_ANIM_DEFAULT
--	print(#anim.anims[checkanim])

	if not ent.doanimate then return end

	anim.animtimer -= dt
	if anim.animtimer < 0 then
		anim.frame += 1
		anim.animtimer = anim.animspeed
		if checkanim == g_ANIM_DEFAULT then
			if anim.frame > #anim.anims[checkanim] then
				anim.frame = 1
			end
		elseif checkanim == g_ANIM_LOSE1_R or checkanim == g_ANIM_STANDUP_R then
			if anim.frame >= #anim.anims[checkanim] then
				anim.frame = #anim.anims[checkanim]
			end
		elseif checkanim == g_ANIM_PUNCH_ATTACK1_R then
			ent.headhitbox = ent.headhitboxattack1
			if #anim.anims[checkanim] == 1 then -- 1 frame animation
				anim.frame = 1
				ent.headhitboxattack1.isactive = true
				ent.isactionpunch1 = false
			else -- multi frames animation
				if anim.frame > #anim.anims[checkanim] then
--					anim.frame = 1
					anim.frame = #anim.anims[checkanim]
					ent.headhitboxattack1.isactive = false
					ent.isactionpunch1 = false
				elseif anim.frame > ent.headhitbox.hitendframe then
					ent.headhitboxattack1.isactive = false
				elseif anim.frame >= ent.headhitbox.hitstartframe then
					ent.headhitboxattack1.isactive = true
				end
			end
		elseif checkanim == g_ANIM_PUNCH_ATTACK2_R then
			ent.headhitbox = ent.headhitboxattack2
			if anim.frame > #anim.anims[checkanim] then
--				anim.frame = 1
				anim.frame = #anim.anims[checkanim]
				ent.headhitboxattack2.isactive = false
				ent.isactionpunch2 = false
			elseif anim.frame > ent.headhitbox.hitendframe then
				ent.headhitboxattack2.isactive = false
			elseif anim.frame >= ent.headhitbox.hitstartframe then
				ent.headhitboxattack2.isactive = true
			end
		elseif checkanim == g_ANIM_KICK_ATTACK1_R then
			ent.spinehitbox = ent.spinehitboxattack1
			if #anim.anims[checkanim] == 1 then -- 1 frame animation
				anim.frame = 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
		anim.bmp:setTextureRegion(anim.anims[checkanim][anim.frame])
	end
end

What the System does:

  • it runs every frame
  • it affects only entities with an animation id
  • it checks which animation to play
  • it decreases the animtimer
  • and increases the current frame in the animation by 1
  • 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

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

SDynamicBodies = Core.class()

local random = math.random

function SDynamicBodies:init(xtiny, xmapdef) -- tiny function
	self.tiny = xtiny -- to access self.tiny variables
	self.tiny.processingSystem(self) -- called once on init and every update
	self.mapdef = xmapdef
end

function SDynamicBodies:filter(ent) -- tiny function
	return ent.body -- only actors with body component
end

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

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

local randomdir = 0 -- for nmes
function SDynamicBodies:process(ent, dt) -- tiny function
	-- is dead or hurt?
	if (ent.washurt and ent.washurt > 0) or (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) or ent.currlives <= 0 then
		ent.body.vx = 0
		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
	-- actions
	if ent.body.isonfloor then -- GROUND
		if ent.isactionpunch1 then
			ent.animation.curranim = g_ANIM_PUNCH_ATTACK1_R
			ent.body.vx = 0 -- *= 0.1*dt, you choose
			ent.body.vy = 0 -- *= 0.1*dt, you choose
		elseif ent.isactionpunch2 then
			ent.animation.curranim = g_ANIM_PUNCH_ATTACK2_R
			ent.body.vx = 0 -- *= 0.1*dt, you choose
			ent.body.vy = 0 -- *= 0.1*dt, you choose
		elseif ent.isactionkick1 then
			ent.animation.curranim = g_ANIM_KICK_ATTACK1_R
			ent.body.vx = 0 -- *= 0.1*dt, you choose
			ent.body.vy = 0 -- *= 0.1*dt, you choose
		elseif ent.isactionkick2 then
			ent.animation.curranim = g_ANIM_KICK_ATTACK2_R
			ent.body.vx = 0 -- *= 0.1*dt, you choose
			ent.body.vy = 0 -- *= 0.1*dt, you choose
		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
		if ent.isactionjump1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then -- JUMP ONLY
			ent.animation.curranim = g_ANIM_JUMP1_R
			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.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
			if ent.pos.y < ent.positionystart - ent.h*0.5 then -- higher apex, you choose
--				ent.body.vx = ent.body.currspeed*dt*4*ent.flip -- acceleration? you choose
				ent.body.vy += ent.body.currjumpspeed*1.2 -- 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)
				ent.body.isonfloor = true
				ent.isactionjump1 = false -- sometimes bug! XXX
			end
		end
		if ent.isactionjumppunch1 then -- JUMP PUNCH
			ent.animation.curranim = g_ANIM_PUNCHJUMP_ATTACK1_R
			if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
			ent.body.currjumpspeed = ent.body.jumpspeed
			if ent.body.isgoingup then ent.body.vy -= ent.body.currjumpspeed end
			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
				ent.body.vy += ent.body.currjumpspeed*1.2 -- 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
				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
		elseif ent.isactionjumpkick1 then -- JUMP KICK
			ent.animation.curranim = g_ANIM_KICKJUMP_ATTACK1_R
			if ent.isplayer1 then ent.body.vx *= 2 end -- acceleration, you choose
			ent.body.currjumpspeed = ent.body.jumpspeed
			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
				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.isactionkick1 = false -- TEST
				ent.isactionjumpkick1 = false -- sometimes bug!
			end
		end
	end
	-- catches the sometimes bug!
	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

This System deals with the player1 being hit or killed:

  • runs once on init and every game loop (process)
  • in init we add the camera and a sound to add some juice to the game
  • there are 2 kind of hurt animations depending on the player1 health (washurt and wasbadlyhurt)
  • when the player1 is hit, we add a camera shake and play some sound
  • we update the HUD
  • when the player1 is dead we play a death sequence and restart the current level

sNmes.lua

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

This System deals with the enemies being hit or killed:

  • runs once on init and every game loop (process)
  • in init we add a sound to add some juice to the game
  • onAdd some explanation below
  • when an enemy is hit, we play some sound

In the onAdd function it is worth noting that instead of creating all the variables for the enemies in their Entity code, I found it 'clever' to put them in the enemy System as they all share the same variables.

The other thing worth noting is an enemy ability. Depending on the attacks an Entity has, they are stored in an abilities table. In a artificial intelligence System we will add shortly, the code will iterate the abilities table and pick a random attack an Entity can perform.

sAI.lua

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

This System controls all the entities with an artificial intelligence (ai) id. In this System an entity can be in an idle state, a move state or an attack state. Each states are applied relative to the distance between the Entity and the player1.

sShadow.lua

Let's quickly add the Shadow System. "sShadow.lua" in the "_S" folder. The code:

This System adds a shadow below an Entity. If the Entity is in a jump state, we update the shadow only on the x axis.

Next?

Systems are fairly straight forward and fairly short for what they can achieve.

We have covered the first set of systems, we have a couple more to add.


Prev.: Tuto tiny-ecs beatemup Part 9 Systems
Next: Tuto tiny-ecs beatemup Part 11 XXX


Tutorial - tiny-ecs beatemup