Bewegung
Entities in Bewegung bringen

Was muss sich bewegen können
Es gibt nur ein paar Entities, die sich bewegen werden, das sind die Figuren, auch Actors genannt. Und es gibt Animationen, zum Beispiel vielleicht eine fliegende Taube oder ein anderes Tier.
Ich möchte also, dass eine Entity sich bewegen kann, indem sie eine ‘Move’-Klasse verwendet.
Wie denn?
Früher, als ich noch jung war haha 😉, habe ich solche Objekte bewegt, indem ich zum Beispiel die x- und y-Koordinaten mit einer bestimmten Geschwindigkeit erhöht oder verringert habe.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Wie du vielleicht schon gelesen hast, verwende ich eine Vector-Klasse.
Die Move-Klasse sieht jetzt so aus und nutzt Vektorberechnungen. Damit kann ich zum Beispiel ein paar Simulationen umsetzen, etwa wenn ein Spieler gegen den Wind laufen muss (etwas langsamer) oder einen Hügel hinauf oder hinunter.
---@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
In der Klasse oben steht auch ein Teil über die Richtung, in die die Figur läuft. Es wird ’nur’ vier Fälle geben, nämlich nach vorne, nach hinten, nach links und nach rechts.
Wenn der Spieler die Figur nach rechts laufen lassen will, möchte ich das sofort wissen. Der Grund ist, dass ich in dem Moment ein anderes Frameset (walk_right) anzeige und die Bewegung starte.
Das mache ich mit einem Kompass, weil ich ihn als allgemeine Hilfsfunktion in engine3 haben will.
---@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
Damit kann ich über die generische Compass-Klasse bestimmen, in welche Richtung die Figur laufen will.
Im Spiel anwenden
Im Spiel kann ich jetzt die generischen Klassen aus engine3 verwenden, und zwar so (ich habe etwas Code weggelassen).
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
Mit dem Basiscode oben ist jetzt Folgendes möglich:
- Eine Entity über den Bildschirm bewegen
- Die Geschwindigkeit pro Entity einstellen
- Die Richtung kennen, in die die Entity will
- Eine eigene Funktion ausführen, wenn die Position erreicht ist
- Erkennen, ob sich die Entity noch bewegt
- Die ‘Move’-Klasse erweitern und überall einsetzen