Beweging
Het laten bewegen van entities
- Martin
- 4 min read

Wat moet kunnen bewegen
Er zijn maar een paar entities die gaan bewegen, dat zijn de characters of actors genoemd. En er zijn ook animaties zoals mischien een vliegende duif of ander dier.
Wat ik dus wil is dat een entity de mogelijkheid krijgt om te bewegen, door gebruik te maken van een ‘Move’ class.
Hoe dan?
Vroeger toen ik nog jong was haha π liet ik dergelijk objecten bewegen door bijv. de x en y coordinaten te verhogen/verlagen met een bepaalde snelheid.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Zoals je wellicht eerder hebt gelezen maak ik gebruik van een Vector class.
De Move class ziet er nu zo uit en maakt gebruik van Vector berekeningen. Ik kan hierdoor bijv. een aantal simulaties toepassen, zoals bijv. wanneer een speler tegen de wind in moet lopen (iets trager gaan), of een heuvel op of omlaag moet lopen.
---@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
Compas
In de bovenstaande class heb ik ook een stukje staan over de richting waar de character naar toe loopt. Er zullen ‘maar’ vier situaties zijn namelijk naar voren, achter, links en rechts.
Indien de speler de character naar rechts wilt laten lopen, wil ik dat direct weten. De rede hiervoor is dat ik dan op dat moment een andere Frameset laat zien (walk_right) en start te movement.
Dit doe ik doormiddel van een compas, aangezien ik het als een algemene helper functie in engine3 wil hebben.
---@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
Hiermee kan ik bepalen doormiddel van de generieke Compass class naar welke kant de character wil lopen.
Toepassen in het spel
In het spel kan ik nu vanuit de engine3 de generieke classes gebruiken. Op de volgende manier (ik heb wat code weggelaten).
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
Met de bovenstaande basis code is het nu mogelijk:
- Een entity over het scherm bewegen
- Snelheid is per entity in te stellen
- De richting waar de entity naar toe wil is bekend
- Wanneer de positie is bereikt kan ik een custom functie uitvoeren
- Detectie of de entity nog aan het bewegen is
- De ‘Move’ class is uitbreidbaar en overal inzetbaar


