【游戏功能】
一、先抛一个坑
追踪弹为什么不能写成"每帧把方向直接指向目标"?写一版试试:弹道像被磁铁吸住,走到哪都贴着目标的鼻尖转急弯,看着不像法术,像一截有脾气的睫毛。真正好看的追踪是一条咬尾的弧线——弹头有速度、有当前朝向,每帧只允许朝目标方向转一个小角度,转不过来的部分留着下一帧继续转。于是弹道有了"体重":目标横移,弹头画出一道优美的侧滑弧线再咬上去;转向力调大,弧线收紧成死咬。
二、机制原理:转向力上限
追踪弹的运动模型只有三条:速度大小恒定,方向由当前朝向角决定;每帧计算目标方向与当前朝向的夹角,把夹角钳制在"转向力乘帧时长"以内;朝向加上钳制后的夹角,按新朝向前进。转向力就是每秒最大转角,是追踪弹性格的唯一参数——两度每秒是懒洋洋的巡游弹,六度是标准法术,十二度是锁定死咬。命中判定沿用步长跨越:剩余距离小于本帧步长加判定半径即命中,高帧率下绝不穿过目标。被甩掉也是真实存在的结果:转向力不够又飞过了头,出屏销毁,玩家看到的是一次真实的 miss。
fx-homing
三、正确写法:转向力模型(引擎侧)
function homingMissile:Update(dt)
local tx = self._target:GetX() - self._x
local ty = (self._target:GetY() - 30) - self._y
local want = math.atan2(ty, tx) -- 目标方向
local cur = math.atan2(self._vy, self._vx) -- 当前朝向
local diff = want - cur
while diff > math.pi do diff = diff - 2 * math.pi end
while diff < -math.pi do diff = diff + 2 * math.pi end
local maxTurn = self._turnRate * dt -- 转向力钳制
diff = math.max(-maxTurn, math.min(maxTurn, diff))
local na = cur + diff
self._vx = math.cos(na) * self._speed
self._vy = math.sin(na) * self._speed
self._x = self._x + self._vx * dt
self._y = self._y + self._vy * dt
local dist = math.sqrt(tx * tx + ty * ty)
if dist <= self._speed * dt + 26 then -- 步长跨越命中
self:Explode()
return false
end
return true
end
四、四个必须钉死的细节
五、心法:给运动一点不完美
追踪弹的弧线之所以好看,是因为它"转不过来";急转弯的磁铁弹之所以难看,是因为它"太完美"。表现系统的通病就是把运动做得过分精确,直线就是直线、指向就是指向,精确得没有生命。给一切运动加一点转向力上限、一点启动加速、一点停下惯性,画面立刻就有了重量和脾气。完美属于逻辑,不完美属于生命。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 一、先抛一个坑 同样是"放一片技能特效",为什么有的游戏炸二十个火球丝般顺滑,有的游戏炸五个就掉帧?直觉答案"…
【游戏功能】 一、一个职业选手的操作拆解 看高手第一视角录像,最费解的操作是:他打一下、退两步、再打一下,全程像在跳一支排练…
【游戏功能】 一、一个被浪费的冰冻 组里新人做了个冰法连招:冰冻命中后立刻接大火球,理论伤害翻倍。试玩却怪怪的——冰冻刚套上…
【游戏功能】 一、一次吓到自己的召唤 召唤兽系统的内测录像里有个名场面:道士按下召唤,屏幕上"啪"地多出一只金毛巨狼,没有地…
【游戏功能】 一、先抛一个坑 领域类技能最怕做成"一次性特效放完拉倒":冰环扩一圈、冻一下、结束——站在领域里的怪第二秒解冻…
【游戏功能】 一、一次只有挨打没有还手的守势 守势类技能首版的哀嚎最一致:"金环弹开了围攻,然后呢?"——然后没有然后。格挡…