Difference between revisions of "Tuto tiny-ecs 2d platformer Part 7 Enemies"
(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 | + | 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: | |
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
− | + | EGroundNmes = Core.class() | |
− | function | + | 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. | + | self.sx = 1 -- 0.96 |
self.sy = self.sx | self.sy = self.sx | ||
− | self.totallives = | + | self.totallives = 1 -- basic nme |
− | self.totalhealth = | + | 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 | |
− | if | + | self.ispersistent = true -- keep sprite visible on dead |
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
self.totallives = 2 | self.totallives = 2 | ||
self.totalhealth = 3 | self.totalhealth = 3 | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
-- recovery | -- recovery | ||
− | self.recovertimer = | + | 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 = | + | 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.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/ | + | 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 | + | -- 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 | local framerate = 1/10 -- 1/12 | ||
− | self.animation = CAnimation.new( | + | self.animation = CAnimation.new(framerate) |
− | + | -- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy) | |
− | self. | + | local texpath |
− | + | local cols, rows | |
− | + | if self.eid == 100 then | |
− | + | texpath = "gfx/nmes/Zombie_0020.png" | |
− | + | cols, rows = 4, 4 | |
− | self. | + | self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy) |
− | + | elseif self.eid == 200 then | |
− | + | texpath = "gfx/nmes/Zombie_0040.png" | |
− | self. | + | 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. | + | self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 1, self.sx, self.sy) |
end | 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 | |
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
end | end | ||
− | + | -- end animations | |
− | + | self.animation.anims = anims | |
− | -- | ||
− | |||
− | |||
− | |||
− | self.animation = | ||
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 |
− | -- | + | -- BODY: CBody:init(xmass, xspeed, xupspeed, xextra) |
− | + | if self.eid == 100 then | |
− | self. | + | 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. | + | self.body = CBody.new(1, 0, 0, true) |
− | |||
− | |||
− | self.body = CBody.new( | ||
− | |||
− | self.body = CBody.new( | ||
end | end | ||
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight) | -- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight) | ||
− | local collw, collh = self.w*0. | + | 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) | ||
− | -- | + | -- 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 | |
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | |||
− | self. | ||
− | |||
− | self. | ||
end | end | ||
</syntaxhighlight> | </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 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? == | == Next? == | ||
− | Before tackling all the systems that will tie our game together, we will create | + | 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 | + | '''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