Tuto tiny-ecs beatemup Part 7 Enemies
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