Difference between revisions of "Tuto tiny-ecs beatemup Part 6 ECS Components"

From GiderosMobile
(Created page with "__TOC__ == 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 contin...")
 
(wip)
Line 41: Line 41:
 
</syntaxhighlight>
 
</syntaxhighlight>
  
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.
+
That's all we need to tell a System this Entity has a collision box. We store the Entity collision box width and height to be used by systems.
  
=== ids ===
+
=== HURT BOX ===
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.
+
Hurt boxes are not components per se, but they use the collision box size in their parameters. I use two hurt boxes for flexibility: a head hurt box and a spine hurt box.
  
'''All the player1 variables below can be used as ids as well, should we want to narrow down a System filter'''
+
Hurt boxes are rectangular areas the Entity will receive damage to when overlapping another Entity hit box. Hurt boxes are simply tables holding some variables:
 +
* is it active or is the Entity in recovery mode
 +
* its x and y position
 +
* its size
  
=== variables: the basics  ===
+
== ANIMATION ==
The systems will need to know a bunch of information about the Entity it is processing:
+
In the previous chapter we already added the Animation Component and created the basic animations: idle, walk, jump, ... . This time we add the attacks animations ("'''ePlayer1.lua'''"):
* 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:
 
 
<syntaxhighlight lang="lua">
 
<syntaxhighlight lang="lua">
CAnimation = Core.class()
+
-- self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 50, 51) -- 28, 31, no or low anticipation / quick hit / no or low overhead is best
 +
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R] = {}
 +
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][1] = self.animation.myanimsimgs[49]
 +
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][2] = self.animation.myanimsimgs[52]
 +
self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][3] = self.animation.myanimsimgs[54]
 +
self.animation:createAnim(g_ANIM_PUNCH_ATTACK2_R, 50, 54) -- low or mid anticipation / quick hit / low or mid overhead is best
 +
-- self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 62, 63) -- 35, 41, no or low anticipation / quick hit / no or low overhead is best
 +
self.animation.anims[g_ANIM_KICK_ATTACK1_R] = {}
 +
self.animation.anims[g_ANIM_KICK_ATTACK1_R][1] = self.animation.myanimsimgs[62]
 +
self.animation.anims[g_ANIM_KICK_ATTACK1_R][2] = self.animation.myanimsimgs[64]
 +
self.animation.anims[g_ANIM_KICK_ATTACK1_R][3] = self.animation.myanimsimgs[67]
 +
self.animation:createAnim(g_ANIM_KICK_ATTACK2_R, 62, 68) -- low or mid anticipation / quick hit / low or mid overhead is best
 +
...
 +
</syntaxhighlight>
  
function CAnimation:init(xspritesheetpath, xcols, xrows, xanimspeed, xoffx, xoffy, sx, sy)
+
We give our animations a name plus the starting image and the ending image in the spritesheet. The animations names were declared in the '''"main.lua"''' file. Here it is better to have quick animations with few images.
-- 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)
+
The code just above demonstrates two ways to create animations. We can either use:
self.anims[xanimname] = {}
+
* ''CAnimation:createAnim'' function when the images are contiguous in the spritesheet (g_ANIM_PUNCH_ATTACK2_R, g_ANIM_KICK_ATTACK2_R)
for i = xstart, xfinish do
+
* manually create the animation table when the images are not contiguous in the spritesheet (g_ANIM_PUNCH_ATTACK1_R, g_ANIM_KICK_ATTACK1_R)
self.anims[xanimname][#self.anims[xanimname]+1] = self.myanimsimgs[i]
 
end
 
end
 
</syntaxhighlight>
 
  
The parameters are:
+
This gives us maximum flexibility!
* 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.
+
When we are done with our animations we can do some clean up.
 
 
==== animations ====
 
Back to the '''"ePlayer1.lua"''' code we create its animations.
 
<syntaxhighlight lang="lua">
 
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
 
...
 
</syntaxhighlight>
 
  
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 ;-)
+
=== HIT BOX ===
 +
Hit boxes are not components per se, but they are somewhat related to the attacks animations. I used two kind of hit boxes: the hit boxes targeting the head and the hit boxes targeting the spine.
  
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).
+
Depending on the attack performed, the hit box will be different in size and will target either another Entity head or spine hurt box. Hit boxes like hurt boxes are tables:
 +
* is it active, the Entity is performing an attack or not
 +
* the animation frame the hit box becomes active
 +
* the animation frame the hit box is no longer active
 +
* the damage it deals
 +
* its x and y position
 +
* its size
  
'''SELF PROMOTION: I made an app to count images in a spritesheet: https://mokatunprod.itch.io/spritesheet-maker-viewer'''
+
== SHADOW ==
 +
XXX
 +
: CShadow:init(xparentw, xshadowsx, xshadowsy)
 +
self.shadow = CShadow.new(self.w*0.6)
  
 
== Next? ==
 
== Next? ==

Revision as of 00:04, 20 November 2024

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 a System this Entity has a collision box. We store the Entity collision box width and height to be used by systems.

HURT BOX

Hurt boxes are not components per se, but they use the collision box size in their parameters. I use two hurt boxes for flexibility: a head hurt box and a spine hurt box.

Hurt boxes are rectangular areas the Entity will receive damage to when overlapping another Entity hit box. Hurt boxes are simply tables holding some variables:

  • is it active or is the Entity in recovery mode
  • its x and y position
  • its size

ANIMATION

In the previous chapter we already added the Animation Component and created the basic animations: idle, walk, jump, ... . This time we add the attacks animations ("ePlayer1.lua"):

--	self.animation:createAnim(g_ANIM_PUNCH_ATTACK1_R, 50, 51) -- 28, 31, no or low anticipation / quick hit / no or low overhead is best
	self.animation.anims[g_ANIM_PUNCH_ATTACK1_R] = {}
	self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][1] = self.animation.myanimsimgs[49]
	self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][2] = self.animation.myanimsimgs[52]
	self.animation.anims[g_ANIM_PUNCH_ATTACK1_R][3] = self.animation.myanimsimgs[54]
	self.animation:createAnim(g_ANIM_PUNCH_ATTACK2_R, 50, 54) -- low or mid anticipation / quick hit / low or mid overhead is best
--	self.animation:createAnim(g_ANIM_KICK_ATTACK1_R, 62, 63) -- 35, 41, no or low anticipation / quick hit / no or low overhead is best
	self.animation.anims[g_ANIM_KICK_ATTACK1_R] = {}
	self.animation.anims[g_ANIM_KICK_ATTACK1_R][1] = self.animation.myanimsimgs[62]
	self.animation.anims[g_ANIM_KICK_ATTACK1_R][2] = self.animation.myanimsimgs[64]
	self.animation.anims[g_ANIM_KICK_ATTACK1_R][3] = self.animation.myanimsimgs[67]
	self.animation:createAnim(g_ANIM_KICK_ATTACK2_R, 62, 68) -- low or mid anticipation / quick hit / low or mid overhead is best
...

We give our animations a name plus the starting image and the ending image in the spritesheet. The animations names were declared in the "main.lua" file. Here it is better to have quick animations with few images.

The code just above demonstrates two ways to create animations. We can either use:

  • CAnimation:createAnim function when the images are contiguous in the spritesheet (g_ANIM_PUNCH_ATTACK2_R, g_ANIM_KICK_ATTACK2_R)
  • manually create the animation table when the images are not contiguous in the spritesheet (g_ANIM_PUNCH_ATTACK1_R, g_ANIM_KICK_ATTACK1_R)

This gives us maximum flexibility!

When we are done with our animations we can do some clean up.

HIT BOX

Hit boxes are not components per se, but they are somewhat related to the attacks animations. I used two kind of hit boxes: the hit boxes targeting the head and the hit boxes targeting the spine.

Depending on the attack performed, the hit box will be different in size and will target either another Entity head or spine hurt box. Hit boxes like hurt boxes are tables:

  • is it active, the Entity is performing an attack or not
  • the animation frame the hit box becomes active
  • the animation frame the hit box is no longer active
  • the damage it deals
  • its x and y position
  • its size

SHADOW

XXX

CShadow:init(xparentw, xshadowsx, xshadowsy)

self.shadow = CShadow.new(self.w*0.6)

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


Tutorial - tiny-ecs beatemup