Difference between revisions of "Tuto tiny-ecs beatemup Part 9 Systems"
(wip) |
(wip) |
||
(One intermediate revision by the same user not shown) | |||
Line 54: | Line 54: | ||
* runs only once when it is called | * runs only once when it is called | ||
* affects only entities which have a spritelayer variable (id) '''and''' a sprite variable (id) | * affects only entities which have a spritelayer variable (id) '''and''' a sprite variable (id) | ||
− | * when an Entity is added to tiny-ecs '''World''', the System adds the Entity to its | + | * when an Entity is added to tiny-ecs '''World''', the '''System''' adds the Entity to its Sprite layer |
− | * when an Entity is removed from tiny-ecs '''World''', the System removes the Entity from its | + | * 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. | |
− | == | + | == sPlayer1Control.lua == |
− | "''' | + | 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: | ||
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
+ | SPlayer1Control = Core.class() | ||
+ | |||
+ | function SPlayer1Control:init(xtiny, xplayer1inputlayer) -- tiny function | ||
+ | xtiny.system(self) -- called only once on init (no update) | ||
+ | self.player1inputlayer = xplayer1inputlayer | ||
+ | end | ||
+ | |||
+ | function SPlayer1Control:filter(ent) -- tiny function | ||
+ | return ent.isplayer1 | ||
+ | end | ||
+ | |||
+ | function SPlayer1Control:onAdd(ent) -- tiny function | ||
+ | self.player1inputlayer:addEventListener(Event.KEY_DOWN, function(e) | ||
+ | if ent.currlives > 0 then | ||
+ | if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = true end | ||
+ | if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = true end | ||
+ | if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = true end | ||
+ | if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = true end | ||
+ | -- ACTIONS: | ||
+ | -- isactionpunch1, isactionpunch2, isactionjumppunch1, | ||
+ | -- isactionkick1, isactionkick2, isactionjumpkick1, | ||
+ | -- isactionjump1 | ||
+ | if e.keyCode == g_keyaction1 then | ||
+ | ent.animation.frame = 0 | ||
+ | ent.isactionpunch1 = true | ||
+ | elseif e.keyCode == g_keyaction2 then | ||
+ | ent.animation.frame = 0 | ||
+ | ent.isactionkick1 = true | ||
+ | end | ||
+ | if e.keyCode == g_keyaction3 then | ||
+ | if ent.body.isonfloor then | ||
+ | ent.animation.frame = 0 | ||
+ | ent.positionystart = ent.pos.y | ||
+ | ent.body.isonfloor = false | ||
+ | ent.body.isgoingup = true | ||
+ | ent.isactionjump1 = true | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | end) | ||
+ | self.player1inputlayer:addEventListener(Event.KEY_UP, function(e) | ||
+ | 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 | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | + | What it does: | |
+ | * runs only once when it is called | ||
+ | * affects only entities with the ''isplayer1'' id | ||
+ | * when the player1 Entity is added to tiny-ecs '''World''', the System registers KEY_DOWN and KEY_UP events | ||
+ | |||
+ | The System processes the user keys input and sets various flags to be processed in a future ''sDynamicBodies'' System we will add. | ||
+ | |||
+ | == sPlayer1.lua == | ||
+ | "'''sPlayer1.lua'''" in the '''"_S"''' folder. The code: | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | SPlayer1 = Core.class() | ||
+ | |||
+ | function SPlayer1:init(xtiny, xcamera) -- 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 | ||
+ | -- fx | ||
+ | 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 | ||
+ | |||
+ | function SPlayer1:filter(ent) -- tiny function | ||
+ | return ent.isplayer1 | ||
+ | end | ||
+ | |||
+ | function SPlayer1:onAdd(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | function SPlayer1:onRemove(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | local resetanim = true | ||
+ | function SPlayer1:process(ent, dt) -- tiny function | ||
+ | -- hurt fx | ||
+ | if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) 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) | ||
+ | self.camera:setZoom(1) -- zoom | ||
+ | end | ||
+ | elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 then | ||
+ | ent.hitfx:setVisible(false) | ||
+ | ent.wasbadlyhurt -= 1 | ||
+ | ent.animation.curranim = g_ANIM_LOSE1_R | ||
+ | if ent.wasbadlyhurt < ent.recoverbadtimer/2 then | ||
+ | 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) | ||
+ | self.camera:setZoom(1) -- zoom | ||
+ | resetanim = true | ||
+ | end | ||
+ | end | ||
+ | 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.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 | ||
+ | local hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100) | ||
+ | self.tiny.hudhealth:setWidth(hudhealthwidth) | ||
+ | if ent.currhealth < ent.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000) | ||
+ | elseif ent.currhealth < ent.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500) | ||
+ | else self.tiny.hudhealth:setColor(0x00ff00) | ||
+ | end | ||
+ | ent.washurt = ent.recovertimer -- timer for a flash effect | ||
+ | ent.sprite:setColorTransform(2, 0, 0, 2) -- the flash effect (a bright red color) | ||
+ | ent.isdirty = false | ||
+ | self.camera:shake(0.6, 16) -- (duration, distance), you choose | ||
+ | self.camera:setZoom(1.2) -- zoom | ||
+ | if ent.currhealth <= 0 then | ||
+ | ent.wasbadlyhurt = ent.recoverbadtimer -- timer for player1 to stand back up | ||
+ | self.camera:shake(0.8, 64) -- (duration, distance), you choose | ||
+ | ent.currlives -= 1 | ||
+ | for i = 1, ent.totallives do self.tiny.hudlives[i]:setVisible(false) end -- dirty but easy XXX | ||
+ | for i = 1, ent.currlives do self.tiny.hudlives[i]:setVisible(true) end -- dirty but easy XXX | ||
+ | if ent.currlives > 0 then | ||
+ | ent.currhealth = ent.totalhealth | ||
+ | hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100) | ||
+ | self.tiny.hudhealth:setWidth(hudhealthwidth) | ||
+ | self.tiny.hudhealth:setColor(0x00ff00) | ||
+ | if ent.currlives == 1 then self.tiny.hudlives[1]:setColor(0xff0000) end | ||
+ | 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 | ||
+ | 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 | ||
+ | </syntaxhighlight> | ||
+ | |||
+ | 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: | ||
+ | <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> | ||
+ | |||
+ | 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. | ||
+ | |||
+ | == XXX.lua == | ||
+ | "'''XXX.lua'''" in the '''"_S"''' folder. The code: | ||
+ | <syntaxhighlight lang="lua"> | ||
+ | </syntaxhighlight> | ||
− | + | 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 | ||
== Next? == | == Next? == |
Latest revision as of 23:54, 21 November 2024
The Systems
We have our entities, we have our components, now the systems. What is an ECS System?
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
That looks scary but worry not we won't use all the callback functions :-)
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:
SDrawable = Core.class()
function SDrawable:init(xtiny) -- tiny function
xtiny.system(self) -- called only once on init (no update)
end
function SDrawable:filter(ent) -- tiny function
return ent.spritelayer and ent.sprite
end
function SDrawable:onAdd(ent) -- tiny function
-- print("SDrawable:onAdd(ent)")
ent.spritelayer:addChild(ent.sprite)
end
function SDrawable:onRemove(ent) -- tiny function
-- print("SDrawable:onRemove(ent)")
ent.spritelayer:removeChild(ent.sprite)
-- cleaning?
ent.sprite = nil
ent = nil
end
What it does:
- runs only once when it is called
- affects only entities which have a spritelayer variable (id) and a sprite variable (id)
- when an Entity is added to tiny-ecs World, the System adds the Entity to its Sprite layer
- when an Entity is removed from tiny-ecs World, the System removes the Entity from its Sprite layer
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.
sPlayer1Control.lua
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:
SPlayer1Control = Core.class()
function SPlayer1Control:init(xtiny, xplayer1inputlayer) -- tiny function
xtiny.system(self) -- called only once on init (no update)
self.player1inputlayer = xplayer1inputlayer
end
function SPlayer1Control:filter(ent) -- tiny function
return ent.isplayer1
end
function SPlayer1Control:onAdd(ent) -- tiny function
self.player1inputlayer:addEventListener(Event.KEY_DOWN, function(e)
if ent.currlives > 0 then
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = true end
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = true end
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = true end
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = true end
-- ACTIONS:
-- isactionpunch1, isactionpunch2, isactionjumppunch1,
-- isactionkick1, isactionkick2, isactionjumpkick1,
-- isactionjump1
if e.keyCode == g_keyaction1 then
ent.animation.frame = 0
ent.isactionpunch1 = true
elseif e.keyCode == g_keyaction2 then
ent.animation.frame = 0
ent.isactionkick1 = true
end
if e.keyCode == g_keyaction3 then
if ent.body.isonfloor then
ent.animation.frame = 0
ent.positionystart = ent.pos.y
ent.body.isonfloor = false
ent.body.isgoingup = true
ent.isactionjump1 = true
end
end
end
end)
self.player1inputlayer:addEventListener(Event.KEY_UP, function(e)
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
What it does:
- runs only once when it is called
- affects only entities with the isplayer1 id
- when the player1 Entity is added to tiny-ecs World, the System registers KEY_DOWN and KEY_UP events
The System processes the user keys input and sets various flags to be processed in a future sDynamicBodies System we will add.
sPlayer1.lua
"sPlayer1.lua" in the "_S" folder. The code:
SPlayer1 = Core.class()
function SPlayer1:init(xtiny, xcamera) -- 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
-- fx
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
function SPlayer1:filter(ent) -- tiny function
return ent.isplayer1
end
function SPlayer1:onAdd(ent) -- tiny function
end
function SPlayer1:onRemove(ent) -- tiny function
end
local resetanim = true
function SPlayer1:process(ent, dt) -- tiny function
-- hurt fx
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) 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)
self.camera:setZoom(1) -- zoom
end
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 then
ent.hitfx:setVisible(false)
ent.wasbadlyhurt -= 1
ent.animation.curranim = g_ANIM_LOSE1_R
if ent.wasbadlyhurt < ent.recoverbadtimer/2 then
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)
self.camera:setZoom(1) -- zoom
resetanim = true
end
end
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.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
local hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
self.tiny.hudhealth:setWidth(hudhealthwidth)
if ent.currhealth < ent.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000)
elseif ent.currhealth < ent.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500)
else self.tiny.hudhealth:setColor(0x00ff00)
end
ent.washurt = ent.recovertimer -- timer for a flash effect
ent.sprite:setColorTransform(2, 0, 0, 2) -- the flash effect (a bright red color)
ent.isdirty = false
self.camera:shake(0.6, 16) -- (duration, distance), you choose
self.camera:setZoom(1.2) -- zoom
if ent.currhealth <= 0 then
ent.wasbadlyhurt = ent.recoverbadtimer -- timer for player1 to stand back up
self.camera:shake(0.8, 64) -- (duration, distance), you choose
ent.currlives -= 1
for i = 1, ent.totallives do self.tiny.hudlives[i]:setVisible(false) end -- dirty but easy XXX
for i = 1, ent.currlives do self.tiny.hudlives[i]:setVisible(true) end -- dirty but easy XXX
if ent.currlives > 0 then
ent.currhealth = ent.totalhealth
hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
self.tiny.hudhealth:setWidth(hudhealthwidth)
self.tiny.hudhealth:setColor(0x00ff00)
if ent.currlives == 1 then self.tiny.hudlives[1]:setColor(0xff0000) end
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
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
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:
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
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.
XXX.lua
"XXX.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
Next?
The time has come to tackle the systems. I will try to make it easy :-)
Prev.: Tuto tiny-ecs beatemup Part 8 Breakables
Next: Tuto tiny-ecs beatemup Part 10 XXX