Tuto tiny-ecs beatemup Part 6 ECS Components
Components
As we have seen in the previous chapter, components are abilities we add to an Entity. Entities can and will share the same components.
Let's continue adding abilities to our player1.
BODY
The Body Component adds an Entity the ability to move both on the x and y axis. You can create a file called "cBody.lua" in the "_C" folder. The code:
CBody = Core.class()
function CBody:init(xspeed, xjumpspeed)
-- body physics properties
self.vx = 0
self.vy = 0
self.speed = xspeed
self.currspeed = self.speed
self.jumpspeed = xjumpspeed
self.currjumpspeed = self.jumpspeed
self.isonfloor = true
self.isgoingup = false
end
The CBody:init function signature has only two parameters: the speed on the x axis and the jumpspeed.
We store other variables to keep track of an Entity current state.
Components are usually small pieces of code, which is nice!
COLLISION BOX
The CollisionBox Component adds an Entity a collision box (wouah, great naming here!). You can create a file called "cCollisionBox.lua" in the "_C" folder. The code:
CCollisionBox = Core.class()
function CCollisionBox:init(xcollwidth, xcollheight)
self.w = xcollwidth
self.h = xcollheight
end
That's all we need to tell our systems this Entity has a collision box, we store the width and the height of that collision box for that Entity.
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:
CAnimation = Core.class()
function CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
-- animation
self.curranim = g_ANIM_DEFAULT
self.frame = 0
self.animspeed = xanimspeed
self.animtimer = self.animspeed
-- retrieve all anims in texture
local myanimstex = Texture.new(xspritesheetpath)
local cellw = myanimstex:getWidth() / xcols
local cellh = myanimstex:getHeight() / xrows
self.myanimsimgs = {}
local myanimstexregion
for r = 1, xrows do
for c = 1, xcols do
myanimstexregion = TextureRegion.new(myanimstex, (c - 1) * cellw, (r - 1) * cellh, cellw, cellh)
self.myanimsimgs[#self.myanimsimgs + 1] = myanimstexregion
end
end
-- anims table ("walk", "jump", "shoot", ...)
self.anims = {}
-- the bitmap
self.bmp = Bitmap.new(self.myanimsimgs[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)
end
function CAnimation:createAnim(xanimname, xstart, xfinish)
self.anims[xanimname] = {}
for i = xstart, xfinish do
self.anims[xanimname][#self.anims[xanimname]+1] = self.myanimsimgs[i]
end
end
The parameters are:
- 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.
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
...
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
Next?
To shorten the length of this part, let's see the other components in the next chapter.
Prev.: Tuto tiny-ecs beatemup Part 5 ePlayer1
Next: Tuto tiny-ecs beatemup Part 7 XXX