Mouvement

Faire bouger les entities

L’utilisation d’une boussole

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