Bevegelse

Å få entities til å bevege seg

Bruken av et kompass

Hva må kunne bevege seg

Det er bare noen få entities som skal bevege seg, nemlig figurene, også kalt actors. Og det finnes også animasjoner, som kanskje en flygende due eller et annet dyr.

Det jeg vil, er altså at en entity skal kunne bevege seg ved å bruke en ‘Move’-klasse.

Hvordan da?

Før i tiden, da jeg fortsatt var ung haha 😉, fikk jeg slike objekter til å bevege seg ved for eksempel å øke eller senke x- og y-koordinatene med en bestemt hastighet.

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

Som du kanskje har lest tidligere, bruker jeg en Vector-klasse.

Move-klassen ser nå slik ut og bruker vektorberegninger. Dermed kan jeg for eksempel lage noen simuleringer, som når en spiller må gå mot vinden (litt saktere) eller opp eller ned en bakke.

---@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 over står det også litt om retningen figuren går i. Det blir ‘bare’ fire situasjoner, nemlig forover, bakover, venstre og høyre.

Hvis spilleren vil få figuren til å gå til høyre, vil jeg vite det med en gang. Grunnen er at jeg da viser et annet Frameset (walk_right) og starter bevegelsen.

Det gjør jeg med et kompass, siden jeg vil ha det som en generell hjelpefunksjon 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 generiske Compass-klassen kan jeg dermed avgjøre hvilken vei figuren vil gå.

Bruke det i spillet

I spillet kan jeg nå bruke de generiske klassene fra engine3 på denne måten (jeg har utelatt litt kode).

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 basiskoden over er det nå mulig å:

  • Flytte en entity over skjermen
  • Stille inn hastigheten per entity
  • Vite hvilken retning entityen vil
  • Kjøre en egen funksjon når posisjonen er nådd
  • Oppdage om entityen fortsatt beveger seg
  • Utvide ‘Move’-klassen og bruke den overalt