Difference between revisions of "Tuto tiny-ecs beatemup Part 9 Systems"
(wip) |
(wip) |
||
Line 375: | Line 375: | ||
The other thing worth noting is an enemy ability. Depending on the attacks an Entity has, they are stored in an ''abilities'' table. In a artificial intelligence System we will add shortly, the code will iterate the ''abilities'' table and pick a random attack an Entity can perform. | The other thing worth noting is an enemy ability. Depending on the attacks an Entity has, they are stored in an ''abilities'' table. In a artificial intelligence System we will add shortly, the code will iterate the ''abilities'' table and pick a random attack an Entity can perform. | ||
− | == | + | == sAI.lua == |
− | "''' | + | "'''sAI.lua'''" in the '''"_S"''' folder. The code: |
<syntaxhighlight lang="lua"> | <syntaxhighlight lang="lua"> | ||
+ | SAI = Core.class() | ||
+ | |||
+ | local random = math.random | ||
+ | |||
+ | function SAI:init(xtiny, xplayer1) -- tiny function | ||
+ | xtiny.processingSystem(self) -- called once on init and every update | ||
+ | self.player1 = xplayer1 | ||
+ | end | ||
+ | |||
+ | function SAI:filter(ent) -- tiny function | ||
+ | return ent.ai | ||
+ | end | ||
+ | |||
+ | function SAI:onAdd(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | function SAI:onRemove(ent) -- tiny function | ||
+ | end | ||
+ | |||
+ | local p1rangex = 192 -- 192 -- magik XXX | ||
+ | local p1hitrangex = 64 -- magik XXX | ||
+ | local p1rangey = 96 -- 96 -- magik XXX | ||
+ | local p1hitrangey = 16 -- magik XXX | ||
+ | local rndaction = 0 -- random punch/kick action | ||
+ | local p1rangetoofar = myappwidth -- disable system to save some CPU, magik XXX | ||
+ | function SAI:process(ent, dt) -- tiny function | ||
+ | if ent.isdead then return end | ||
+ | local function fun() | ||
+ | -- some flags | ||
+ | ent.doanimate = true -- to save some cpu | ||
+ | ent.readytohit = false | ||
+ | if (ent.pos.x > self.player1.pos.x + p1rangetoofar or ent.pos.x < self.player1.pos.x - p1rangetoofar) then | ||
+ | ent.doanimate = false | ||
+ | return | ||
+ | end | ||
+ | if (ent.pos.x > self.player1.pos.x + p1rangex or ent.pos.x < self.player1.pos.x - p1rangex) or | ||
+ | (ent.pos.y > self.player1.pos.y + p1rangey or ent.pos.y < self.player1.pos.y - p1rangey) then -- OUTSIDE ATTACK RANGE | ||
+ | -- idle | ||
+ | ent.isleft, ent.isright = false, false | ||
+ | ent.isup, ent.isdown = false, false | ||
+ | ent.body.currspeed = ent.body.speed | ||
+ | ent.body.currjumpspeed = ent.body.jumpspeed | ||
+ | else -- INSIDE ATTACK RANGE | ||
+ | -- x | ||
+ | if ent.pos.x > random(self.player1.pos.x+p1hitrangex, self.player1.pos.x+p1rangex) then | ||
+ | ent.isleft, ent.isright = true, false | ||
+ | ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX | ||
+ | elseif ent.pos.x < random(self.player1.pos.x-p1rangex, self.player1.pos.x-p1hitrangex) then | ||
+ | ent.isleft, ent.isright = false, true | ||
+ | ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX | ||
+ | end | ||
+ | -- y | ||
+ | if ent.pos.y > random(self.player1.pos.y, self.player1.pos.y+p1hitrangey) then | ||
+ | ent.isup, ent.isdown = true, false | ||
+ | ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX | ||
+ | ent.readytohit = true | ||
+ | elseif ent.pos.y < random(self.player1.pos.y-p1hitrangey, self.player1.pos.y) then | ||
+ | ent.isup, ent.isdown = false, true | ||
+ | ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX | ||
+ | ent.readytohit = true | ||
+ | end | ||
+ | -- nmes always face player1 | ||
+ | if not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | if ent.pos.x > self.player1.pos.x then ent.flip = -1 | ||
+ | else ent.flip = 1 | ||
+ | end | ||
+ | end | ||
+ | end | ||
+ | -- ATTACK | ||
+ | if ent.readytohit then | ||
+ | ent.curractiontimer -= 1 | ||
+ | if ent.curractiontimer < 0 then | ||
+ | ent.animation.frame = 0 | ||
+ | rndaction = ent.abilities[random(#ent.abilities)] -- pick a random attack | ||
+ | if rndaction == 1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.isactionpunch1 = true | ||
+ | elseif rndaction == 2 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.isactionpunch2 = true | ||
+ | elseif rndaction == 3 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.isactionkick1 = true | ||
+ | elseif rndaction == 4 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.isactionkick2 = true | ||
+ | elseif rndaction == 5 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.positionystart = ent.pos.y | ||
+ | ent.body.isonfloor = false | ||
+ | ent.body.isgoingup = true | ||
+ | ent.isactionjumppunch1 = true | ||
+ | ent.body.currspeed *= 1+random(4)*0.1 -- randomize speed, you choose to add it and the params | ||
+ | -- jump in the direction of the flip | ||
+ | if ent.flip == 1 then ent.isleft = false ent.isright = true | ||
+ | else ent.isleft = true ent.isright = false | ||
+ | end | ||
+ | elseif rndaction == 6 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then | ||
+ | ent.positionystart = ent.pos.y | ||
+ | ent.body.isonfloor = false | ||
+ | ent.body.isgoingup = true | ||
+ | ent.isactionjumpkick1 = true | ||
+ | ent.body.currspeed *= 1+random(3)*0.1 -- randomize speed, you choose to add it and the params | ||
+ | -- jump in the direction of the flip | ||
+ | if ent.flip == 1 then ent.isleft = false ent.isright = true | ||
+ | else ent.isleft = true ent.isright = false | ||
+ | end | ||
+ | end | ||
+ | ent.curractiontimer = ent.actiontimer | ||
+ | end | ||
+ | end | ||
+ | Core.yield(1) | ||
+ | end | ||
+ | Core.asyncCall(fun) -- profiler seems to be faster without asyncCall (because of pairs traversing?) | ||
+ | end | ||
</syntaxhighlight> | </syntaxhighlight> | ||
− | This System | + | This System controls all the entities with an artificial intelligence (''ai'') id. In this System an entity can be in an '''idle''' state, a '''move''' state or an '''attack''' state. Each states are applied relative to the distance between the Entity and the ''player1''. |
− | |||
− | |||
− | |||
− | |||
== Next? == | == Next? == |
Revision as of 18:01, 22 November 2024
The Systems
We have our entities, we have our components, now the systems. What is an ECS System?
A System is a wrapper around function callbacks for manipulating Entities. Systems are implemented as tables that contain at least one method; an update function that takes parameters like so: function system:update(dt) There are also a few other optional callbacks: *function system:filter(entity) Returns true if this System should include this Entity, otherwise should return false. If this isn't specified, no Entities are included in the System. *function system:onAdd(entity) Called when an Entity is added to the System. *function system:onRemove(entity) Called when an Entity is removed from the System. *function system:onModify(dt) Called when the System is modified by adding or removing Entities from the System. *function system:onAddToWorld(world) Called when the System is added to the World, before any entities are added to the system. *function system:onRemoveFromWorld(world) Called when the System is removed from the world, after all Entities are removed from the System. *function system:preWrap(dt) Called on each system before update is called on any system. *function system:postWrap(dt) Called on each system in reverse order after update is called on each system. Please see Tiny-ecs#System_functions for more information
That looks scary but worry not we won't use all the callback functions :-)
To put it simple a System manipulates entities. Let's see our first System.
sDrawable.lua
Please create a file "sDrawable.lua" in the "_S" folder and the code:
SDrawable = Core.class()
function SDrawable:init(xtiny) -- tiny function
xtiny.system(self) -- called only once on init (no update)
end
function SDrawable:filter(ent) -- tiny function
return ent.spritelayer and ent.sprite
end
function SDrawable:onAdd(ent) -- tiny function
-- print("SDrawable:onAdd(ent)")
ent.spritelayer:addChild(ent.sprite)
end
function SDrawable:onRemove(ent) -- tiny function
-- print("SDrawable:onRemove(ent)")
ent.spritelayer:removeChild(ent.sprite)
-- cleaning?
ent.sprite = nil
ent = nil
end
What it does:
- runs only once when it is called
- affects only entities which have a spritelayer variable (id) and a sprite variable (id)
- when an Entity is added to tiny-ecs World, the System adds the Entity to its Sprite layer
- when an Entity is removed from tiny-ecs World, the System removes the Entity from its Sprite layer
In other words, the System adds an Entity to a Sprite layer when the Entity is added to tiny-ecs World, and removes it from that Sprite layer when the Entity is destroyed.
sPlayer1Control.lua
I am adding systems in an order that seems logical and helps in understanding the making of the game. The next System we add is the player1 controller.
"sPlayer1Control.lua" in the "_S" folder. The code:
SPlayer1Control = Core.class()
function SPlayer1Control:init(xtiny, xplayer1inputlayer) -- tiny function
xtiny.system(self) -- called only once on init (no update)
self.player1inputlayer = xplayer1inputlayer
end
function SPlayer1Control:filter(ent) -- tiny function
return ent.isplayer1
end
function SPlayer1Control:onAdd(ent) -- tiny function
self.player1inputlayer:addEventListener(Event.KEY_DOWN, function(e)
if ent.currlives > 0 then
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = true end
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = true end
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = true end
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = true end
-- ACTIONS:
-- isactionpunch1, isactionpunch2, isactionjumppunch1,
-- isactionkick1, isactionkick2, isactionjumpkick1,
-- isactionjump1
if e.keyCode == g_keyaction1 then
ent.animation.frame = 0
ent.isactionpunch1 = true
elseif e.keyCode == g_keyaction2 then
ent.animation.frame = 0
ent.isactionkick1 = true
end
if e.keyCode == g_keyaction3 then
if ent.body.isonfloor then
ent.animation.frame = 0
ent.positionystart = ent.pos.y
ent.body.isonfloor = false
ent.body.isgoingup = true
ent.isactionjump1 = true
end
end
end
end)
self.player1inputlayer:addEventListener(Event.KEY_UP, function(e)
if ent.currlives > 0 then
if e.keyCode == KeyCode.LEFT or e.keyCode == g_keyleft then ent.isleft = false end
if e.keyCode == KeyCode.RIGHT or e.keyCode == g_keyright then ent.isright = false end
if e.keyCode == KeyCode.UP or e.keyCode == g_keyup then ent.isup = false end
if e.keyCode == KeyCode.DOWN or e.keyCode == g_keydown then ent.isdown = false end
-- if e.keyCode == g_keyaction1 then ent.isactionpunch1 = false end
-- if e.keyCode == g_keyaction2 then ent.isactionkick1 = false end
-- if e.keyCode == g_keyaction3 then end
end
end)
end
What it does:
- runs only once when it is called
- affects only entities with the isplayer1 id
- when the player1 Entity is added to tiny-ecs World, the System registers KEY_DOWN and KEY_UP events
The System processes the user keys input and sets various flags to be processed in a future sDynamicBodies System we will add.
sPlayer1.lua
"sPlayer1.lua" in the "_S" folder. The code:
SPlayer1 = Core.class()
function SPlayer1:init(xtiny, xcamera) -- tiny function
self.tiny = xtiny -- ref so we can remove entities from tiny system
self.tiny.processingSystem(self) -- called once on init and every update
-- fx
self.camera = xcamera -- camera shake
-- sfx
self.snd = Sound.new("audio/sfx/sfx_deathscream_human14.wav")
self.channel = self.snd:play(0, false, true)
end
function SPlayer1:filter(ent) -- tiny function
return ent.isplayer1
end
function SPlayer1:onAdd(ent) -- tiny function
end
function SPlayer1:onRemove(ent) -- tiny function
end
local resetanim = true
function SPlayer1:process(ent, dt) -- tiny function
-- hurt fx
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) then
ent.washurt -= 1
ent.animation.curranim = g_ANIM_HURT_R
if ent.washurt < ent.recovertimer*0.5 then ent.hitfx:setVisible(false) end
if ent.washurt <= 0 then
ent.sprite:setColorTransform(1, 1, 1, 1)
self.camera:setZoom(1) -- zoom
end
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 then
ent.hitfx:setVisible(false)
ent.wasbadlyhurt -= 1
ent.animation.curranim = g_ANIM_LOSE1_R
if ent.wasbadlyhurt < ent.recoverbadtimer/2 then
if resetanim then
resetanim = false
ent.animation.frame = 0
end
ent.animation.curranim = g_ANIM_STANDUP_R
end
if ent.wasbadlyhurt <= 0 then
ent.sprite:setColorTransform(1, 1, 1, 1)
self.camera:setZoom(1) -- zoom
resetanim = true
end
end
if ent.isdirty then -- hit
local function map(v, minSrc, maxSrc, minDst, maxDst, clampValue)
local newV = (v - minSrc) / (maxSrc - minSrc) * (maxDst - minDst) + minDst
return not clampValue and newV or clamp(newV, minDst><maxDst, minDst<>maxDst)
end
self.channel = self.snd:play()
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
ent.hitfx:setVisible(true)
ent.hitfx:setPosition(ent.pos.x+ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.pos.y+ent.headhurtbox.y-ent.headhurtbox.h/2)
ent.spritelayer:addChild(ent.hitfx)
ent.currhealth -= ent.damage
local hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
self.tiny.hudhealth:setWidth(hudhealthwidth)
if ent.currhealth < ent.totalhealth/3 then self.tiny.hudhealth:setColor(0xff0000)
elseif ent.currhealth < ent.totalhealth/2 then self.tiny.hudhealth:setColor(0xff5500)
else self.tiny.hudhealth:setColor(0x00ff00)
end
ent.washurt = ent.recovertimer -- timer for a flash effect
ent.sprite:setColorTransform(2, 0, 0, 2) -- the flash effect (a bright red color)
ent.isdirty = false
self.camera:shake(0.6, 16) -- (duration, distance), you choose
self.camera:setZoom(1.2) -- zoom
if ent.currhealth <= 0 then
ent.wasbadlyhurt = ent.recoverbadtimer -- timer for player1 to stand back up
self.camera:shake(0.8, 64) -- (duration, distance), you choose
ent.currlives -= 1
for i = 1, ent.totallives do self.tiny.hudlives[i]:setVisible(false) end -- dirty but easy XXX
for i = 1, ent.currlives do self.tiny.hudlives[i]:setVisible(true) end -- dirty but easy XXX
if ent.currlives > 0 then
ent.currhealth = ent.totalhealth
hudhealthwidth = map(ent.currhealth, 0, ent.totalhealth, 0, 100)
self.tiny.hudhealth:setWidth(hudhealthwidth)
self.tiny.hudhealth:setColor(0x00ff00)
if ent.currlives == 1 then self.tiny.hudlives[1]:setColor(0xff0000) end
end
end
end
if ent.currlives <= 0 then -- deaded
-- stop all movements
ent.isleft = false
ent.isright = false
ent.isup = false
ent.isdown = false
-- play dead sequence
ent.isdirty = false
resetanim = false
ent.washurt = ent.recovertimer
ent.wasbadlyhurt = ent.recoverbadtimer
ent.animation.curranim = g_ANIM_LOSE1_R
ent.sprite:setColorTransform(255*0.5/255, 255*0.5/255, 255*0.5/255, 1)
self.camera:setZoom(1) -- zoom
ent.animation.bmp:setY(ent.animation.bmp:getY()-1)
if ent.animation.bmp:getY() < -200 then -- you choose
switchToScene(LevelX.new())
end
end
end
This System deals with the player1 being hit or killed:
- runs once on init and every game loop (process)
- in init we add the camera and a sound to add some juice to the game
- there are 2 kind of hurt animations depending on the player1 health (washurt and wasbadlyhurt)
- when the player1 is hit, we add a camera shake and play some sound
- we update the HUD
- when the player1 is dead we play a death sequence and restart the current level
sNmes.lua
"sNmes.lua" in the "_S" folder. The code:
SNmes = Core.class()
local random = math.random
function SNmes:init(xtiny, xbump) -- tiny function
self.tiny = xtiny -- class ref so we can remove entities from tiny world
self.tiny.processingSystem(self) -- called once on init and every update
self.bworld = xbump
-- sfx
self.snd = Sound.new("audio/sfx/sfx_deathscream_human14.wav")
self.channel = self.snd:play(0, false, true)
end
function SNmes:filter(ent) -- tiny function
return ent.isnme
end
function SNmes:onAdd(ent) -- tiny function
ent.flip = math.random(100)
if ent.flip > 50 then ent.flip = 1 else ent.flip = -1 end
ent.currlives = ent.totallives
ent.currhealth = ent.totalhealth
ent.washurt = 0
ent.wasbadlyhurt = 0
ent.isdead = false
ent.curractiontimer = ent.actiontimer
ent.positionystart = 0
-- abilities
ent.abilities = {}
if ent.headhitboxattack1 then ent.abilities[#ent.abilities+1] = 1 end -- punch1
if ent.headhitboxattack2 then ent.abilities[#ent.abilities+1] = 2 end -- punch2
if ent.spinehitboxattack1 then ent.abilities[#ent.abilities+1] = 3 end -- kick1
if ent.spinehitboxattack2 then ent.abilities[#ent.abilities+1] = 4 end -- kick2
if ent.headhitboxjattack1 then ent.abilities[#ent.abilities+1] = 5 end -- jumppunch1
if ent.spinehitboxjattack1 then ent.abilities[#ent.abilities+1] = 6 end -- jumpkick1
-- for k, v in pairs(ent.abilities) do print(k, v) end
end
function SNmes:onRemove(ent) -- tiny function
self.bworld:remove(ent) -- remove collision box from cbump world here!
end
local resetanim = true
function SNmes:process(ent, dt) -- tiny function
-- hurt fx
if ent.washurt and ent.washurt > 0 and not (ent.wasbadlyhurt and ent.wasbadlyhurt > 0) and not ent.isdead then
ent.washurt -= 1
ent.animation.curranim = g_ANIM_HURT_R
if ent.washurt < ent.recovertimer*0.5 then ent.hitfx:setVisible(false) end
if ent.washurt <= 0 then ent.sprite:setColorTransform(1, 1, 1, 1) end
elseif ent.wasbadlyhurt and ent.wasbadlyhurt > 0 and not ent.isdead then
ent.wasbadlyhurt -= 1
ent.animation.curranim = g_ANIM_LOSE1_R
if ent.wasbadlyhurt < ent.recoverbadtimer*0.5 then
ent.hitfx:setVisible(false)
if resetanim then
resetanim = false
ent.animation.frame = 0
end
ent.animation.curranim = g_ANIM_STANDUP_R
end
if ent.wasbadlyhurt <= 0 then
ent.sprite:setColorTransform(1, 1, 1, 1)
resetanim = true
end
end
if ent.isdirty then -- hit
self.channel = self.snd:play()
if self.channel then self.channel:setVolume(g_sfxvolume*0.01) end
ent.hitfx:setVisible(true)
ent.hitfx:setPosition(ent.pos.x+ent.collbox.w/2+(ent.headhurtbox.x*ent.flip), ent.pos.y+ent.headhurtbox.y-ent.headhurtbox.h/2)
ent.spritelayer:addChild(ent.hitfx)
ent.currhealth -= ent.damage
ent.washurt = ent.recovertimer -- timer for a flash effect
-- ent.sprite:setColorTransform(0, 0, 2, 3) -- the flash effect (a bright red color)
ent.isdirty = false
if ent.currhealth <= 0 then
ent.wasbadlyhurt = ent.recoverbadtimer -- timer for actor to stand back up
ent.currlives -= 1
if ent.currlives > 0 then ent.currhealth = ent.totalhealth end
end
end
if ent.currlives <= 0 then -- deaded
-- stop all movements
ent.isleft = false
ent.isright = false
ent.isup = false
ent.isdown = false
-- play dead sequence
ent.isdirty = false
ent.washurt = ent.recovertimer
ent.wasbadlyhurt = ent.recoverbadtimer
-- blood
if not ent.isdead then
ent.hitfx:setVisible(true)
ent.hitfx:setColorTransform(3, 0, 0, random(1, 3)/10) -- blood stain
ent.hitfx:setPosition(ent.pos.x+ent.collbox.w/2, ent.pos.y)
ent.hitfx:setRotation(random(360))
ent.hitfx:setScale(random(5, 8)/10)
ent.bgfxlayer:addChild(ent.hitfx)
ent.isdead = true
end
ent.animation.curranim = g_ANIM_LOSE1_R
resetanim = false -- ??? XXX
ent.sprite:setColorTransform((-ent.pos.y<>ent.pos.y)/255, (-ent.pos.y<>ent.pos.y)/255, 0, 1)
ent.shadow.sprite:setVisible(false)
ent.pos -= vector(8*ent.flip, 8)
ent.sprite:setPosition(ent.pos)
ent.sprite:setScale(ent.sprite:getScale()+0.07)
if ent.pos.y < -256 then
self.tiny.tworld:removeEntity(ent) -- sprite is removed in SDrawable
self.tiny.numberofnmes -= 1
end
end
end
This System deals with the enemies being hit or killed:
- runs once on init and every game loop (process)
- in init we add a sound to add some juice to the game
- onAdd some explanation below
- when an enemy is hit, we play some sound
In the onAdd function it is worth noting that instead of creating all the variables for the enemies in their Entity code, I found it 'clever' to put them in the enemy System as they all share the same variables.
The other thing worth noting is an enemy ability. Depending on the attacks an Entity has, they are stored in an abilities table. In a artificial intelligence System we will add shortly, the code will iterate the abilities table and pick a random attack an Entity can perform.
sAI.lua
"sAI.lua" in the "_S" folder. The code:
SAI = Core.class()
local random = math.random
function SAI:init(xtiny, xplayer1) -- tiny function
xtiny.processingSystem(self) -- called once on init and every update
self.player1 = xplayer1
end
function SAI:filter(ent) -- tiny function
return ent.ai
end
function SAI:onAdd(ent) -- tiny function
end
function SAI:onRemove(ent) -- tiny function
end
local p1rangex = 192 -- 192 -- magik XXX
local p1hitrangex = 64 -- magik XXX
local p1rangey = 96 -- 96 -- magik XXX
local p1hitrangey = 16 -- magik XXX
local rndaction = 0 -- random punch/kick action
local p1rangetoofar = myappwidth -- disable system to save some CPU, magik XXX
function SAI:process(ent, dt) -- tiny function
if ent.isdead then return end
local function fun()
-- some flags
ent.doanimate = true -- to save some cpu
ent.readytohit = false
if (ent.pos.x > self.player1.pos.x + p1rangetoofar or ent.pos.x < self.player1.pos.x - p1rangetoofar) then
ent.doanimate = false
return
end
if (ent.pos.x > self.player1.pos.x + p1rangex or ent.pos.x < self.player1.pos.x - p1rangex) or
(ent.pos.y > self.player1.pos.y + p1rangey or ent.pos.y < self.player1.pos.y - p1rangey) then -- OUTSIDE ATTACK RANGE
-- idle
ent.isleft, ent.isright = false, false
ent.isup, ent.isdown = false, false
ent.body.currspeed = ent.body.speed
ent.body.currjumpspeed = ent.body.jumpspeed
else -- INSIDE ATTACK RANGE
-- x
if ent.pos.x > random(self.player1.pos.x+p1hitrangex, self.player1.pos.x+p1rangex) then
ent.isleft, ent.isright = true, false
ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX
elseif ent.pos.x < random(self.player1.pos.x-p1rangex, self.player1.pos.x-p1hitrangex) then
ent.isleft, ent.isright = false, true
ent.body.currspeed = ent.body.speed * random(10, 15)*0.1 -- magik XXX
end
-- y
if ent.pos.y > random(self.player1.pos.y, self.player1.pos.y+p1hitrangey) then
ent.isup, ent.isdown = true, false
ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX
ent.readytohit = true
elseif ent.pos.y < random(self.player1.pos.y-p1hitrangey, self.player1.pos.y) then
ent.isup, ent.isdown = false, true
ent.body.currjumpspeed = ent.body.jumpspeed * random(10, 64) -- magik XXX
ent.readytohit = true
end
-- nmes always face player1
if not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
if ent.pos.x > self.player1.pos.x then ent.flip = -1
else ent.flip = 1
end
end
end
-- ATTACK
if ent.readytohit then
ent.curractiontimer -= 1
if ent.curractiontimer < 0 then
ent.animation.frame = 0
rndaction = ent.abilities[random(#ent.abilities)] -- pick a random attack
if rndaction == 1 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.isactionpunch1 = true
elseif rndaction == 2 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.isactionpunch2 = true
elseif rndaction == 3 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.isactionkick1 = true
elseif rndaction == 4 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.isactionkick2 = true
elseif rndaction == 5 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.positionystart = ent.pos.y
ent.body.isonfloor = false
ent.body.isgoingup = true
ent.isactionjumppunch1 = true
ent.body.currspeed *= 1+random(4)*0.1 -- randomize speed, you choose to add it and the params
-- jump in the direction of the flip
if ent.flip == 1 then ent.isleft = false ent.isright = true
else ent.isleft = true ent.isright = false
end
elseif rndaction == 6 and not (ent.isactionjumppunch1 or ent.isactionjumpkick1) then
ent.positionystart = ent.pos.y
ent.body.isonfloor = false
ent.body.isgoingup = true
ent.isactionjumpkick1 = true
ent.body.currspeed *= 1+random(3)*0.1 -- randomize speed, you choose to add it and the params
-- jump in the direction of the flip
if ent.flip == 1 then ent.isleft = false ent.isright = true
else ent.isleft = true ent.isright = false
end
end
ent.curractiontimer = ent.actiontimer
end
end
Core.yield(1)
end
Core.asyncCall(fun) -- profiler seems to be faster without asyncCall (because of pairs traversing?)
end
This System controls all the entities with an artificial intelligence (ai) id. In this System an entity can be in an idle state, a move state or an attack state. Each states are applied relative to the distance between the Entity and the player1.
Next?
The time has come to tackle the systems. I will try to make it easy :-)
Prev.: Tuto tiny-ecs beatemup Part 8 Breakables
Next: Tuto tiny-ecs beatemup Part 10 XXX