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

From GiderosMobile
(wip)
 
(wip)
 
(2 intermediate revisions by the same user not shown)
Line 1: Line 1:
 
__TOC__
 
__TOC__
  
== The last ones ==
+
== The Systems ==
We have two entities left to make. Some breakable objects entities which will spawn collectible entities.
+
We have our entities, we have our components, now the systems. What is an ECS '''System'''?
  
As I have mentioned before components are shared amongst entities and this is exactly the case here too. The breakable objects and the collectibles share the same components as our previous actors and the structure is almost the same as well.
+
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'''
  
== eDestructibleObject.lua ==
+
That looks scary but worry not we won't use all the callback functions :-)
'''Excuse my english, I am not an english native speaker. I called the breakable objects destructibles!'''
 
  
Please create a file "'''eDestructibleObject.lua'''" in the '''"_E"''' 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">
 +
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
 +
</syntaxhighlight>
 +
 
 +
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:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
EDestructibleObject = Core.class()
+
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 EDestructibleObject:init(xspritelayer, xpos)
+
function SPlayer1Control:onAdd(ent) -- tiny function
-- ids
+
self.player1inputlayer:addEventListener(Event.KEY_DOWN, function(e)
self.isdestructibleobject = true
+
if ent.currlives > 0 then
-- sprite layer
+
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = true end
self.spritelayer = xspritelayer
+
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = true end
-- params
+
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = true end
self.pos = xpos
+
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = true end
self.positionystart = self.pos.y -- for sprite sorting
+
-- ACTIONS:
self.sx = 1
+
-- isactionpunch1, isactionpunch2, isactionjumppunch1,
self.sy = self.sx
+
-- isactionkick1, isactionkick2, isactionjumpkick1,
self.flip = math.random(100)
+
-- isactionjump1
if self.flip > 50 then self.flip = 1
+
if e.keyCode == g_keyaction1 then
else self.flip = -1
+
ent.animation.frame = 0
end
+
ent.isactionpunch1 = true
self.totallives = 1
+
elseif e.keyCode == g_keyaction2 then
self.totalhealth = 2
+
ent.animation.frame = 0
if g_difficulty == 0 then -- easy
+
ent.isactionkick1 = true
self.totalhealth *= 0.5
+
end
end
+
if e.keyCode == g_keyaction3 then
self.currlives = self.totallives
+
if ent.body.isonfloor then
self.currhealth = self.totalhealth
+
ent.animation.frame = 0
-- recovery
+
ent.positionystart = ent.pos.y
self.washurt = 0
+
ent.body.isonfloor = false
self.wasbadlyhurt = 0
+
ent.body.isgoingup = true
self.recovertimer = 10
+
ent.isactionjump1 = true
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
+
end
self.hitfx:setAnchorPoint(0.5, 0.5)
+
end
-- COMPONENTS
+
end
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
+
end)
local texpath = "gfx/breakable/Barrel_02_0012.png"
+
self.player1inputlayer:addEventListener(Event.KEY_UP, function(e)
local framerate = 1
+
if ent.currlives > 0 then
self.animation = CAnimation.new(texpath, 1, 1, framerate, 0, 0, self.sx, self.sy)
+
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = false end
self.sprite = self.animation.sprite
+
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = false end
self.sprite:setScale(self.sx*self.flip, self.sy) -- for the flip
+
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = false end
self.animation.sprite = nil -- free some memory
+
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = false end
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
+
-- if e.keyCode == g_keyaction1 then ent.isactionpunch1 = false end
-- create animations: CAnimation:createAnim(xanimname, xstart, xfinish)
+
-- if e.keyCode == g_keyaction2 then ent.isactionkick1 = false end
self.animation:createAnim(g_ANIM_DEFAULT, 1, 1)
+
-- if e.keyCode == g_keyaction3 then end
self.animation:createAnim(g_ANIM_IDLE_R, 1, 1)
+
end
-- clean up
+
end)
self.animation.myanimsimgs = nil
 
-- BODY: CBody:init(xspeed, xjumpspeed)
 
self.body = CBody.new(0, 0) -- xspeed, xjumpspeed
 
self.body.defaultmass = 1
 
self.body.currmass = self.body.defaultmass
 
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 
local collw, collh = self.w*1, 8*self.sy
 
self.collbox = CCollisionBox.new(collw, collh)
 
-- hurt box
 
local hhbw, hhbh = self.w, 1*self.h/2
 
self.headhurtbox = {
 
isactive=false,
 
x=0*self.sx,
 
y=-self.h+hhbh*0.5+self.collbox.h/2,
 
w=hhbw,
 
h=hhbh,
 
}
 
local shbw, shbh = self.w, 3*self.h/4
 
self.spinehurtbox = {
 
isactive=false,
 
x=0*self.sx,
 
y=self.collbox.h/2-shbh/2,
 
w=shbw,
 
h=shbh,
 
}
 
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 
self.shadow = CShadow.new(self.w*1.1)
 
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The main differences:
+
What it does:
* it is a single frame animation
+
* runs only once when it is called
* no attacks
+
* affects only entities with the ''isplayer1'' id
* no AI
+
* when the player1 Entity is added to tiny-ecs '''World''', the System registers KEY_DOWN and KEY_UP events
  
Once a breakable object is destroyed by the player, it spawns a collectible.
+
The System processes the user keys input and sets various flags to be processed in a future ''sDynamicBodies'' System we will add.
  
== eCollectible.lua ==
+
== sPlayer1.lua ==
"'''eCollectible.lua'''" in the '''"_E"''' folder. The code:
+
"'''sPlayer1.lua'''" in the '''"_S"''' folder. The code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
ECollectible = Core.class()
+
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
  
function ECollectible:init(xspritelayer, xpos)
+
local resetanim = true
-- ids
+
function SPlayer1:process(ent, dt) -- tiny function
self.iscollectible = true
+
-- hurt fx
-- sprite layer
+
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) then
self.spritelayer = xspritelayer
+
ent.washurt -= 1
-- params
+
ent.animation.curranim = g_ANIM_HURT_R
self.pos = xpos
+
if ent.washurt < ent.recovertimer*0.5 then ent.hitfx:setVisible(false) end
self.positionystart = self.pos.y -- for sprite sorting
+
if ent.washurt <= 0 then
self.sx = 1
+
ent.sprite:setColorTransform(1, 1, 1, 1)
self.sy = self.sx
+
self.camera:setZoom(1) -- zoom
self.flip = math.random(100)
+
end
if self.flip > 80 then self.flip = 1
+
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 then
else self.flip = -1
+
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
self.totallives = 1
 
self.currlives = self.totallives
 
-- COMPONENTS
 
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
 
local texpath = "gfx/collectible/Dragon Eggs 1.png"
 
local framerate = 1
 
self.animation = CAnimation.new(texpath, 1, 1, framerate, 0, 0, self.sx, self.sy)
 
self.sprite = self.animation.sprite
 
self.sprite:setScale(self.sx*self.flip, self.sy)
 
self.animation.sprite = nil -- free some memory
 
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
 
-- create animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 
self.animation:createAnim(g_ANIM_DEFAULT, 1, 1)
 
self.animation:createAnim(g_ANIM_IDLE_R, 1, 1)
 
-- clean up
 
self.animation.myanimsimgs = nil
 
-- BODY: CBody:init(xspeed, xjumpspeed)
 
self.body = CBody.new(0, 0) -- xspeed, xjumpspeed
 
self.body.defaultmass = 1
 
self.body.currmass = self.body.defaultmass
 
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 
local collw, collh = self.w*0.6, self.h*0.6
 
self.collbox = CCollisionBox.new(collw, collh)
 
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 
self.shadow = CShadow.new(self.w*0.75)
 
-- IDEA: CREATE AN OSCILLATION COMPONENT using body xspeed, xjumpspeed!
 
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
You get the idea ;-)
+
This System deals with the player1 being hit or killed:
 
+
* runs once on init and '''every game loop''' (''process'')
'''We are done making all our entities!!!'''
+
* 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
  
 
== Next? ==
 
== Next? ==

Latest revision as of 21:27, 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

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


Tutorial - tiny-ecs beatemup