Difference between revisions of "Tuto tiny-ecs beatemup Part 7 Enemies"

From GiderosMobile
(wip)
 
 
(One intermediate revision by the same user not shown)
Line 1: Line 1:
 
__TOC__
 
__TOC__
  
== ePlayer1.lua ==
+
== Enemies ==
This is going to be our first actor. Each actor will be an ECS Entity, let's create a file "''ePlayer1.lua''" in the '''"_E"''' folder.
+
The next entities we are going to create: the enemies.
  
The arguments of the init function are: the layer the player sprite will be added to, the position as a vector, and the layer for any fancy graphics effects we may add to the player.
+
As I have mentioned before components are shared amongst entities and this is exactly the case here. The enemies share the same components as our player1. The structure of an enemy Entity is almost the same as the player1 Entity. I will however show the code for each one of them because their attributes have different values, like the graphics, the animations, their speed, strengths, ...
  
The code:
+
== eNme1.lua ==
 +
The first enemy actor, please create a file "'''eNme1.lua'''" in the '''"_E"''' folder and the code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
EPlayer1 = Core.class()
+
ENme1 = Core.class()
  
function EPlayer1:init(xspritelayer, xpos, xbgfxlayer)
+
function ENme1:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
 
-- ids
 
-- ids
self.isplayer1 = true
+
self.isnme = true
self.doanimate = true -- to save some cpu
 
 
-- sprite layers
 
-- sprite layers
 
self.spritelayer = xspritelayer
 
self.spritelayer = xspritelayer
Line 19: Line 19:
 
-- params
 
-- params
 
self.pos = xpos
 
self.pos = xpos
self.positionystart = 0
+
self.sx = 1.4
self.sx = 1
 
 
self.sy = self.sx
 
self.sy = self.sx
self.flip = 1
+
self.totallives = 2
self.totallives = 3
+
self.totalhealth = 2
self.totalhealth = 10
+
-- recovery
self.currjumps = 5
+
self.recovertimer = 8
 +
self.recoverbadtimer = 30
 +
self.actiontimer = math.random(32, 96)
 
if g_difficulty == 0 then -- easy
 
if g_difficulty == 0 then -- easy
self.totallives = 5
+
self.totallives = 1
self.totalhealth = 20
+
self.totalhealth = 1
self.currjumps = 8
+
self.recovertimer *= 0.5
 +
self.recoverbadtimer *= 0.5
 +
self.actiontimer *= 2
 
elseif g_difficulty == 2 then -- hard
 
elseif g_difficulty == 2 then -- hard
self.currjumps = 3
+
self.totallives = 2
end
+
self.totalhealth = 3
self.currlives = self.totallives
 
self.currhealth = self.totalhealth
 
-- recovery
 
self.washurt = 0
 
self.wasbadlyhurt = 0
 
self.recovertimer = 30
 
self.recoverbadtimer = 90
 
if g_difficulty == 0 then -- easy
 
 
self.recovertimer *= 2
 
self.recovertimer *= 2
 
self.recoverbadtimer *= 2
 
self.recoverbadtimer *= 2
elseif g_difficulty == 2 then -- hard
+
self.actiontimer *= 0.5
self.recovertimer *= 0.5
 
self.recoverbadtimer *= 0.5
 
 
end
 
end
self.ispaused = false -- 'P' key for pausing the game
+
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
self.hitfx = Bitmap.new(Texture.new("gfx/fx/2.png"))
 
 
self.hitfx:setAnchorPoint(0.5, 0.5)
 
self.hitfx:setAnchorPoint(0.5, 0.5)
 
-- COMPONENTS
 
-- COMPONENTS
 
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
 
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
local texpath = "gfx/player1/mh_blue_haired2m_0130.png"
+
local texpath = "gfx/nmes/Ravi-Rigged_m_0001.png"
local framerate = 1/10
+
local framerate = 1/10 -- 1/12
self.animation = CAnimation.new(texpath, 12, 11, framerate, 0, 0, self.sx, self.sy)
+
self.animation = CAnimation.new(texpath, 9, 6, framerate, 0, 0, self.sx, self.sy)
 
self.sprite = self.animation.sprite
 
self.sprite = self.animation.sprite
 
self.animation.sprite = nil -- free some memory
 
self.animation.sprite = nil -- free some memory
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight() -- with applied scale
+
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
-- print("player1 size: ", self.w, self.h)
 
 
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
self.animation:createAnim(g_ANIM_DEFAULT, 1, 15)
+
self.animation:createAnim(g_ANIM_DEFAULT, 1, 18)
self.animation:createAnim(g_ANIM_IDLE_R, 1, 15) -- fluid is best
+
self.animation:createAnim(g_ANIM_IDLE_R, 1, 18) -- fluid is best
self.animation:createAnim(g_ANIM_WALK_R, 16, 26) -- fluid is best
+
self.animation:createAnim(g_ANIM_WALK_R, 19, 29) -- fluid is best
self.animation:createAnim(g_ANIM_JUMP1_R, 74, 76) -- fluid is best
+
self.animation:createAnim(g_ANIM_HURT_R, 48, 48) -- fluid is best
self.animation:createAnim(g_ANIM_HURT_R, 90, 100) -- fluid is best
+
self.animation:createAnim(g_ANIM_STANDUP_R, 30, 35) -- fluid is best
self.animation:createAnim(g_ANIM_STANDUP_R, 103, 113) -- fluid is best
+
self.animation:createAnim(g_ANIM_LOSE1_R, 33, 33) -- fluid is best
self.animation:createAnim(g_ANIM_LOSE1_R, 113, 124) -- fluid is best
 
 
-- BODY: CBody:init(xspeed, xjumpspeed)
 
-- BODY: CBody:init(xspeed, xjumpspeed)
self.body = CBody.new(192*32, 0.65) -- (192*32, 1), xspeed, xjumpspeed
+
if g_difficulty == 0 then -- easy
 +
self.body = CBody.new(math.random(64, 128)*32, 1) -- xspeed, xjumpspeed
 +
else
 +
self.body = CBody.new(math.random(128, 160)*32, 1) -- xspeed, xjumpspeed
 +
end
 
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
local collw, collh = self.w*0.4, 8*self.sy
+
local collw, collh = self.w/3, 8*self.sy
 
self.collbox = CCollisionBox.new(collw, collh)
 
self.collbox = CCollisionBox.new(collw, collh)
-- HURT BOX
+
-- hurt box
-- head hurt box w & h
+
local hhbw, hhbh = self.w/2, self.h/3
local hhbw, hhbh = self.w*0.25, self.h*0.3
 
 
self.headhurtbox = {
 
self.headhurtbox = {
 
isactive=false,
 
isactive=false,
x=-2*self.sx,
+
x=6*self.sx,
 
y=0*self.sy-self.h/2-self.collbox.h*2,
 
y=0*self.sy-self.h/2-self.collbox.h*2,
 
w=hhbw,
 
w=hhbw,
 
h=hhbh,
 
h=hhbh,
 
}
 
}
-- spine hurt box w & h
+
local shbw, shbh = self.w/2, self.h/3 -- magik XXX
local shbw, shbh = self.w*0.35, self.h*0.4
 
 
self.spinehurtbox = {
 
self.spinehurtbox = {
 
isactive=false,
 
isactive=false,
Line 92: Line 84:
 
}
 
}
 
-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 
-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
-- self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 50, 51) -- 28, 31, no or low anticipation / quick hit / no or low overhead is best
+
self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 37, 41) -- no or low anticipation / quick hit / no or low overhead is best
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R] = {}
+
self.animation:createAnim(g_ANIM_PUNCH_ATTACK2_R, 48, 51) -- no or low anticipation / quick hit / no or low overhead is best
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][1] = self.animation.myanimsimgs[49]
 
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][2] = self.animation.myanimsimgs[52]
 
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][3] = self.animation.myanimsimgs[54]
 
self.animation:createAnim(g_ANIM_PUNCH_ATTACK2_R, 50, 54) -- low or mid anticipation / quick hit / low or mid overhead is best
 
-- self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 62, 63) -- 35, 41, no or low anticipation / quick hit / no or low overhead is best
 
self.animation.anims[g_ANIM_KICK_ATTACK1_R] = {}
 
self.animation.anims[g_ANIM_KICK_ATTACK1_R][1] = self.animation.myanimsimgs[62]
 
self.animation.anims[g_ANIM_KICK_ATTACK1_R][2] = self.animation.myanimsimgs[64]
 
self.animation.anims[g_ANIM_KICK_ATTACK1_R][3] = self.animation.myanimsimgs[67]
 
self.animation:createAnim(g_ANIM_KICK_ATTACK2_R, 62, 68) -- low or mid anticipation / quick hit / low or mid overhead is best
 
-- self.animation:createAnim(g_ANIM_PUNCHJUMP_ATTACK1_R, 75, 82) -- low or mid anticipation / quick hit / low or mid overhead is best
 
self.animation.anims[g_ANIM_PUNCHJUMP_ATTACK1_R] = {}
 
self.animation.anims[g_ANIM_PUNCHJUMP_ATTACK1_R][1] = self.animation.myanimsimgs[78]
 
self.animation.anims[g_ANIM_PUNCHJUMP_ATTACK1_R][2] = self.animation.myanimsimgs[80]
 
self.animation.anims[g_ANIM_PUNCHJUMP_ATTACK1_R][3] = self.animation.myanimsimgs[82]
 
-- self.animation:createAnim(g_ANIM_KICKJUMP_ATTACK1_R, 83, 88) -- low or mid anticipation / quick hit / low or mid overhead is best
 
self.animation.anims[g_ANIM_KICKJUMP_ATTACK1_R] = {}
 
self.animation.anims[g_ANIM_KICKJUMP_ATTACK1_R][1] = self.animation.myanimsimgs[84]
 
self.animation.anims[g_ANIM_KICKJUMP_ATTACK1_R][2] = self.animation.myanimsimgs[86]
 
 
-- clean up
 
-- clean up
 
self.animation.myanimsimgs = nil
 
self.animation.myanimsimgs = nil
 
-- hit box
 
-- hit box
self.headhitboxattack1 = { -- g_ANIM_PUNCH_ATTACK1_R
+
self.headhitboxattack1 = {
 
isactive=false,
 
isactive=false,
 
hitstartframe=2,
 
hitstartframe=2,
 
hitendframe=3,
 
hitendframe=3,
 
damage=1,
 
damage=1,
x=self.collbox.w*0.6,
+
x=self.collbox.w*1.1,
y=-self.h*0.6+collh*0.5,
+
y=-self.h*0.7+collh*0.5,
 
w=20*self.sx,
 
w=20*self.sx,
h=32*self.sy,
+
h=20*self.sy,
 
}
 
}
self.headhitboxattack2 = { -- g_ANIM_PUNCH_ATTACK2_R X
+
self.headhitboxattack2 = {
 +
isactive=false,
 +
hitstartframe=2,
 +
hitendframe=3,
 +
damage=1,
 +
x=self.collbox.w*1.1,
 +
y=-self.h*0.7+collh*0.5,
 +
w=20*self.sx,
 +
h=20*self.sy,
 +
}
 +
-- AI: CAI:init(xstartpos, dx, dy)
 +
self.ai = CAI.new(self.pos, dx, dy)
 +
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 +
self.shadow = CShadow.new(self.w*0.75)
 +
end
 +
</syntaxhighlight>
 +
 
 +
This is almost exactly the same code as the player1, there are three main differences:
 +
* some variables were moved to the enemy System, this makes the code for our enemies shorter
 +
* we adjust the enemy speed parameters based on the level and the difficulty of the game
 +
* we have an extra Component: the '''AI Component'''
 +
 
 +
=== AI ===
 +
In order for the enemies to attack the player we give them an artificial intelligence ability (Component). You can create a file "'''cAI.lua'''" in the '''"_C"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
CAI = Core.class()
 +
 
 +
function CAI:init(xstartpos, dx, dy)
 +
self.startpos = xstartpos
 +
self.dx = dx -- delta x
 +
self.dy = dy -- delta y
 +
end
 +
</syntaxhighlight>
 +
 
 +
It stores the enemy starting position and some range constraints (delta). This Component will be used in an AI System.
 +
 
 +
'''We have now created all the components for our game!'''
 +
 
 +
== eNme2.lua ==
 +
"'''eNme2.lua'''" in the '''"_E"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
ENme2 = Core.class()
 +
 
 +
function ENme2:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
 +
-- ids
 +
self.isnme = true
 +
-- sprite layers
 +
self.spritelayer = xspritelayer
 +
self.bgfxlayer = xbgfxlayer
 +
-- params
 +
self.pos = xpos
 +
self.sx = 1.3
 +
self.sy = self.sx
 +
self.totallives = 2
 +
self.totalhealth = 2
 +
-- recovery
 +
self.recovertimer = 8
 +
self.recoverbadtimer = 30
 +
self.actiontimer = math.random(32, 96)
 +
if g_difficulty == 0 then -- easy
 +
self.totallives = 1
 +
self.totalhealth = 1
 +
self.recovertimer *= 0.5
 +
self.recoverbadtimer *= 0.5
 +
self.actiontimer *= 2
 +
elseif g_difficulty == 2 then -- hard
 +
self.totallives = 2
 +
self.totalhealth = 3
 +
self.recovertimer *= 2
 +
self.recoverbadtimer *= 2
 +
self.actiontimer *= 0.5
 +
end
 +
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
 +
self.hitfx:setAnchorPoint(0.5, 0.5)
 +
-- COMPONENTS
 +
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
 +
local texpath = "gfx/nmes/Chad_m_0001.png"
 +
local framerate = 1/10 -- 1/12
 +
self.animation = CAnimation.new(texpath, 10, 6, framerate, 0, 0, self.sx, self.sy)
 +
self.sprite = self.animation.sprite
 +
self.animation.sprite = nil -- free some memory
 +
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
 +
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 +
self.animation:createAnim(g_ANIM_DEFAULT, 1, 18)
 +
self.animation:createAnim(g_ANIM_IDLE_R, 1, 18) -- fluid is best
 +
self.animation:createAnim(g_ANIM_WALK_R, 19, 27) -- fluid is best
 +
self.animation:createAnim(g_ANIM_HURT_R, 56, 58) -- fluid is best
 +
self.animation:createAnim(g_ANIM_STANDUP_R, 29, 31) -- fluid is best
 +
self.animation:createAnim(g_ANIM_LOSE1_R, 56, 58) -- fluid is best
 +
-- BODY: CBody:init(xspeed, xjumpspeed)
 +
if g_difficulty == 0 then -- easy
 +
self.body = CBody.new(math.random(64, 128)*32, 1) -- xspeed, xjumpspeed
 +
else
 +
self.body = CBody.new(math.random(128, 160)*32, 1) -- xspeed, 1, xjumpspeed
 +
end
 +
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 +
local collw, collh = self.w*0.4, 8*self.sy
 +
self.collbox = CCollisionBox.new(collw, collh)
 +
-- hurt box
 +
local hhbw, hhbh = self.w/2, self.h/3
 +
self.headhurtbox = {
 +
isactive=false,
 +
x=-3*self.sx,
 +
y=0*self.sy-self.h/2-self.collbox.h/2,
 +
w=hhbw,
 +
h=hhbh,
 +
}
 +
local shbw, shbh = self.w/2, self.h/3
 +
self.spinehurtbox = {
 +
isactive=false,
 +
x=-3*self.sx,
 +
y=0*self.sy-shbh/2+self.collbox.h/2,
 +
w=shbw,
 +
h=shbh,
 +
}
 +
-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 +
self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 28, 34) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 41, 46) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_PUNCHJUMP_ATTACK1_R, 35, 40) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_KICKJUMP_ATTACK1_R, 47, 55) -- no or low anticipation / quick hit / no or low overhead is best
 +
-- clean up
 +
self.animation.myanimsimgs = nil
 +
-- hit box
 +
self.headhitboxattack1 = {
 
isactive=false,
 
isactive=false,
 
hitstartframe=1,
 
hitstartframe=1,
 +
hitendframe=6,
 +
damage=1,
 +
x=self.collbox.w*0.7,
 +
y=-self.h*0.5+collh*0.5,
 +
w=20*self.sx,
 +
h=self.h
 +
}
 +
self.spinehitboxattack1 = {
 +
isactive=false,
 +
hitstartframe=3,
 
hitendframe=4,
 
hitendframe=4,
damage=2,
+
damage=1,
 
x=self.collbox.w*0.75,
 
x=self.collbox.w*0.75,
y=-self.h*0.65+collh*0.5,
+
y=-self.h*0.4,
 
w=32*self.sx,
 
w=32*self.sx,
h=32*self.sy,
+
h=48*self.sy,
 
}
 
}
self.spinehitboxattack1 = { -- g_ANIM_KICK_ATTACK1_R
+
self.headhitboxjattack1 = {
 
isactive=false,
 
isactive=false,
 
hitstartframe=2,
 
hitstartframe=2,
hitendframe=3,
+
hitendframe=4,
damage=1,
+
damage=2,
 
x=self.collbox.w*0.7,
 
x=self.collbox.w*0.7,
y=-self.h*0.25+collh*0.5,
+
y=-self.h*0.6,
 
w=40*self.sx,
 
w=40*self.sx,
h=self.h*0.5,
+
h=40*self.sy,
 
}
 
}
self.spinehitboxattack2 = { -- g_ANIM_KICK_ATTACK2_R
+
self.spinehitboxjattack1 = {
 
isactive=false,
 
isactive=false,
hitstartframe=2,
+
hitstartframe=6,
hitendframe=4,
+
hitendframe=8,
 
damage=2,
 
damage=2,
x=self.collbox.w*0.8,
+
x=self.collbox.w*0.5,
 
y=-self.h*0.25+collh*0.5,
 
y=-self.h*0.25+collh*0.5,
w=32*self.sx,
+
w=48*self.sx,
 
h=self.h*0.5,
 
h=self.h*0.5,
 
}
 
}
self.headhitboxjattack1 = { -- g_ANIM_PUNCHJUMP_ATTACK1_R
+
-- AI: CAI:init(xstartpos, dx, dy)
 +
self.ai = CAI.new(self.pos, dx, dy)
 +
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 +
self.shadow = CShadow.new(self.w*0.75)
 +
end
 +
</syntaxhighlight>
 +
 
 +
== eNme3.lua ==
 +
"'''eNme3.lua'''" in the '''"_E"''' folder. The code:
 +
<syntaxhighlight lang="lua">
 +
ENme3 = Core.class()
 +
 
 +
function ENme3:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
 +
-- ids
 +
self.isnme = true
 +
-- sprite layers
 +
self.spritelayer = xspritelayer
 +
self.bgfxlayer = xbgfxlayer
 +
-- params
 +
self.pos = xpos
 +
self.sx = 1.2
 +
self.sy = self.sx
 +
self.totallives = 2
 +
self.totalhealth = 3
 +
-- recovery
 +
self.recovertimer = 16
 +
self.recoverbadtimer = 30
 +
self.actiontimer = math.random(32, 64)
 +
if g_difficulty == 0 then -- easy
 +
self.totallives = 1
 +
self.totalhealth = 1
 +
self.recovertimer *= 0.5
 +
self.recoverbadtimer *= 0.5
 +
self.actiontimer *= 2
 +
elseif g_difficulty == 2 then -- hard
 +
self.totallives = 2
 +
self.totalhealth = 4
 +
self.recovertimer *= 2
 +
self.recoverbadtimer *= 2
 +
self.actiontimer *= 0.5
 +
end
 +
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
 +
self.hitfx:setAnchorPoint(0.5, 0.5)
 +
-- COMPONENTS
 +
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
 +
local texpath = "gfx/nmes/65bf1add77c22d1745a4790bM_0019.png"
 +
local framerate = 1/8 -- 1/10
 +
self.animation = CAnimation.new(texpath, 9, 6, framerate, 0, 0, self.sx, self.sy)
 +
self.sprite = self.animation.sprite
 +
self.animation.sprite = nil -- free some memory
 +
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
 +
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 +
self.animation:createAnim(g_ANIM_DEFAULT, 1, 14)
 +
self.animation:createAnim(g_ANIM_IDLE_R, 1, 14) -- fluid is best
 +
self.animation:createAnim(g_ANIM_WALK_R, 15, 20) -- fluid is best
 +
self.animation:createAnim(g_ANIM_HURT_R, 49, 53) -- fluid is best
 +
self.animation:createAnim(g_ANIM_STANDUP_R, 51, 53) -- fluid is best
 +
self.animation:createAnim(g_ANIM_LOSE1_R, 50, 51) -- fluid is best
 +
-- BODY: CBody:init(xspeed, xjumpspeed)
 +
if g_difficulty == 0 then -- easy
 +
self.body = CBody.new(math.random(64, 128)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
 +
else
 +
self.body = CBody.new(math.random(128, 160)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
 +
end
 +
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 +
local collw, collh = self.w*0.4, 8*self.sy
 +
self.collbox = CCollisionBox.new(collw, collh)
 +
-- hurt box
 +
local hhbw, hhbh = self.w/2, self.h/3
 +
self.headhurtbox = {
 +
isactive=false,
 +
x=-3*self.sx,
 +
y=0*self.sy-self.h/2-self.collbox.h/2,
 +
w=hhbw,
 +
h=hhbh,
 +
}
 +
local shbw, shbh = self.w/2, self.h/3
 +
self.spinehurtbox = {
 
isactive=false,
 
isactive=false,
hitstartframe=6,
+
x=-0*self.sx,
hitendframe=8,
+
y=0*self.sy-shbh/2+self.collbox.h/2,
damage=3,
+
w=shbw,
x=self.collbox.w*0.7,
+
h=shbh,
y=-self.h*0.5+collh*0.5,
+
}
w=40*self.sx,
+
-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
h=self.h*0.5,
+
self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 23, 28) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 38, 43) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_KICKJUMP_ATTACK1_R, 40, 42) -- no or low anticipation / quick hit / no or low overhead is best
 +
-- clean up
 +
self.animation.myanimsimgs = nil
 +
-- hit box
 +
self.headhitboxattack1 = {
 +
isactive=false,
 +
hitstartframe=2,
 +
hitendframe=3,
 +
damage=1,
 +
x=self.collbox.w*0.6,
 +
y=-self.h*0.6,
 +
w=20*self.sx,
 +
h=20*self.sy,
 
}
 
}
self.spinehitboxjattack1 = { -- g_ANIM_KICKJUMP_ATTACK1_R
+
self.spinehitboxattack1 = {
 
isactive=false,
 
isactive=false,
 
hitstartframe=3,
 
hitstartframe=3,
 
hitendframe=5,
 
hitendframe=5,
 +
damage=1,
 +
x=self.collbox.w*0.75,
 +
y=-self.h*0.4,
 +
w=32*self.sx,
 +
h=48*self.sy,
 +
}
 +
self.spinehitboxjattack1 = {
 +
isactive=false,
 +
hitstartframe=1,
 +
hitendframe=3,
 
damage=3,
 
damage=3,
 
x=self.collbox.w*0.5,
 
x=self.collbox.w*0.5,
y=-self.h*0.25+collh*0.5,
+
y=-self.h*0.25,
w=64*self.sx,
+
w=60*self.sx,
h=self.h*0.5,
+
h=60*self.sy,
 
}
 
}
 +
-- AI: CAI:init(xstartpos, dx, dy)
 +
self.ai = CAI.new(self.pos, dx, dy)
 
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
self.shadow = CShadow.new(self.w*0.6)
+
self.shadow = CShadow.new(self.w*0.75)
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== Code comments ==
+
== eNme4.lua ==
Let's break it down!
+
"'''eNme4.lua'''" in the '''"_E"''' folder. The code:
 
 
=== ids ===
 
Ids are used as filters by the ECS systems. Based on an Entity id, a System will process it or not. Here we have an id to tell this is the player1 Entity and an id telling this sprite is animated.
 
 
 
'''All the player1 variables below can be used as ids as well, should we want to narrow down a System filter'''
 
 
 
=== variables: the basics  ===
 
The systems will need to know a bunch of information about the Entity it is processing:
 
* the sprite layers the actor lives in
 
* the actor position and scale
 
* ''positionystart'' the starting position on the y axis before the actor performs a jump
 
* flip to indicate the direction the actor is facing
 
* the number of lives, health, jumps, ...
 
 
 
=== recovery ===
 
When the actor is hit, we give it some time to recover. During this time the actor is invincible.
 
 
 
=== my mistake! ===
 
I added a ''self.ispaused'' variable which is completly useless, you can safely delete it :-)
 
 
 
''self.hitfx'' is one of those fx we add to the fxlayer when the player1 successfully hits another actor.
 
 
 
== COMPONENTS ==
 
The beauty of ECS is its modularity. Let's add our player1 some components. A Component is an ability you add to an Entity. Entities can and will share the same components.
 
 
 
=== ANIMATION ===
 
The first component we add is the Animation Component. Create a file called "''cAnimation.lua''" in the '''"_C"''' folder and add the following code:
 
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
CAnimation = Core.class()
+
ENme4 = Core.class()
  
function CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
+
function ENme4:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
-- animation
+
-- ids
self.curranim = g_ANIM_DEFAULT
+
self.isnme = true
self.frame = 0
+
-- sprite layers
self.animspeed = xanimspeed
+
self.spritelayer = xspritelayer
self.animtimer = self.animspeed
+
self.bgfxlayer = xbgfxlayer
-- retrieve all anims in texture
+
-- params
local myanimstex = Texture.new(xspritesheetpath)
+
self.pos = xpos
local cellw = myanimstex:getWidth() / xcols
+
self.sx = 1.4
local cellh = myanimstex:getHeight() / xrows
+
self.sy = self.sx
self.myanimsimgs = {}
+
self.totallives = 2
local myanimstexregion
+
self.totalhealth = 3
for r = 1, xrows do
+
-- recovery
for c = 1, xcols do
+
self.recovertimer = 16
myanimstexregion = TextureRegion.new(myanimstex, (c - 1) * cellw, (r - 1) * cellh, cellw, cellh)
+
self.recoverbadtimer = 30
self.myanimsimgs[#self.myanimsimgs + 1] = myanimstexregion
+
self.actiontimer = math.random(32, 64)
end
+
if g_difficulty == 0 then -- easy
 +
self.totallives = 1
 +
self.totalhealth = 1
 +
self.recovertimer *= 0.5
 +
self.recoverbadtimer *= 0.5
 +
self.actiontimer *= 2
 +
elseif g_difficulty == 2 then -- hard
 +
self.totallives = 2
 +
self.totalhealth = 4
 +
self.recovertimer *= 2
 +
self.recoverbadtimer *= 2
 +
self.actiontimer *= 0.5
 
end
 
end
-- anims table ("walk", "jump", "shoot", ...)
+
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
self.anims = {}
+
self.hitfx:setAnchorPoint(0.5, 0.5)
-- the bitmap
+
-- COMPONENTS
self.bmp = Bitmap.new(self.myanimsimgs[1]) -- starting bmp texture
+
-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
self.bmp:setScale(sx, sy) -- scale!
+
local texpath = "gfx/nmes/mh_fatty01m_0001.png"
self.bmp:setAnchorPoint(0.5, 0.5) -- we will flip the bitmap
+
local framerate = 1/8
-- set position inside sprite
+
self.animation = CAnimation.new(texpath, 9, 5, framerate, 0, 0, self.sx, self.sy)
self.bmp:setPosition(xoffx*sx, xoffy*sy) -- work best with image centered spritesheets
+
self.sprite = self.animation.sprite
-- our final sprite
+
self.animation.sprite = nil -- free some memory
self.sprite = Sprite.new()
+
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
self.sprite:addChild(self.bmp)
+
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
end
+
self.animation:createAnim(g_ANIM_DEFAULT, 1, 23)
 
+
self.animation:createAnim(g_ANIM_IDLE_R, 1, 23) -- fluid is best
function CAnimation:createAnim(xanimname, xstart, xfinish)
+
self.animation:createAnim(g_ANIM_WALK_R, 24, 34) -- fluid is best
self.anims[xanimname] = {}
+
self.animation:createAnim(g_ANIM_JUMP1_R, 35, 43) -- fluid is best
for i = xstart, xfinish do
+
self.animation:createAnim(g_ANIM_HURT_R, 5, 7) -- fluid is best
self.anims[xanimname][#self.anims[xanimname]+1] = self.myanimsimgs[i]
+
self.animation:createAnim(g_ANIM_STANDUP_R, 39, 42) -- fluid is best
 +
self.animation:createAnim(g_ANIM_LOSE1_R, 12, 14) -- fluid is best
 +
-- BODY: CBody:init(xspeed, xjumpspeed)
 +
if g_difficulty == 0 then -- easy
 +
self.body = CBody.new(math.random(64, 128)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
 +
else
 +
self.body = CBody.new(math.random(128, 160)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
 
end
 
end
 +
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
 +
local collw, collh = self.w*0.25, 8*self.sy
 +
self.collbox = CCollisionBox.new(collw, collh)
 +
-- hurt box
 +
local hhbw, hhbh = self.w/2, self.h/3 -- magik XXX
 +
self.headhurtbox = {
 +
isactive=false,
 +
x=-2*self.sx,
 +
y=0*self.sy-self.h/2-self.collbox.h/2,
 +
w=hhbw,
 +
h=hhbh,
 +
}
 +
local shbw, shbh = self.w/2, self.h/3 -- magik XXX
 +
self.spinehurtbox = {
 +
isactive=false,
 +
x=-0*self.sx,
 +
y=0*self.sy-shbh/2+self.collbox.h/2,
 +
w=shbw,
 +
h=shbh,
 +
}
 +
-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
 +
self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 35, 43) -- no or low anticipation / quick hit / no or low overhead is best
 +
self.animation:createAnim(g_ANIM_PUNCHJUMP_ATTACK1_R, 35, 43) -- no or low anticipation / quick hit / no or low overhead is best
 +
-- clean up
 +
self.animation.myanimsimgs = nil
 +
-- hit box
 +
self.headhitboxattack1 = {
 +
isactive=false,
 +
hitstartframe=1,
 +
hitendframe=8,
 +
damage=1,
 +
x=self.collbox.w*0.4*self.sx,
 +
y=-self.h*0.5+collh*0.5,
 +
w=24*self.sx,
 +
h=self.h,
 +
}
 +
self.headhitboxjattack1 = {
 +
isactive=false,
 +
hitstartframe=1,
 +
hitendframe=8,
 +
damage=2,
 +
x=self.collbox.w*0.7*self.sx,
 +
y=-self.h*0.5*self.sy,
 +
w=40*self.sx,
 +
h=self.h*0.5,
 +
}
 +
-- AI: CAI:init(xstartpos, dx, dy)
 +
self.ai = CAI.new(self.pos, dx, dy)
 +
-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
 +
self.shadow = CShadow.new(self.w*0.75)
 
end
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
The parameters are:
+
That's it, we have all our enemies!
* xspritesheetpath: the path to the player1 spritesheet
 
* xcols, xrows: the spritesheet number of columns and rows
 
* xanimspeed: the animation speed in ms
 
* xoffx, xoffy: an offset in case we need to
 
* sx, sy: the scale of the sprite
 
 
 
The Animation Component cuts the spritesheet into single images and puts them in the ''self.myanimsimgs'' table. We then use ''self.myanimsimgs'' and pick the first image as our player1 bitmap. The ''self.myanimsimgs'' table is also used to create the animations with the '''createAnim''' function as we see next.
 
 
 
==== animations ====
 
Back to the '''"ePlayer1.lua"''' code we create its animations.
 
<syntaxhighlight lang="lua">
 
self.animation:createAnim(g_ANIM_DEFAULT, 1, 15)
 
self.animation:createAnim(g_ANIM_IDLE_R, 1, 15) -- fluid is best
 
self.animation:createAnim(g_ANIM_WALK_R, 16, 26) -- fluid is best
 
self.animation:createAnim(g_ANIM_JUMP1_R, 74, 76) -- fluid is best
 
...
 
</syntaxhighlight>
 
 
 
We first create the basics animations: idle, walk, jump. We also have extra animations: hurt, stand up and lose. Here it is better to have smooth animations so we have quite a few images. The g_ANIM_DEFAULT animation was used when building the game, we shouldn't need it anymore but I left it in the project ;-)
 
 
 
We assign the starting image and the ending image in the spritesheet for each animations. The animations names were declared in the '''"main.lua"''' file (this is where you would add any extra animations).
 
  
  '''SELF PROMOTION: I made an app to count images in a spritesheet: https://mokatunprod.itch.io/spritesheet-maker-viewer'''
+
  '''Please note: our enemies have different attacks, some have 2 kind of attacks, some have 3, some can kick, some can jump attack, ...'''
  
 
== Next? ==
 
== Next? ==
To shorten the length of this part, let's see the other components in the next chapter.
+
Before tackling all the systems that will tie our game together, we will create the last two entities of our game: the '''breakable objects''' and the '''collectibles'''.
  
  
 
Prev.: [[Tuto tiny-ecs beatemup Part 6 ECS Components]]</br>
 
Prev.: [[Tuto tiny-ecs beatemup Part 6 ECS Components]]</br>
'''Next: [[Tuto tiny-ecs beatemup Part 8 XXX]]'''
+
'''Next: [[Tuto tiny-ecs beatemup Part 8 Breakables]]'''
  
  
 
'''[[Tutorial - tiny-ecs beatemup]]'''
 
'''[[Tutorial - tiny-ecs beatemup]]'''
 
{{GIDEROS IMPORTANT LINKS}}
 
{{GIDEROS IMPORTANT LINKS}}

Latest revision as of 08:08, 20 November 2024

Enemies

The next entities we are going to create: the enemies.

As I have mentioned before components are shared amongst entities and this is exactly the case here. The enemies share the same components as our player1. The structure of an enemy Entity is almost the same as the player1 Entity. I will however show the code for each one of them because their attributes have different values, like the graphics, the animations, their speed, strengths, ...

eNme1.lua

The first enemy actor, please create a file "eNme1.lua" in the "_E" folder and the code:

ENme1 = Core.class()

function ENme1:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
	-- ids
	self.isnme = true
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1.4
	self.sy = self.sx
	self.totallives = 2
	self.totalhealth = 2
	-- recovery
	self.recovertimer = 8
	self.recoverbadtimer = 30
	self.actiontimer = math.random(32, 96)
	if g_difficulty == 0 then -- easy
		self.totallives = 1
		self.totalhealth = 1
		self.recovertimer *= 0.5
		self.recoverbadtimer *= 0.5
		self.actiontimer *= 2
	elseif g_difficulty == 2 then -- hard
		self.totallives = 2
		self.totalhealth = 3
		self.recovertimer *= 2
		self.recoverbadtimer *= 2
		self.actiontimer *= 0.5
	end
	self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
	self.hitfx:setAnchorPoint(0.5, 0.5)
	-- COMPONENTS
	-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
	local texpath = "gfx/nmes/Ravi-Rigged_m_0001.png"
	local framerate = 1/10 -- 1/12
	self.animation = CAnimation.new(texpath, 9, 6, framerate, 0, 0, self.sx, self.sy)
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_DEFAULT, 1, 18)
	self.animation:createAnim(g_ANIM_IDLE_R, 1, 18) -- fluid is best
	self.animation:createAnim(g_ANIM_WALK_R, 19, 29) -- fluid is best
	self.animation:createAnim(g_ANIM_HURT_R, 48, 48) -- fluid is best
	self.animation:createAnim(g_ANIM_STANDUP_R, 30, 35) -- fluid is best
	self.animation:createAnim(g_ANIM_LOSE1_R, 33, 33) -- fluid is best
	-- BODY: CBody:init(xspeed, xjumpspeed)
	if g_difficulty == 0 then -- easy
		self.body = CBody.new(math.random(64, 128)*32, 1) -- xspeed, xjumpspeed
	else
		self.body = CBody.new(math.random(128, 160)*32, 1) -- xspeed, xjumpspeed
	end
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w/3, 8*self.sy
	self.collbox = CCollisionBox.new(collw, collh)
	-- hurt box
	local hhbw, hhbh = self.w/2, self.h/3
	self.headhurtbox = {
		isactive=false,
		x=6*self.sx,
		y=0*self.sy-self.h/2-self.collbox.h*2,
		w=hhbw,
		h=hhbh,
	}
	local shbw, shbh = self.w/2, self.h/3 -- magik XXX
	self.spinehurtbox = {
		isactive=false,
		x=-0*self.sx,
		y=0*self.sy-shbh/2+self.collbox.h/2,
		w=shbw,
		h=shbh,
	}
	-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 37, 41) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK2_R, 48, 51) -- no or low anticipation / quick hit / no or low overhead is best
	-- clean up
	self.animation.myanimsimgs = nil
	-- hit box
	self.headhitboxattack1 = {
		isactive=false,
		hitstartframe=2,
		hitendframe=3,
		damage=1,
		x=self.collbox.w*1.1,
		y=-self.h*0.7+collh*0.5,
		w=20*self.sx,
		h=20*self.sy,
	}
	self.headhitboxattack2 = {
		isactive=false,
		hitstartframe=2,
		hitendframe=3,
		damage=1,
		x=self.collbox.w*1.1,
		y=-self.h*0.7+collh*0.5,
		w=20*self.sx,
		h=20*self.sy,
	}
	-- AI: CAI:init(xstartpos, dx, dy)
	self.ai = CAI.new(self.pos, dx, dy)
	-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
	self.shadow = CShadow.new(self.w*0.75)
end

This is almost exactly the same code as the player1, there are three main differences:

  • some variables were moved to the enemy System, this makes the code for our enemies shorter
  • we adjust the enemy speed parameters based on the level and the difficulty of the game
  • we have an extra Component: the AI Component

AI

In order for the enemies to attack the player we give them an artificial intelligence ability (Component). You can create a file "cAI.lua" in the "_C" folder. The code:

CAI = Core.class()

function CAI:init(xstartpos, dx, dy)
	self.startpos = xstartpos
	self.dx = dx -- delta x
	self.dy = dy -- delta y
end

It stores the enemy starting position and some range constraints (delta). This Component will be used in an AI System.

We have now created all the components for our game!

eNme2.lua

"eNme2.lua" in the "_E" folder. The code:

ENme2 = Core.class()

function ENme2:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
	-- ids
	self.isnme = true
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1.3
	self.sy = self.sx
	self.totallives = 2
	self.totalhealth = 2
	-- recovery
	self.recovertimer = 8
	self.recoverbadtimer = 30
	self.actiontimer = math.random(32, 96)
	if g_difficulty == 0 then -- easy
		self.totallives = 1
		self.totalhealth = 1
		self.recovertimer *= 0.5
		self.recoverbadtimer *= 0.5
		self.actiontimer *= 2
	elseif g_difficulty == 2 then -- hard
		self.totallives = 2
		self.totalhealth = 3
		self.recovertimer *= 2
		self.recoverbadtimer *= 2
		self.actiontimer *= 0.5
	end
	self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
	self.hitfx:setAnchorPoint(0.5, 0.5)
	-- COMPONENTS
	-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
	local texpath = "gfx/nmes/Chad_m_0001.png"
	local framerate = 1/10 -- 1/12
	self.animation = CAnimation.new(texpath, 10, 6, framerate, 0, 0, self.sx, self.sy)
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_DEFAULT, 1, 18)
	self.animation:createAnim(g_ANIM_IDLE_R, 1, 18) -- fluid is best
	self.animation:createAnim(g_ANIM_WALK_R, 19, 27) -- fluid is best
	self.animation:createAnim(g_ANIM_HURT_R, 56, 58) -- fluid is best
	self.animation:createAnim(g_ANIM_STANDUP_R, 29, 31) -- fluid is best
	self.animation:createAnim(g_ANIM_LOSE1_R, 56, 58) -- fluid is best
	-- BODY: CBody:init(xspeed, xjumpspeed)
	if g_difficulty == 0 then -- easy
		self.body = CBody.new(math.random(64, 128)*32, 1) -- xspeed, xjumpspeed
	else
		self.body = CBody.new(math.random(128, 160)*32, 1) -- xspeed, 1, xjumpspeed
	end
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w*0.4, 8*self.sy
	self.collbox = CCollisionBox.new(collw, collh)
	-- hurt box
	local hhbw, hhbh = self.w/2, self.h/3
	self.headhurtbox = {
		isactive=false,
		x=-3*self.sx,
		y=0*self.sy-self.h/2-self.collbox.h/2,
		w=hhbw,
		h=hhbh,
	}
	local shbw, shbh = self.w/2, self.h/3
	self.spinehurtbox = {
		isactive=false,
		x=-3*self.sx,
		y=0*self.sy-shbh/2+self.collbox.h/2,
		w=shbw,
		h=shbh,
	}
	-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 28, 34) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 41, 46) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_PUNCHJUMP_ATTACK1_R, 35, 40) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_KICKJUMP_ATTACK1_R, 47, 55) -- no or low anticipation / quick hit / no or low overhead is best
	-- clean up
	self.animation.myanimsimgs = nil
	-- hit box
	self.headhitboxattack1 = {
		isactive=false,
		hitstartframe=1,
		hitendframe=6,
		damage=1,
		x=self.collbox.w*0.7,
		y=-self.h*0.5+collh*0.5,
		w=20*self.sx,
		h=self.h
	}
	self.spinehitboxattack1 = {
		isactive=false,
		hitstartframe=3,
		hitendframe=4,
		damage=1,
		x=self.collbox.w*0.75,
		y=-self.h*0.4,
		w=32*self.sx,
		h=48*self.sy,
	}
	self.headhitboxjattack1 = {
		isactive=false,
		hitstartframe=2,
		hitendframe=4,
		damage=2,
		x=self.collbox.w*0.7,
		y=-self.h*0.6,
		w=40*self.sx,
		h=40*self.sy,
	}
	self.spinehitboxjattack1 = {
		isactive=false,
		hitstartframe=6,
		hitendframe=8,
		damage=2,
		x=self.collbox.w*0.5,
		y=-self.h*0.25+collh*0.5,
		w=48*self.sx,
		h=self.h*0.5,
	}
	-- AI: CAI:init(xstartpos, dx, dy)
	self.ai = CAI.new(self.pos, dx, dy)
	-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
	self.shadow = CShadow.new(self.w*0.75)
end

eNme3.lua

"eNme3.lua" in the "_E" folder. The code:

ENme3 = Core.class()

function ENme3:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
	-- ids
	self.isnme = true
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1.2
	self.sy = self.sx
	self.totallives = 2
	self.totalhealth = 3
	-- recovery
	self.recovertimer = 16
	self.recoverbadtimer = 30
	self.actiontimer = math.random(32, 64)
	if g_difficulty == 0 then -- easy
		self.totallives = 1
		self.totalhealth = 1
		self.recovertimer *= 0.5
		self.recoverbadtimer *= 0.5
		self.actiontimer *= 2
	elseif g_difficulty == 2 then -- hard
		self.totallives = 2
		self.totalhealth = 4
		self.recovertimer *= 2
		self.recoverbadtimer *= 2
		self.actiontimer *= 0.5
	end
	self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
	self.hitfx:setAnchorPoint(0.5, 0.5)
	-- COMPONENTS
	-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
	local texpath = "gfx/nmes/65bf1add77c22d1745a4790bM_0019.png"
	local framerate = 1/8 -- 1/10
	self.animation = CAnimation.new(texpath, 9, 6, framerate, 0, 0, self.sx, self.sy)
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_DEFAULT, 1, 14)
	self.animation:createAnim(g_ANIM_IDLE_R, 1, 14) -- fluid is best
	self.animation:createAnim(g_ANIM_WALK_R, 15, 20) -- fluid is best
	self.animation:createAnim(g_ANIM_HURT_R, 49, 53) -- fluid is best
	self.animation:createAnim(g_ANIM_STANDUP_R, 51, 53) -- fluid is best
	self.animation:createAnim(g_ANIM_LOSE1_R, 50, 51) -- fluid is best
	-- BODY: CBody:init(xspeed, xjumpspeed)
	if g_difficulty == 0 then -- easy
		self.body = CBody.new(math.random(64, 128)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
	else
		self.body = CBody.new(math.random(128, 160)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
	end
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w*0.4, 8*self.sy
	self.collbox = CCollisionBox.new(collw, collh)
	-- hurt box
	local hhbw, hhbh = self.w/2, self.h/3
	self.headhurtbox = {
		isactive=false,
		x=-3*self.sx,
		y=0*self.sy-self.h/2-self.collbox.h/2,
		w=hhbw,
		h=hhbh,
	}
	local shbw, shbh = self.w/2, self.h/3
	self.spinehurtbox = {
		isactive=false,
		x=-0*self.sx,
		y=0*self.sy-shbh/2+self.collbox.h/2,
		w=shbw,
		h=shbh,
	}
	-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 23, 28) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 38, 43) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_KICKJUMP_ATTACK1_R, 40, 42) -- no or low anticipation / quick hit / no or low overhead is best
	-- clean up
	self.animation.myanimsimgs = nil
	-- hit box
	self.headhitboxattack1 = {
		isactive=false,
		hitstartframe=2,
		hitendframe=3,
		damage=1,
		x=self.collbox.w*0.6,
		y=-self.h*0.6,
		w=20*self.sx,
		h=20*self.sy,
	}
	self.spinehitboxattack1 = {
		isactive=false,
		hitstartframe=3,
		hitendframe=5,
		damage=1,
		x=self.collbox.w*0.75,
		y=-self.h*0.4,
		w=32*self.sx,
		h=48*self.sy,
	}
	self.spinehitboxjattack1 = {
		isactive=false,
		hitstartframe=1,
		hitendframe=3,
		damage=3,
		x=self.collbox.w*0.5,
		y=-self.h*0.25,
		w=60*self.sx,
		h=60*self.sy,
	}
	-- AI: CAI:init(xstartpos, dx, dy)
	self.ai = CAI.new(self.pos, dx, dy)
	-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
	self.shadow = CShadow.new(self.w*0.75)
end

eNme4.lua

"eNme4.lua" in the "_E" folder. The code:

ENme4 = Core.class()

function ENme4:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
	-- ids
	self.isnme = true
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1.4
	self.sy = self.sx
	self.totallives = 2
	self.totalhealth = 3
	-- recovery
	self.recovertimer = 16
	self.recoverbadtimer = 30
	self.actiontimer = math.random(32, 64)
	if g_difficulty == 0 then -- easy
		self.totallives = 1
		self.totalhealth = 1
		self.recovertimer *= 0.5
		self.recoverbadtimer *= 0.5
		self.actiontimer *= 2
	elseif g_difficulty == 2 then -- hard
		self.totallives = 2
		self.totalhealth = 4
		self.recovertimer *= 2
		self.recoverbadtimer *= 2
		self.actiontimer *= 0.5
	end
	self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
	self.hitfx:setAnchorPoint(0.5, 0.5)
	-- COMPONENTS
	-- ANIMATION: CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
	local texpath = "gfx/nmes/mh_fatty01m_0001.png"
	local framerate = 1/8
	self.animation = CAnimation.new(texpath, 9, 5, framerate, 0, 0, self.sx, self.sy)
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_DEFAULT, 1, 23)
	self.animation:createAnim(g_ANIM_IDLE_R, 1, 23) -- fluid is best
	self.animation:createAnim(g_ANIM_WALK_R, 24, 34) -- fluid is best
	self.animation:createAnim(g_ANIM_JUMP1_R, 35, 43) -- fluid is best
	self.animation:createAnim(g_ANIM_HURT_R, 5, 7) -- fluid is best
	self.animation:createAnim(g_ANIM_STANDUP_R, 39, 42) -- fluid is best
	self.animation:createAnim(g_ANIM_LOSE1_R, 12, 14) -- fluid is best
	-- BODY: CBody:init(xspeed, xjumpspeed)
	if g_difficulty == 0 then -- easy
		self.body = CBody.new(math.random(64, 128)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
	else
		self.body = CBody.new(math.random(128, 160)*32, math.random(40, 56)/100) -- xspeed, xjumpspeed
	end
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w*0.25, 8*self.sy
	self.collbox = CCollisionBox.new(collw, collh)
	-- hurt box
	local hhbw, hhbh = self.w/2, self.h/3 -- magik XXX
	self.headhurtbox = {
		isactive=false,
		x=-2*self.sx,
		y=0*self.sy-self.h/2-self.collbox.h/2,
		w=hhbw,
		h=hhbh,
	}
	local shbw, shbh = self.w/2, self.h/3 -- magik XXX
	self.spinehurtbox = {
		isactive=false,
		x=-0*self.sx,
		y=0*self.sy-shbh/2+self.collbox.h/2,
		w=shbw,
		h=shbh,
	}
	-- create attacks animations: CAnimation:createAnim(xanimname, xstart, xfinish)
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 35, 43) -- no or low anticipation / quick hit / no or low overhead is best
	self.animation:createAnim(g_ANIM_PUNCHJUMP_ATTACK1_R, 35, 43) -- no or low anticipation / quick hit / no or low overhead is best
	-- clean up
	self.animation.myanimsimgs = nil
	-- hit box
	self.headhitboxattack1 = {
		isactive=false,
		hitstartframe=1,
		hitendframe=8,
		damage=1,
		x=self.collbox.w*0.4*self.sx,
		y=-self.h*0.5+collh*0.5,
		w=24*self.sx,
		h=self.h,
	}
	self.headhitboxjattack1 = {
		isactive=false,
		hitstartframe=1,
		hitendframe=8,
		damage=2,
		x=self.collbox.w*0.7*self.sx,
		y=-self.h*0.5*self.sy,
		w=40*self.sx,
		h=self.h*0.5,
	}
	-- AI: CAI:init(xstartpos, dx, dy)
	self.ai = CAI.new(self.pos, dx, dy)
	-- SHADOW: CShadow:init(xparentw, xshadowsx, xshadowsy)
	self.shadow = CShadow.new(self.w*0.75)
end

That's it, we have all our enemies!

Please note: our enemies have different attacks, some have 2 kind of attacks, some have 3, some can kick, some can jump attack, ...

Next?

Before tackling all the systems that will tie our game together, we will create the last two entities of our game: the breakable objects and the collectibles.


Prev.: Tuto tiny-ecs beatemup Part 6 ECS Components
Next: Tuto tiny-ecs beatemup Part 8 Breakables


Tutorial - tiny-ecs beatemup