Pergerakan
Menggerakkan entity

Apa yang perlu boleh bergerak
Hanya beberapa entity yang akan bergerak, iaitu watak-watak, yang juga dipanggil actor. Ada juga animasi, contohnya mungkin burung merpati yang terbang atau haiwan lain.
Jadi yang saya mahu ialah sesuatu entity boleh bergerak dengan menggunakan kelas ‘Move’.
Bagaimana?
Dulu semasa saya masih muda haha 😉, saya menggerakkan objek sebegini contohnya dengan menambah atau mengurangkan koordinat x dan y pada kelajuan tertentu.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Seperti yang mungkin anda sudah baca sebelum ini, saya menggunakan kelas Vector.
Kelas Move kini kelihatan begini dan menggunakan pengiraan vektor. Dengan itu saya boleh menerapkan beberapa simulasi, contohnya apabila pemain perlu berjalan melawan angin (sedikit perlahan) atau naik dan turun bukit.
---@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
Dalam kelas di atas juga ada bahagian tentang arah watak berjalan. ‘Hanya’ ada empat keadaan, iaitu ke depan, ke belakang, ke kiri dan ke kanan.
Jika pemain mahu watak berjalan ke kanan, saya mahu tahu serta-merta. Sebabnya pada ketika itu saya memaparkan Frameset lain (walk_right) dan memulakan pergerakan.
Ini saya lakukan dengan kompas, kerana saya mahu menjadikannya fungsi pembantu umum dalam 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
Dengan kelas generik Compass, saya boleh menentukan ke arah mana watak mahu berjalan.
Mengaplikasikan dalam permainan
Dalam permainan kini saya boleh menggunakan kelas generik daripada engine3 dengan cara berikut (saya buang sedikit 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
Dengan kod asas di atas kini boleh:
- Menggerakkan entity di skrin
- Menetapkan kelajuan bagi setiap entity
- Mengetahui arah yang dituju entity
- Menjalankan fungsi tersuai apabila kedudukan dicapai
- Mengesan sama ada entity masih bergerak
- Memperluas kelas ‘Move’ dan menggunakannya di mana-mana