移动
让 entity 动起来

哪些东西需要能移动
只有少数几个 entity 会移动,那就是角色,也叫 actor。另外还有一些动画,比如也许一只飞翔的鸽子或其他动物。
所以我希望 entity 可以通过使用 ‘Move’ 类来移动。
怎么做?
以前我还年轻的时候,哈哈 😉,我会用这样的方法让这类对象移动:比如按一定速度增加或减少 x 和 y 坐标。
-- not 100% wrong but check vector math first
local speed = 1
if go left then
object.x += speed * dt
end
你之前可能已经读到过,我使用了一个 Vector 类。
现在的 Move 类是这样的,用的是向量计算。这样我就可以做一些模拟,比如玩家要逆风行走(稍微慢一点),或者上坡下坡。
---@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
指南针
上面的类里也有一部分是关于角色行走方向的。“只”会有四种情况:向前、向后、向左和向右。
如果玩家想让角色向右走,我希望马上知道。原因是那一刻我要显示另一个 Frameset(walk_right)并开始移动。
我是用指南针来实现的,因为我想把它作为 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
借助通用的 Compass 类,我就能判断角色想往哪个方向走。
在游戏中使用
现在我可以在游戏里这样使用 engine3 的通用类(省略了一些代码)。
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
有了上面的基础代码,现在可以:
- 让 entity 在屏幕上移动
- 为每个 entity 设置速度
- 知道 entity 想往哪个方向走
- 到达位置时执行自定义函数
- 检测 entity 是否还在移动
- 扩展 ‘Move’ 类并在任何地方使用