Tuto tiny-ecs 2d platformer Part 8 more entities

From GiderosMobile

Some more Entities

A game is made of many "actors" besides the players and the enemies. Here we will add more "actors" aka entities (collectibles, doors, keys, ...).

Most of the time we create our entities in Tiled as seen in Tuto_tiny-ecs_2d_platformer_Part_4_LevelX. When we create an Entity we pass arguments like the position, the size, colors, ...

Here are some examples of how entities are parsed from Tiled in _tiled_levels.lua:

	...
	elseif layer.name == "physics_keys" then
		for i = 1, #layer.objects do
			o = layer.objects[i]
			local xid = o.name -- example "doorAA", "mvpfBB", ...
			xid = "key"..tostring(xid) -- we append the keyword key to tell it is a key XXX
			local opos = vector(o.x, o.y)
			--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
			local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
			xtiny.tworld:addEntity(e)
			xbworld:add(e, e.pos.x, e.pos.y, e.collbox.w, e.collbox.h)
		end
	elseif layer.name == "physics_coins" then
		...
			--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
			local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 4*8, 4*8)
		...
	elseif layer.name == "physics_lives" then
		...
			--ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
			local e = ECollectibles.new(xid, xlayers["actors"], opos, 1*8, 5*8, 4*8)
	...

The passing of arguments to create an Entity is called the function signature, here we can say the Entity signature.

As I have mentioned before, components are shared amongst entities and this is exactly the case here

eCollectibles.lua

Let's add some collectibles, shall we? Please create a file called "eCollectibles.lua" in the "_E" folder. The code:

ECollectibles = Core.class()

function ECollectibles:init(xid, xspritelayer, xpos, xspeed, xdx, xdy)
	-- ids
	self.iscollectible = true
	self.eid = xid
	self.doanimate = true -- to save some cpu
	-- sprite layer
	self.spritelayer = xspritelayer
	-- params
	self.pos = xpos
	self.sx = 1
	self.sy = self.sx
	self.flip = 1
	self.totallives = 1
	self.currlives = self.totallives
	-- default to coin
	local framerate = 1/10 -- magik XXX
	local texpath = "gfx/collectibles/Coin_A_0_0001.png"
	local cols, rows = 4, 3
	self.sx = 0.7
	if self.eid == "lives" then -- heart
		texpath = "gfx/collectibles/Heart_2_0001.png"
		cols, rows = 4, 3
		self.sx = 0.8
	elseif self.eid:find("door") then -- key
		texpath = "gfx/collectibles/Key_3_0001.png"
		cols, rows = 4, 3
		self.sx = 0.8
	end
	self.sy = self.sx
	-- COMPONENTS
	-- ANIMATION
	local anims = {} -- table to hold actor animations
	local animsimgs = {} -- table to hold actor animations images
	-- CAnimation:init(xanimspeed)
	self.animation = CAnimation.new(framerate)
	-- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
	self.animation:cutSpritesheet(texpath, cols, rows, animsimgs, 0, 0, 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, cols*rows)
	self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, cols*rows)
	-- end animations
	self.animation.anims = anims
	self.sprite = self.animation.sprite
	self.sprite:setScale(self.sx*self.flip, self.sy)
	self.animation.sprite = nil -- free some memory
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight() -- with applied scale
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w//1, self.h//1 -- must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
	-- COscillation:init(xspeed, xamplitudex, xamplitudey)
	self.oscillation = COscillation.new(xspeed, xdx, xdy)
end

When we create a collectible, we assign it an entity id (eid). Depending on the eid we will either spawn a coin, a life (to heal the player) or a key (to open doors).

The code follows the same principle as the previous entities (player1 and enemies). The only thing we add here is an oscillation Component to give our collectibles some juice. Please create a file called "cOscillation.lua" in the "_C" folder. The code:

COscillation = Core.class()

function COscillation:init(xspeed, xamplitudex, xamplitudey)
	self.vx = 0
	self.vy = 0
	self.speed = xspeed
	self.amplitudex = xamplitudex
	self.amplitudey = xamplitudey
end

An oscillation System will look for it and act accordingly. Easy as E C S ;-)

eDoor.lua

We need some doors keys can open. Please create a file called "eDoor.lua" in the "_E" folder. The code:

EDoor = Core.class()

function EDoor:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xbgfxlayer)
	local function hexToRgb(hex)
		return (hex >> 16 & 0xff), (hex >> 8 & 0xff), (hex & 0xff)
	end
	-- ids
	self.isdoor = true
	self.eid = xid -- player1 needs key matching eid
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1
	self.sy = self.sx
	self.flip = 1
	if xtexpath then -- texture + color modulate?
		local tex = Texture.new(xtexpath)
		local texsx, texsy = tex:getWidth()/w, tex:getHeight()/h
		self.sprite = Pixel.new(tex, w, h, texsx, texsy)
		if xcolor then -- color modulate
			local r, g, b = hexToRgb(xcolor)
			self.sprite:setColorTransform(r/255, g/255, b/255, 1)
		end
	else -- color only
		self.sprite = Pixel.new(xcolor or 0xffffff, 1, w, h)
	end
	self.sprite:setAnchorPoint(0.5, 0.5)
	self.sprite:setScale(self.sx, self.sy)
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- COMPONENTS
	-- BODY: CBody:init(xmass, xspeed, xupspeed)
	self.body = CBody.new(1, xspeed.x, xspeed.y)
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w//1, self.h//1 -- must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
	-- motion AI: CDistance:init(xstartpos, dx, dy)
	self.distance = CDistance.new(self.pos, dx, dy)
	self.dir = xdir
end

What's in there? When we create this door Entity we can texture or color it so the doors are not all the same, plus it has a distance Component to control how far a door can open. Please create a file called "cDistance.lua" in the "_C" folder. The code:

CDistance = Core.class()

function CDistance:init(xstartpos, dx, dy)
	self.startpos = xstartpos
	self.dx = dx -- delta x
	self.dy = dy -- delta y
end

A System will look for it and act accordingly.

eMvpf.lua

What is a platformer without moving platforms? Please create a file called "eMvpf.lua" in the "_E" folder. The code:

EMvpf = Core.class()

function EMvpf:init(xid, xspritelayer, xpos, xtexpath, xcolor, w, h, dx, dy, xdir, xspeed, xispt, xbgfxlayer)
	local function hexToRgb(hex)
		return (hex >> 16 & 0xff), (hex >> 8 & 0xff), (hex & 0xff)
	end
	-- ids
	self.eid = xid
	self.ismvpf = true
	if xispt then -- passthrough moving platform
		self.isptmvpf = true
	end
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1
	self.sy = self.sx
	self.flip = 1
	if xtexpath then -- texture + color modulate?
		local tex = Texture.new(xtexpath)
		local texsx, texsy = tex:getWidth()/w, tex:getHeight()/h
		self.sprite = Pixel.new(tex, w, h, texsx, texsy) -- letterbox (tex,w,h,tex_scale_x,tex_scale_y,tex_ax,tex_ay)
		if xcolor then -- color modulate
			local r, g, b = hexToRgb(xcolor)
			self.sprite:setColorTransform(r/255, g/255, b/255, 1)
		end
	else -- color only
		self.sprite = Pixel.new(xcolor or 0xffffff, 1, w, h)
	end
	self.sprite:setAnchorPoint(0.5, 0.5)
	self.sprite:setScale(self.sx, self.sy)
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- COMPONENTS
	-- BODY: CBody:init(xmass, xspeed, xupspeed)
	self.body = CBody.new(0, xspeed.x, xspeed.y) -- xmass, xspeed, xupspeed
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w//1, self.h//1 -- must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
	-- motion AI: CDistance:init(xstartpos, dx, dy)
	self.distance = CDistance.new(self.pos, dx, dy)
	-- initiate moving platform movement
	self.dir = xdir
	if self.dir:match("U") then self.isup = true end
	if self.dir:match("D") then self.isdown = true end
	if self.dir:match("L") then self.isleft = true end
	if self.dir:match("R") then self.isright = true end
end

Same as a door Entity, we can texture or color it. The variable self.dir initializes and starts the moving platform in a given direction. That's it, no new components.

A System will control the doors.

eProjectiles.lua

The actors will throw projectiles. Please create a file called "eProjectiles.lua" in the "_E" folder. The code:

EProjectiles = Core.class()

function EProjectiles:init(xid, xmass, xangle, xspritelayer, xpos, xvx, xvy, dx, dy, xpersist)
	-- ids
	self.isprojectile = true
	self.eid = xid
	self.doanimate = false -- if not animated set to false to save some cpu
	self.ispersistant = xpersist -- pierce through nmes
	-- sprite layer
	self.spritelayer = xspritelayer
	-- params
	self.pos = xpos
	self.sx = 0.8
	self.sy = 1 -- self.sx
	self.flip = 1
	self.totallives = 1
	self.currlives = self.totallives
	-- recovery
	self.washurt = 0
	self.wasbadlyhurt = 0
	self.recovertimer = 30
	self.recoverbadtimer = 90
	-- COMPONENTS
	-- ANIMATION
	local anims = {} -- table to hold actor animations
	local animsimgs = {} -- table to hold actor animations images
	-- CAnimation:init(xanimspeed)
	local framerate = 1/10
	self.animation = CAnimation.new(framerate)
	-- CAnimation:cutSpritesheet(xspritesheetpath, xcols, xrows, xanimsimgs, xoffx, xoffy, sx, sy)
	local texpath = "gfx/ammos/bullet1_0001.png" -- player1 bullets
	if self.eid == 100 then -- nmes bullets
		texpath = "gfx/ammos/bullet1_0010.png"
	end
	self.animation:cutSpritesheet(texpath, 1, 1, animsimgs, 0, 0, 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, 1)
	self.animation:createAnim(anims, g_ANIM_IDLE_R, animsimgs, nil, 1, 1)
	-- end animations
	self.animation.anims = anims
	self.sprite = self.animation.sprite
	self.animation.sprite = nil -- free some memory
	self.sprite:setScale(self.sx*self.flip, self.sy)
	self.sprite:setRotation(^>xangle) -- ^>rad, convert radians to degrees
	self.w, self.h = self.sprite:getWidth(), self.sprite:getHeight()
	-- BODY: CBody:init(xmass, xspeed, xupspeed)
	self.body = CBody.new(xmass or 0, xvx, xvy)
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = (self.w*0.5)//1, (self.h*0.5)//1 -- must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
	-- AI: CDistance:init(xstartpos, dx, dy)
	self.dist = CDistance.new(self.pos, dx, dy)
end

Nothing new here!

A System will control the projectiles.

eSensor.lua

The sensor Entity will tell if an actor is in a given area and will perform certain tasks. In our game we use sensors to move the doors open or close.

Please create a file called "eSensor.lua" in the "_E" folder. The code:

ESensor = Core.class()

function ESensor:init(xid, xspritelayer, xpos, w, h, xbgfxlayer)
	-- ids
	self.issensor = true
	self.eid = xid -- sensor will assign its eid to player inventory (doorid, keyid, ...)
--	print("issensor", self.eid)
	-- sprite layers
	self.spritelayer = xspritelayer
	self.bgfxlayer = xbgfxlayer
	-- params
	self.pos = xpos
	self.sx = 1
	self.sy = self.sx
	self.flip = 1
	self.w, self.h = w*self.sx, h*self.sy
	-- COMPONENTS
	-- COLLISION BOX: CCollisionBox:init(xcollwidth, xcollheight)
	local collw, collh = self.w//1, self.h//1 -- full size collision box, must be round numbers for cbump physics!
	self.collbox = CCollisionBox.new(collw, collh)
end

A sensor will check the player1 inventory for a given item (key id, ...). When there is a match between the sensor id and the inventory id, the task is performed.

A System will control the sensors.

Next?

We are done making all our entities!

Remember: entites are nothing more than a bunch of variables

And now, the time has come to tackle the systems. I will try to make it easy :-)


Prev.: Tuto tiny-ecs 2d platformer Part 7 Enemies
Next: Tuto tiny-ecs 2d platformer Part 9 Systems


Tutorial - tiny-ecs 2d platformer