Rörelse

Att få entities att röra sig

Användningen av en kompass

Vad ska kunna röra sig

Det är bara några få entities som ska röra sig, nämligen figurerna, även kallade actors. Och det finns också animationer, som kanske en flygande duva eller något annat djur.

Det jag vill är alltså att en entity ska kunna röra sig genom att använda en ‘Move’-klass.

Hur då?

Förr, när jag fortfarande var ung haha 😉, fick jag sådana objekt att röra sig genom att till exempel öka eller minska x- och y-koordinaterna med en viss hastighet.

-- not 100% wrong but check vector math first
local speed = 1
if go left then
  object.x += speed * dt
end

Som du kanske har läst tidigare använder jag en Vector-klass.

Move-klassen ser nu ut så här och använder vektorberäkningar. Därför kan jag till exempel göra några simuleringar, som när en spelare måste gå i motvind (lite långsammare) eller uppför eller nerför en backe.

---@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

Kompass

I klassen ovan finns också en del om vilken riktning figuren går åt. Det kommer ‘bara’ att finnas fyra fall, nämligen framåt, bakåt, vänster och höger.

Om spelaren vill få figuren att gå åt höger vill jag veta det direkt. Anledningen är att jag då visar ett annat Frameset (walk_right) och startar rörelsen.

Det gör jag med en kompass, eftersom jag vill ha den som en allmän hjälpfunktion i 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

Med den generiska Compass-klassen kan jag på så sätt avgöra åt vilket håll figuren vill gå.

Använda i spelet

I spelet kan jag nu använda de generiska klasserna från engine3 på följande sätt (jag har utelämnat en del kod).

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

Med baskoden ovan går det nu att:

  • Flytta en entity över skärmen
  • Ställa in hastigheten per entity
  • Veta åt vilket håll entityn vill
  • Köra en egen funktion när positionen är nådd
  • Upptäcka om entityn fortfarande rör sig
  • Bygga ut ‘Move’-klassen och använda den överallt