Difference between revisions of "Tuto tiny-ecs 2d platformer Part 7 Enemies"

From GiderosMobile
(wip)
 
 
Line 4: Line 4:
 
The next entities we are going to create: the 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, ...
+
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.
  
== eNme1.lua ==
+
== eGroundNmes.lua ==
The first enemy actor, please create a file "'''eNme1.lua'''" in the '''"_E"''' folder and the code:
+
Please create a file "'''eGroundNmes.lua'''" in the '''"_E"''' folder and the code:
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
ENme1 = Core.class()
+
EGroundNmes = Core.class()
  
function ENme1:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
+
function EGroundNmes:init(xid, xspritelayer, xpos, xbgfxlayer, xcollectible)
 
-- ids
 
-- ids
 
self.isnme = true
 
self.isnme = true
 +
self.eid = xid
 +
self.ispersistent = false -- keep sprite visible when dead
 
-- sprite layers
 
-- sprite layers
 
self.spritelayer = xspritelayer
 
self.spritelayer = xspritelayer
 
self.bgfxlayer = xbgfxlayer
 
self.bgfxlayer = xbgfxlayer
 +
-- actor holds a collectible?
 +
if xcollectible then
 +
self.collectible = xcollectible
 +
end
 
-- params
 
-- params
 
self.pos = xpos
 
self.pos = xpos
self.sx = 1.4
+
self.sx = 1 -- 0.96
 
self.sy = self.sx
 
self.sy = self.sx
self.totallives = 2
+
self.totallives = 1 -- basic nme
self.totalhealth = 2
+
self.totalhealth = 3 -- basic nme
-- recovery
+
-- 100: no move, no jump, shoot straight, you choose the nme abilities!
self.recovertimer = 8
+
-- 200: move, jump, no shoot, you choose the nme abilities!
self.recoverbadtimer = 30
+
-- 300: no move, no jump, shoot all angles, shield, you choose the nme abilities!
self.actiontimer = math.random(32, 96)
+
if self.eid == 200 then
if g_difficulty == 0 then -- easy
+
self.ispersistent = true -- keep sprite visible on dead
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.totallives = 2
 
self.totalhealth = 3
 
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
 
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
 
</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
 
-- recovery
self.recovertimer = 8
+
self.recovertimer = 10
 
self.recoverbadtimer = 30
 
self.recoverbadtimer = 30
self.actiontimer = math.random(32, 96)
+
self.actiontimer = 90 -- 60, math.random(32, 96), low value=hard, high value=easy
 
if g_difficulty == 0 then -- easy
 
if g_difficulty == 0 then -- easy
 
self.totallives = 1
 
self.totallives = 1
self.totalhealth = 1
+
self.totalhealth = 3
 
self.recovertimer *= 0.5
 
self.recovertimer *= 0.5
 
self.recoverbadtimer *= 0.5
 
self.recoverbadtimer *= 0.5
 
self.actiontimer *= 2
 
self.actiontimer *= 2
 
elseif g_difficulty == 2 then -- hard
 
elseif g_difficulty == 2 then -- hard
self.totallives = 2
 
self.totalhealth = 3
 
 
self.recovertimer *= 2
 
self.recovertimer *= 2
 
self.recoverbadtimer *= 2
 
self.recoverbadtimer *= 2
 
self.actiontimer *= 0.5
 
self.actiontimer *= 0.5
 
end
 
end
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
+
self.hitfx = Bitmap.new(Texture.new("gfx/fxs/1.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
local texpath = "gfx/nmes/Chad_m_0001.png"
+
local anims = {} -- table to hold actor animations
 +
local animsimgs = {} -- table to hold actor animations images
 +
-- CAnimation:init(xanimspeed)
 
local framerate = 1/10 -- 1/12
 
local framerate = 1/10 -- 1/12
self.animation = CAnimation.new(texpath, 10, 6, framerate, 0, 0, self.sx, self.sy)
+
self.animation = CAnimation.new(framerate)
self.sprite = self.animation.sprite
+
-- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
self.animation.sprite = nil -- free some memory
+
local texpath
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
+
local cols, rows
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
+
if self.eid == 100 then
self.animation:createAnim(g_ANIM_DEFAULT, 1, 18)
+
texpath = "gfx/nmes/Zombie_0020.png"
self.animation:createAnim(g_ANIM_IDLE_R, 1, 18) -- fluid is best
+
cols, rows = 4, 4
self.animation:createAnim(g_ANIM_WALK_R, 19, 27) -- fluid is best
+
self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
self.animation:createAnim(g_ANIM_HURT_R, 56, 58) -- fluid is best
+
elseif self.eid == 200 then
self.animation:createAnim(g_ANIM_STANDUP_R, 29, 31) -- fluid is best
+
texpath = "gfx/nmes/Zombie_0040.png"
self.animation:createAnim(g_ANIM_LOSE1_R, 56, 58) -- fluid is best
+
cols, rows = 3, 3
-- BODY: CBody:init(xspeed, xjumpspeed)
+
self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
if g_difficulty == 0 then -- easy
+
elseif self.eid == 300 then
self.body = CBody.new(math.random(64, 128)*32, 1) -- xspeed, xjumpspeed
+
texpath = "gfx/nmes/Zombie_0001.png"
else
+
cols, rows = 5, 3
self.body = CBody.new(math.random(128, 160)*32, 1) -- xspeed, 1, xjumpspeed
+
self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
 
end
 
end
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
+
-- 1st set of animations: CAnimation:createAnim(xanims, xanimname, xanimsimgs, xtable, xstart, xfinish)
local collw, collh = self.w*0.4, 8*self.sy
+
if self.eid == 100 then
self.collbox = CCollisionBox.new(collw, collh)
+
self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, cols*rows)
-- hurt box
+
self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, cols*rows) -- fluid is best
local hhbw, hhbh = self.w/2, self.h/3
+
elseif self.eid == 200 then
self.headhurtbox = {
+
self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, cols*rows)
isactive=false,
+
self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, cols*rows) -- fluid is best
x=-3*self.sx,
+
self.animation:createAnim(anims, g_ANIM_RUN_R, animsimgs, nil, 1, cols*rows) -- fluid is best
y=0*self.sy-self.h/2-self.collbox.h/2,
+
elseif self.eid == 300 then
w=hhbw,
+
self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 5, 5)
h=hhbh,
+
self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 5, 5) -- fluid is best
}
 
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
 
</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
 
end
self.hitfx = Bitmap.new(Texture.new("gfx/fx/1.png"))
+
-- end animations
self.hitfx:setAnchorPoint(0.5, 0.5)
+
self.animation.anims = anims
-- 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.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()
+
self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight() -- with applied scale
-- create basics animations: CAnimation:createAnim(xanimname, xstart, xfinish)
+
-- BODY: CBody:init(xmass, xspeed, xupspeed, xextra)
self.animation:createAnim(g_ANIM_DEFAULT, 1, 14)
+
if self.eid == 100 then
self.animation:createAnim(g_ANIM_IDLE_R, 1, 14) -- fluid is best
+
self.body = CBody.new(1, 0, 0, true)
self.animation:createAnim(g_ANIM_WALK_R, 15, 20) -- fluid is best
+
elseif self.eid == 200 then
self.animation:createAnim(g_ANIM_HURT_R, 49, 53) -- fluid is best
+
self.body = CBody.new(1, 12*8, 64*8, true)
self.animation:createAnim(g_ANIM_STANDUP_R, 51, 53) -- fluid is best
+
elseif self.eid == 300 then
self.animation:createAnim(g_ANIM_LOSE1_R, 50, 51) -- fluid is best
+
self.body = CBody.new(1, 0, 0, true)
-- 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)
 
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
local collw, collh = self.w*0.4, 8*self.sy
+
local collw, collh = (self.w*0.5)//1, (self.h*0.8)//1 -- must be round numbers for cbump physics!
 
self.collbox = CCollisionBox.new(collw, collh)
 
self.collbox = CCollisionBox.new(collw, collh)
-- hurt box
+
-- AI
local hhbw, hhbh = self.w/2, self.h/3
+
self.ai = true
self.headhurtbox = {
+
-- shield
isactive=false,
+
self.shield = {}
x=-3*self.sx,
+
self.shield.sprite = Pixel.new(0x5555ff, 0.75, 8, collh-6)
y=0*self.sy-self.h/2-self.collbox.h/2,
+
self.shield.sprite.sx = 1
w=hhbw,
+
self.shield.sprite.sy = self.shield.sprite.sx
h=hhbh,
+
self.shield.sprite:setScale(self.shield.sprite.sx, self.shield.sprite.sy)
}
+
self.shield.sprite:setAnchorPoint(0.5, 0.5)
local shbw, shbh = self.w/2, self.h/3
+
self.spritelayer:addChild(self.shield.sprite)
self.spinehurtbox = {
+
self.shield.offset = vector(collw, (collh+6)*0.5)
isactive=false,
+
self.shield.timer = 4*8 -- 4*8, 2*8
x=-0*self.sx,
+
self.shield.currtimer = self.shield.timer
y=0*self.sy-shbh/2+self.collbox.h/2,
+
self.shield.damage = 0.1
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
 
end
 
</syntaxhighlight>
 
</syntaxhighlight>
  
== eNme4.lua ==
+
This is almost exactly the same code as the player1, there are three main differences:
"'''eNme4.lua'''" in the '''"_E"''' folder. The code:
+
* some variables were moved to the enemy System, this makes the code for our enemies shorter
<syntaxhighlight lang="lua">
+
* we adjust the enemy parameters based on the difficulty of the game and the Entity id (eid)
ENme4 = Core.class()
+
* we have an extra Component: the '''AI Component'''
  
function ENme4:init(xspritelayer, xpos, dx, dy, xbgfxlayer)
+
'''note''': I only add ground enemies, feel free to create your own when you have finished the tutorial ;-)
-- 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
 
</syntaxhighlight>
 
  
That's it, we have all our enemies!
+
=== AI ===
 +
In order for the enemies to attack the player, we give them an artificial intelligence ability. This Component is only given a name (self.ai), there is no code to it!
  
'''Please note: our enemies have different attacks, some have 2 kind of attacks, some have 3, some can kick, some can jump attack, ...'''
+
The '''AI System''' will filter the entities based on that name. In the AI System our enemies will have different behaviors like movements and attacks.
  
 
== Next? ==
 
== 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'''.
+
Before tackling all the systems that will tie our game together, we will create some more entities for our game: '''moving platforms''', '''doors''', '''collectibles''', ...
  
  
 
Prev.: [[Tuto tiny-ecs 2d platformer Part 6 ECS Components]]</br>
 
Prev.: [[Tuto tiny-ecs 2d platformer Part 6 ECS Components]]</br>
'''Next: [[Tuto tiny-ecs 2d platformer Part 8 Breakables]]'''
+
'''Next: [[Tuto tiny-ecs 2d platformer Part 8 more entities]]'''
  
  
 
'''[[Tutorial - tiny-ecs 2d platformer]]'''
 
'''[[Tutorial - tiny-ecs 2d platformer]]'''
 
{{GIDEROS IMPORTANT LINKS}}
 
{{GIDEROS IMPORTANT LINKS}}

Latest revision as of 09:54, 11 September 2025

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.

eGroundNmes.lua

Please create a file "eGroundNmes.lua" in the "_E" folder and the code:

EGroundNmes = Core.class()

function EGroundNmes:init(xid, xspritelayer, xpos, xbgfxlayer, xcollectible)
	-- ids
	self.isnme = true
	self.eid = xid
	self.ispersistent = false -- keep sprite visible when dead
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- actor holds a collectible?
	if xcollectible then
		self.collectible = xcollectible
	end
	-- params
	self.pos = xpos
	self.sx = 1 -- 0.96
	self.sy = self.sx
	self.totallives = 1 -- basic nme
	self.totalhealth = 3 -- basic nme
	-- 100: no move, no jump, shoot straight, you choose the nme abilities!
	-- 200: move, jump, no shoot, you choose the nme abilities!
	-- 300: no move, no jump, shoot all angles, shield, you choose the nme abilities!
	if self.eid == 200 then
		self.ispersistent = true -- keep sprite visible on dead
		self.totallives = 2
		self.totalhealth = 3
	end
	-- recovery
	self.recovertimer = 10
	self.recoverbadtimer = 30
	self.actiontimer = 90 -- 60, math.random(32, 96), low value=hard, high value=easy
	if g_difficulty == 0 then -- easy
		self.totallives = 1
		self.totalhealth = 3
		self.recovertimer *= 0.5
		self.recoverbadtimer *= 0.5
		self.actiontimer *= 2
	elseif g_difficulty == 2 then -- hard
		self.recovertimer *= 2
		self.recoverbadtimer *= 2
		self.actiontimer *= 0.5
	end
	self.hitfx = Bitmap.new(Texture.new("gfx/fxs/1.png"))
	self.hitfx:setAnchorPoint(0.5, 0.5)
	-- COMPONENTS
	-- ANIMATION
	local anims = {} -- table to hold actor animations
	local animsimgs = {} -- table to hold actor animations images
	-- CAnimation:init(xanimspeed)
	local framerate = 1/10 -- 1/12
	self.animation = CAnimation.new(framerate)
	-- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
	local texpath
	local cols, rows
	if self.eid == 100 then
		texpath = "gfx/nmes/Zombie_0020.png"
		cols, rows = 4, 4
		self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
	elseif self.eid == 200 then
		texpath = "gfx/nmes/Zombie_0040.png"
		cols, rows = 3, 3
		self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
	elseif self.eid == 300 then
		texpath = "gfx/nmes/Zombie_0001.png"
		cols, rows = 5, 3
		self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy)
	end
	-- 1st set of animations: CAnimation:createAnim(xanims, xanimname, xanimsimgs, xtable, xstart, xfinish)
	if self.eid == 100 then
		self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, cols*rows)
		self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, cols*rows) -- fluid is best
	elseif self.eid == 200 then
		self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, cols*rows)
		self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, cols*rows) -- fluid is best
		self.animation:createAnim(anims, g_ANIM_RUN_R, animsimgs, nil, 1, cols*rows) -- fluid is best
	elseif self.eid == 300 then
		self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 5, 5)
		self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 5, 5) -- fluid is best
	end
	-- end animations
	self.animation.anims = anims
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight() -- with applied scale
	-- BODY: CBody:init(xmass, xspeed, xupspeed, xextra)
	if self.eid == 100 then
		self.body = CBody.new(1, 0, 0, true)
	elseif self.eid == 200 then
		self.body = CBody.new(1, 12*8, 64*8, true)
	elseif self.eid == 300 then
		self.body = CBody.new(1, 0, 0, true)
	end
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = (self.w*0.5)//1, (self.h*0.8)//1 -- must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
	-- AI
	self.ai = true
	-- shield
	self.shield = {}
	self.shield.sprite = Pixel.new(0x5555ff, 0.75, 8, collh-6)
	self.shield.sprite.sx = 1
	self.shield.sprite.sy = self.shield.sprite.sx
	self.shield.sprite:setScale(self.shield.sprite.sx, self.shield.sprite.sy)
	self.shield.sprite:setAnchorPoint(0.5, 0.5)
	self.spritelayer:addChild(self.shield.sprite)
	self.shield.offset = vector(collw, (collh+6)*0.5)
	self.shield.timer = 4*8 -- 4*8, 2*8
	self.shield.currtimer = self.shield.timer
	self.shield.damage = 0.1
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 parameters based on the difficulty of the game and the Entity id (eid)
  • we have an extra Component: the AI Component
note: I only add ground enemies, feel free to create your own when you have finished the tutorial ;-)

AI

In order for the enemies to attack the player, we give them an artificial intelligence ability. This Component is only given a name (self.ai), there is no code to it!

The AI System will filter the entities based on that name. In the AI System our enemies will have different behaviors like movements and attacks.

Next?

Before tackling all the systems that will tie our game together, we will create some more entities for our game: moving platforms, doors, collectibles, ...


Prev.: Tuto tiny-ecs 2d platformer Part 6 ECS Components
Next: Tuto tiny-ecs 2d platformer Part 8 more entities


Tutorial - tiny-ecs 2d platformer