【游戏功能】
一、一次背对背的战斗
攻城战录像里最出圈的一幕:战士和怪物贴脸互砍,一个是背对怪挥刀,一个是背对人咆哮,俩人像约好了各自面对镜头。回放追查,朝向数据全对——玩家朝向写的是右,怪朝向写的是左,可玩家其实站在怪的右边。攻防双方各自朝着"绝对方向"站,而没有朝向"彼此"。战斗是两个人的关系,朝向必须是相对的:你看着我,我看着你,差一档都不行。
二、机制原理:朝向是对彼此的
正确做法是在交战建立的那一刻做朝向对表:攻击方把目标坐标代入自己的朝向计算,人物只有左右两个朝向档,目标在右侧用右档、在左侧用左档,转身成本极低且永远明确;受击方用六方向档位量化从自己指向攻击者的连线,取最近档。对表不是一次性的——走位会改变相对方位,双方每帧重算朝向,人物二选一切换,怪物六档里滑动,镜头里永远保持对视。攻击瞬间再校验一次朝向,不对就强转后出刀,宁可慢半帧也不能背对背出招。
mir8-duel
三、正确写法:相对朝向对表
local MONSTER_DIRS = { 0, 2, 3, 4, 5, 6 } -- 怪的六个可用方向档
function gameActor:FaceDir2(targetX) -- 人物: 两向
self._dir = targetX >= self._x and 2 or 6 -- 2右 6左
end
function gameActorMonster:FaceDir6(targetX, targetY)
local dx, dy = targetX - self._x, (targetY - 10) - (self._y - 20)
local deg = ((math.atan2(dy, dx) * 57.29577951 + 90) + 360) % 360
local best, bestDiff = 0, 999
for _, d in ipairs(MONSTER_DIRS) do
local diff = math.min(math.abs(deg - d * 45), 360 - math.abs(deg - d * 45))
if diff < bestDiff then bestDiff, best = diff, d end
end
self._dir = best
end
-- 交战双方每帧对表
function combatPair:UpdateFacing()
self._player:FaceDir2(self._monster:GetX())
self._monster:FaceDir6(self._player:GetX(), self._player:GetY())
end
-- 出刀前强转校验
function combatPair:OnAttack()
self:UpdateFacing()
self._player:PlayAction(global.MMO.ANIM_ATTACK)
end
四、四个必须钉死的细节
五、心法:关系数据放在关系层
朝向对表这个小事说的是个架构原则:凡是描述"两个对象之间关系"的数据——相对朝向、距离、仇恨、交易状态——都该放在关系对象上,而不是塞进任何一方。塞进一方,另一方就要反查;放在关系层,双向对表一次算清。背对背的战斗看着是表现 bug,根子是朝向这个关系数据被当成了单方属性。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【语法算法】 元方法 __close 的错误传递就这两行: to-be-closed 变量离开作用域时自动调用 __clos…
【语法算法】 goto 向后跳的控制就这一行: ::retry:: 定义标签, goto retry 跳回标签——向后跳的全…
【语法算法】 元方法 __close 的多资源清理就这两行: 两个 to-be-closed 变量——声明的逆序清理——先关…
【语法算法】 随机种子的确定性本质就这一行: 固定种子意味着随机数序列完全确定——同样的种子产生同样的序列——随机种子的全部…
【语法算法】 协程的状态机就这一行: status 返回四种字符串之一:"suspended"、"running"、"nor…
【语法算法】 xpcall 的错误对象就这两行: xpcall 和 pcall 的本质区别就是第二个参数——错误处理函数——…