Bevægelse
At få entities til at bevæge sig

Hvad skal kunne bevæge sig
Der er kun nogle få entities, der skal bevæge sig, nemlig personerne, også kaldet actors. Og der er også animationer, som måske en flyvende due eller et andet dyr.
Det, jeg vil, er altså, at en entity kan bevæge sig ved at bruge en ‘Move’-klasse.
Hvordan?
Dengang jeg var ung haha 😉, fik jeg den slags objekter til at bevæge sig ved fx at øge eller mindske x- og y-koordinaterne med en bestemt hastighed.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Som du måske har læst tidligere, bruger jeg en Vector-klasse.
Move-klassen ser nu sådan ud og bruger vektorberegninger. Dermed kan jeg fx lave nogle simuleringer, som når en spiller skal gå mod vinden (lidt langsommere) eller op eller ned ad 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
Kompas
I klassen ovenfor er der også et stykke om den retning, personen går i. Der bliver ‘kun’ fire situationer, nemlig fremad, bagud, til venstre og til højre.
Hvis spilleren vil have personen til at gå til højre, vil jeg vide det med det samme. Grunden er, at jeg så i det øjeblik viser et andet Frameset (walk_right) og starter bevægelsen.
Det gør jeg med et kompas, da jeg vil have det som en generel hjælpefunktion 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-klasse kan jeg dermed afgøre, i hvilken retning personen vil gå.
Brug i spillet
I spillet kan jeg nu bruge de generiske klasser fra engine3 på følgende måde (jeg har udeladt noget 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 ovenfor er det nu muligt at:
- Flytte en entity hen over skærmen
- Indstille hastigheden pr. entity
- Kende retningen, som entity vil i
- Udføre en custom funktion, når positionen er nået
- Registrere, om entity stadig bevæger sig
- Udvide ‘Move’-klassen og bruge den overalt