Mouvement
Faire bouger les entities

Ce qui doit pouvoir bouger
Seules quelques entities vont bouger : ce sont les personnages, aussi appelés actors. Il y a aussi des animations, comme peut-être un pigeon qui vole ou un autre animal.
Ce que je veux, c’est donc qu’une entity puisse se déplacer en utilisant une classe ‘Move’.
Comment ?
Autrefois, quand j’étais encore jeune haha 😉, je faisais bouger ce genre d’objets par exemple en augmentant ou en diminuant les coordonnées x et y selon une certaine vitesse.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Comme vous l’avez peut-être déjà lu, j’utilise une classe Vector.
La classe Move ressemble maintenant à ceci et utilise des calculs vectoriels. Je peux ainsi appliquer quelques simulations, par exemple quand un joueur doit marcher contre le vent (un peu plus lentement), ou monter ou descendre une colline.
---@enum Move.UP Movement up direction.
---@enum Move.DOWN Movement down direction.
---@enum Move.LEFT Movement left direction.
---@enum Move.RIGHT Movement right direction.
Move.IDLE = nil
Move.UP = 1
Move.DOWN = 2
Move.LEFT = 3
Move.RIGHT = 4
...
---@param i integer
function Move:setSpeed(i)
self.speed = i
end
function Move:getSpeed()
return self.speed
end
--- Stop traveling to destination.
---@see function.afterStop
---@param ... any Multiple arguments passed into the callback
function Move:stop(...)
self.vDestination = nil
self:callback(self.callbackAfterStop, self.pObject, self, ...)
end
--- Move point to destination
---@param dt number DeltaTime
---@return integer? x,integer? y,boolean? stopped
function Move:updateToDestination(dt)
if self.vDestination == nil then
return
end
local dist = self.vPosition:dist(self.vDestination)
local step = self.speed * dt
local stopped = false
if dist <= step then
-- at destination
-- This will stop the movement and set the position x,y
self.vPosition.x = self.vDestination.x
self.vPosition.y = self.vDestination.y
self:stop()
stopped = true
else
-- normalize vector (between the target and sprite)
local nx = (self.vDestination.x - self.vPosition.x) / dist
local ny = (self.vDestination.y - self.vPosition.y) / dist
self.vPosition.x = self.vPosition.x + nx * step
self.vPosition.y = self.vPosition.y + ny * step
end
return self.vPosition.x, self.vPosition.y, stopped
end
---@param x integer
---@param y integer
function Move:setDestinationTarget(x, y)
self.vDestination = Vector(x, y)
end
function Move:isMoving()
return self.vDestination and (self.vPosition.x ~= self.vDestination.x or self.vPosition.y ~= self.vDestination.y)
end
Boussole
Dans la classe ci-dessus, il y a aussi une partie sur la direction dans laquelle marche le personnage. Il n’y aura ‘que’ quatre situations : vers l’avant, vers l’arrière, à gauche et à droite.
Si le joueur veut faire marcher le personnage vers la droite, je veux le savoir tout de suite. La raison, c’est qu’à ce moment-là j’affiche un autre Frameset (walk_right) et je lance le mouvement.
Je le fais avec une boussole, car je veux l’avoir comme fonction utilitaire générale dans engine3.
---@param directions table
function Move:getMovementDirection(directions)
local octant = Compass.getOctant(self.vPosition, self.vDestination, true)
-- default movement directions.
-- It will use for up and down 1 extra octant.
if directions == nil then
directions = {
{Compass.N, Compass.NNE, Compass.NW, Compass.NNW, Compass.NE}, -- up
{Compass.S, Compass.SW, Compass.SE, Compass.SSW, Compass.SSE}, -- down
{Compass.W, Compass.WSW, Compass.WNW}, -- left
{Compass.E, Compass.ESE, Compass.ENE} -- right
}
end
for moveConstant, direction in pairs(directions) do
for _, octantConstant in pairs(direction) do
if octantConstant == octant then
return moveConstant
end
end
end
return Move.IDLE
end
Grâce à la classe générique Compass, je peux ainsi déterminer de quel côté le personnage veut marcher.
Utilisation dans le jeu
Dans le jeu, je peux maintenant utiliser les classes génériques d’engine3, de la façon suivante (j’ai retiré un peu de code).
local Move = require "lib.engine3.src.framework.move.move"
local Compass = require "lib.engine3.src.framework.helper.compass"
function Entity:new(data)
...
self.frameset[Move.LEFT] = self.frameset[Move.RIGHT]
self.movable = Move(self.activeFrameset:getX(), self.activeFrameset:getY(), self)
self.movable.callbackAfterStop["default"] = function(s, movable)
-- Set the latest x,y postition to make it more correct.
s:setXY(movable.vPosition.x, movable.vPosition.y)
s.lastMove = s.move
s:show() -- pause set the frameset to the idle frame
end
...
end
function Entity:walk(targetX, targetY)
...
self.movable:setDestinationTarget(targetX, targetY)
local oldMove = self.move
local newMove =
self.movable:getMovementDirection(
{
{Compass.N, Compass.NE, Compass.NW, Compass.NNW, Compass.NNE}, -- up
{Compass.S, Compass.SW, Compass.SE, Compass.SSW, Compass.SSE}, -- down
{Compass.W, Compass.WSW, Compass.WNW}, -- left
{Compass.E, Compass.ESE, Compass.ENE} -- right
}
...
if oldMove ~= newMove then
self.activeFrameset:stop()
else
setFrameKey = self.activeFrameset:getFrameKey()
end
self.activeFrameset = self.frameset[self.move]
...
end
---@private
---@param self Animation
---@param frameset Frameset
---@param update boolean
---@param currentFrame Frame
---@param dt number
Entity.framesetUpdate = function(self, frameset, update, currentFrame, dt)
-- local oldX = self.movable.vPosition.x
-- local oldY = self.movable.vPosition.y
local x, y = self.movable:updateToDestination(dt)
if x ~= nil and y ~= nil then
-- Update each frameset, so no glitch is visible
self:setXY(x, y)
end
end
Avec le code de base ci-dessus, il est maintenant possible de :
- Déplacer une entity à l’écran
- Régler la vitesse pour chaque entity
- Connaître la direction dans laquelle l’entity veut aller
- Exécuter une fonction personnalisée quand la position est atteinte
- Détecter si l’entity est encore en mouvement
- Étendre la classe ‘Move’ et l’utiliser partout