【游戏功能】
一、先抛一个坑
追踪弹为什么不能写成"每帧把方向直接指向目标"?写一版试试:弹道像被磁铁吸住,走到哪都贴着目标的鼻尖转急弯,看着不像法术,像一截有脾气的睫毛。真正好看的追踪是一条咬尾的弧线——弹头有速度、有当前朝向,每帧只允许朝目标方向转一个小角度,转不过来的部分留着下一帧继续转。于是弹道有了"体重":目标横移,弹头画出一道优美的侧滑弧线再咬上去;转向力调大,弧线收紧成死咬。
二、机制原理:转向力上限
追踪弹的运动模型只有三条:速度大小恒定,方向由当前朝向角决定;每帧计算目标方向与当前朝向的夹角,把夹角钳制在"转向力乘帧时长"以内;朝向加上钳制后的夹角,按新朝向前进。转向力就是每秒最大转角,是追踪弹性格的唯一参数——两度每秒是懒洋洋的巡游弹,六度是标准法术,十二度是锁定死咬。命中判定沿用步长跨越:剩余距离小于本帧步长加判定半径即命中,高帧率下绝不穿过目标。被甩掉也是真实存在的结果:转向力不够又飞过了头,出屏销毁,玩家看到的是一次真实的 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 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【游戏功能】 无极真气的全部开关就这一行:给角色挂一层六秒的金气光环。但光环底下压着三条规矩——窗口期、三刀递增、过期作废。…
【游戏功能】 气功波为什么不掉血?战士一掌把狼怪推出去三步开外,狼怪头顶连个伤害数字都没有——新手第一反应是"技能没生效",…
【游戏功能】 法术共振报错刷了半屏: bolt target moved: anchor stale ——第一道雷落下到第二…
【游戏功能】 上周团里法师连放七个玄冰刺,怪的血没掉多少,指挥却在喊"完美控场"——直到有人回放录像才发现,狼怪绕着冰圈外圈…
【游戏功能】 彻地钉整套表现的钥匙就这一行:钉子不是打在狼怪身上,是从 地里的固定点 窜出来。今天顺着这行拆彻地钉——为什么…
【游戏功能】 道士的符为什么追着人咬?你明明看着它出手时冲左,狼怪往右一挪,符在半空拐了个弯还是贴上它的胸口——这不是弹道,…