Tuto tiny-ecs 2d platformer Part 5 ePlayer1
ePlayer1.lua
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 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.
The code:
EPlayer1 = Core.class()
function EPlayer1:init(xspritelayer, xpos, xbgfxlayer)
-- ids
self.isplayer1 = true
self.doanimate = true -- to save some cpu
self.ispersistent = true -- keep sprite visible when dead
-- sprite layers
self.spritelayer = xspritelayer
self.bgfxlayer = xbgfxlayer
-- params
self.pos = xpos
self.sx = 1 -- 0.96, 0.8, 1.05, 1.1, 1.2
self.sy = self.sx
self.flip = 1
self.totallives = 3
self.totalhealth = 3
if g_difficulty == 0 then -- easy
self.totallives *= 2
self.totalhealth *= 2
end
self.currlives = self.totallives
self.currhealth = self.totalhealth
-- recovery
self.washurt = 0
self.wasbadlyhurt = 0
self.recovertimer = 20 -- 30
self.recoverbadtimer = 60 -- 90
if g_difficulty == 0 then -- easy
self.recovertimer *= 2
self.recoverbadtimer *= 2
elseif g_difficulty == 2 then -- hard
self.recovertimer *= 0.5
self.recoverbadtimer *= 0.5
end
self.hitfx = Bitmap.new(Texture.new("gfx/fxs/2.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/14, 1/18
self.animation = CAnimation.new(framerate)
-- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
local texpath = "gfx/player1/Matt_0001.png"
self.animation:cutSpritesheet(texpath, 9, 6, animsimgs, 0, 1, self.sx, self.sy)
-- 1st set of animations: CAnimation:createAnim(xanims, xanimname, xanimsimgs, xtable, xstart, xfinish)
self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, 15)
self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, 15) -- fluid is best
self.animation:createAnim(anims, g_ANIM_RUN_R, animsimgs, nil, 16, 22) -- fluid is best
self.animation:createAnim(anims, g_ANIM_JUMPUP_R, animsimgs, nil, 23, 23) -- fluid is best
self.animation:createAnim(anims, g_ANIM_JUMPDOWN_R, animsimgs, nil, 23, 25) -- fluid is best
self.animation:createAnim(anims, g_ANIM_LADDER_IDLE_R, animsimgs, nil, 1, 15) -- fluid is best
self.animation:createAnim(anims, g_ANIM_LADDER_UP_R, animsimgs, nil, 16, 22) -- fluid is best
self.animation:createAnim(anims, g_ANIM_LADDER_DOWN_R, animsimgs, nil, 16, 22) -- fluid is best
self.animation:createAnim(anims, g_ANIM_WALL_IDLE_R, animsimgs, nil, 33, 33) -- fluid is best
self.animation:createAnim(anims, g_ANIM_WALL_UP_R, animsimgs, nil, 26, 37) -- fluid is best
self.animation:createAnim(anims, g_ANIM_WALL_DOWN_R, animsimgs, nil, 43, 54) -- fluid is best
self.animation:createAnim(anims, g_ANIM_HURT_R, animsimgs, { 38, 39, 38, 39, 1, }) -- fluid is best
self.animation:createAnim(anims, g_ANIM_LOSE1_R, animsimgs, nil, 38, 42) -- fluid is best
self.animation:createAnim(anims, g_ANIM_STANDUP_R, animsimgs, { 42, 41, 40, 39, 1, }) -- fluid is best
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
print("player1 size (scaled): ", self.w, self.h)
-- BODY: CBody:init(xmass, xspeed, xupspeed, xextra)
self.body = CBody.new(1, 18*8, 68*8, true) -- (1, 22*8, 70*8, true)
-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
local collw, collh = (self.w*0.3)//1, (self.h*0.75)//1 -- must be round numbers for cbump physics!
self.collbox = CCollisionBox.new(collw, collh)
-- shield
self.shield = {}
-- self.shield.sprite = Bitmap.new(Texture.new("gfx/fxs/Husky_0001.png"))
self.shield.sprite = Pixel.new(0xffaa00, 0.75, 8, collh+4)
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:setAlpha(0.8)
self.shield.sprite:setAnchorPoint(0.5, 0.5)
self.spritelayer:addChild(self.shield.sprite)
self.shield.offset = vector(collw, (collh-4)*0.5) -- (5*8, -1*8)
self.shield.timer = 3*8 -- 2*8
self.shield.currtimer = self.shield.timer
self.shield.damage = 0.5
end
Code comments
Let's break it down!
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, 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
- 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.
fx
self.hitfx is one of those sprite we add to the fxlayer when the player1 is hit by 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:
CAnimation = Core.class()
function CAnimation:init(xanimspeed)
-- animation
self.curranim = g_ANIM_DEFAULT
self.frame = 0
self.animspeed = xanimspeed
self.animtimer = self.animspeed
end
function CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
-- retrieve all anims in texture
local myanimstex = Texture.new(xspritesheetpath)
local cellw = myanimstex:getWidth()/xcols
local cellh = myanimstex:getHeight()/xrows
for r = 1, xrows do
for c = 1, xcols do
local myanimstexregion = TextureRegion.new(
myanimstex, (c-1)*cellw, (r-1)*cellh, cellw, cellh
)
xanimsimgs[#xanimsimgs + 1] = myanimstexregion
end
end
-- the bitmap
self.bmp = Bitmap.new(xanimsimgs[1]) -- starting bmp texture
self.bmp:setScale(sx, sy) -- scale!
self.bmp:setAnchorPoint(0.5, 0.5) -- we will flip the bitmap
-- set position inside sprite
self.bmp:setPosition(xoffx*sx, xoffy*sy) -- work best with image centered spritesheets
-- our final sprite
self.sprite = Sprite.new()
self.sprite:addChild(self.bmp)
self.bmp:setColorTransform(8*32/255, 8*32/255, 8*32/255, 9*32/255)
end
function CAnimation:createAnim(xanims, xanimname, xanimsimgs, xtable, xstart, xfinish)
xanims[xanimname] = {}
if xtable and #xtable > 0 then
for i = 1, #xtable do
xanims[xanimname][#xanims[xanimname]+1] = xanimsimgs[xtable[i]]
end
else
for i = xstart, xfinish do
xanims[xanimname][#xanims[xanimname]+1] = xanimsimgs[i]
end
end
end
In the init function we simply indicate the animation speed, then we initialize the basics variables.
Then the Animation Component cuts the spritesheet into single images.
Finally the Animation Component creates the animations table with the animation name and images.
animations
Back to the "ePlayer1.lua" code we create the animations.
To add images to an animation you can pass a table with the spritesheet images cell number:
self.animation:createAnim(anims, g_ANIM_STANDUP_R, animsimgs, { 42, 41, 40, 39, 1, }, nil)
Or you assign the starting image and the ending image in the spritesheets:
self.animation:createAnim(anims, g_ANIM_DEFAULT, animsimgs, nil, 1, 15)
self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, 15)
The g_ANIM_DEFAULT is a fallback animation in case an animation doesn't exist.
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
Next?
To shorten the length of this part, let's see the other components in the next chapter.
Prev.: Tuto tiny-ecs 2d platformer Part 4 LevelX
Next: Tuto tiny-ecs 2d platformer Part 6 ECS Components
Tutorial - tiny-ecs 2d platformer