Gerakan
Membuat entity bergerak

Apa yang harus bisa bergerak
Hanya ada beberapa entity yang akan bergerak, yaitu para karakter, yang juga disebut actor. Dan ada juga animasi, misalnya burung merpati yang terbang atau hewan lain.
Jadi yang saya inginkan adalah sebuah entity bisa bergerak dengan memakai class ‘Move’.
Caranya?
Dulu waktu saya masih muda haha 😉, saya menggerakkan objek seperti itu misalnya dengan menaikkan atau menurunkan koordinat x dan y dengan kecepatan tertentu.
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
Seperti yang mungkin sudah kamu baca sebelumnya, saya memakai class Vector.
Class Move sekarang seperti ini dan memakai perhitungan vektor. Dengan begitu saya bisa menerapkan beberapa simulasi, misalnya saat pemain harus berjalan melawan angin (sedikit lebih lambat), 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
Di class di atas juga ada bagian tentang arah ke mana karakter berjalan. Hanya akan ada ’empat’ situasi, yaitu ke depan, ke belakang, ke kiri, dan ke kanan.
Kalau pemain ingin karakter berjalan ke kanan, saya ingin langsung tahu. Alasannya, saat itu juga saya menampilkan Frameset yang lain (walk_right) dan memulai gerakannya.
Ini saya lakukan dengan kompas, karena saya ingin menjadikannya fungsi bantu umum di 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 class generik Compass, saya bisa menentukan ke arah mana karakter ingin berjalan.
Menerapkannya di game
Di dalam game sekarang saya bisa memakai class generik dari engine3 dengan cara berikut (sebagian kode saya hilangkan).
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 kode dasar di atas sekarang bisa:
- Menggerakkan entity di layar
- Mengatur kecepatan per entity
- Mengetahui arah yang dituju entity
- Menjalankan fungsi kustom saat posisi sudah tercapai
- Mendeteksi apakah entity masih bergerak
- Memperluas class ‘Move’ dan memakainya di mana saja