Difference between revisions of "Tuto tiny-ecs beatemup Part 10 Systems 2"
(wip) |
(wip) |
||
Line 356: | Line 356: | ||
* finally sometimes an actor doesn't land after a jump attack so we force it to do so | * finally sometimes an actor doesn't land after a jump attack so we force it to do so | ||
− | == | + | == sHitboxHurtboxCollision.lua == |
− | "''' | + | "'''sHitboxHurtboxCollision.lua'''" in the '''"_S"''' folder. The code: |
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
+ | SHitboxHurtboxCollision = Core.class() | ||
+ | |||
+ | function SHitboxHurtboxCollision:init(xtiny) -- tiny function | ||
+ | xtiny.processingSystem(self) -- called once on init and every update | ||
+ | self.spriteslist = xtiny.spriteslist | ||
+ | end | ||
+ | |||
+ | function SHitboxHurtboxCollision:filter(ent) -- tiny function | ||
+ | return ent.headhitbox or ent.spinehitbox or ent.headhurtbox or ent.spinehurtbox | ||
+ | end | ||
+ | |||
+ | function SHitboxHurtboxCollision:onAdd(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | function SHitboxHurtboxCollision:onRemove(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | function SHitboxHurtboxCollision:process(ent, dt) -- tiny function | ||
+ | local function checkY(actor1y, actor2y, prange) | ||
+ | return (-(actor1y-actor2y)<>(actor1y-actor2y)) < prange | ||
+ | end | ||
+ | local function checkCollision(box1x, box1y, box1w, box1h, box2x, box2y, box2w, box2h) | ||
+ | return not (box1x - box1w/2 > box2x + box2w/2 or -- is box1 on the right side of box2? | ||
+ | box1y - box1h/2 > box2y + box2h/2 or -- is box1 under box2? | ||
+ | box1x + box1w/2 < box2x - box2w/2 or -- is box1 on the left side of box2? | ||
+ | box1y + box1h/2 < box2y - box2h/2) -- is box1 above box2? | ||
+ | end | ||
+ | if ent.headhitbox and ent.headhitbox.isactive then -- HEAD | ||
+ | for k, _ in pairs(self.spriteslist) do -- k = entity, v = true | ||
+ | -- 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 head 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.isactionjumppunch1 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.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 | ||
+ | elseif (ent.isactionjumppunch1 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.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 | ||
+ | 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 | ||
+ | elseif ent.spinehitbox and ent.spinehitbox.isactive then -- SPINE | ||
+ | for k, _ in pairs(self.spriteslist) do | ||
+ | -- 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 | ||
+ | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | This System deals | + | This System checks if a hitbox and a hurtbox overlap then deals damages to the hurt actor: |
* runs once on init and '''every game loop''' (''process'') | * runs once on init and '''every game loop''' (''process'') | ||
− | * in ''init'' we | + | * 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''' |
− | * when an | + | * 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 | ||
− | + | == XXX.lua == | |
− | |||
− | |||
− | |||
− | == | ||
"'''sAI.lua'''" in the '''"_S"''' folder. The code: | "'''sAI.lua'''" in the '''"_S"''' folder. The code: | ||
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> |
Revision as of 20:05, 23 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 is reponsible for moving the actors:
- runs once on init and every game loop (process)
- in init we add the map definition to constraint the actors movement
- 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)
- then there are the attacks: ground attacks and jump attacks
- finally sometimes an actor doesn't land after a jump attack so we force it to do so
sHitboxHurtboxCollision.lua
"sHitboxHurtboxCollision.lua" in the "_S" folder. The code:
SHitboxHurtboxCollision = Core.class()
function SHitboxHurtboxCollision:init(xtiny) -- tiny function
xtiny.processingSystem(self) -- called once on init and every update
self.spriteslist = xtiny.spriteslist
end
function SHitboxHurtboxCollision:filter(ent) -- tiny function
return ent.headhitbox or ent.spinehitbox or ent.headhurtbox or ent.spinehurtbox
end
function SHitboxHurtboxCollision:onAdd(ent) -- tiny function
end
function SHitboxHurtboxCollision:onRemove(ent) -- tiny function
end
function SHitboxHurtboxCollision:process(ent, dt) -- tiny function
local function checkY(actor1y, actor2y, prange)
return (-(actor1y-actor2y)<>(actor1y-actor2y)) < prange
end
local function checkCollision(box1x, box1y, box1w, box1h, box2x, box2y, box2w, box2h)
return not (box1x - box1w/2 > box2x + box2w/2 or -- is box1 on the right side of box2?
box1y - box1h/2 > box2y + box2h/2 or -- is box1 under box2?
box1x + box1w/2 < box2x - box2w/2 or -- is box1 on the left side of box2?
box1y + box1h/2 < box2y - box2h/2) -- is box1 above box2?
end
if ent.headhitbox and ent.headhitbox.isactive then -- HEAD
for k, _ in pairs(self.spriteslist) do -- k = entity, v = true
-- 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 head 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.isactionjumppunch1 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.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
elseif (ent.isactionjumppunch1 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.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
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
elseif ent.spinehitbox and ent.spinehitbox.isactive then -- SPINE
for k, _ in pairs(self.spriteslist) do
-- 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
end
This System checks if a hitbox and a hurtbox overlap then deals damages to the hurt actor:
- 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
XXX.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