【游戏功能】
一、一具还在挨打的尸体
野外挂机两小时,客户端突然开始发烫。截帧一看:屏幕角落躺着七具怪物的尸体,全部保持着死亡动作的末帧,但每具尸体身上还挂着受击特效循环播放——死亡动作播完了,受击监听没有注销,路过的队友隔几秒顺手一刀,尸体就一直"疼"下去。没人报错,没人崩溃,资源就这么一点一点漏着。尸体不是没有表现,是有一套必须关干净的表现开关。
二、机制原理:终态动作与监听注销
怪物的动作全集是一台轮回机:站立是默认态,走动由巡逻驱动,攻击由仇恨触发,受击是过路态,死亡是唯一的终态。死亡动作各方向十帧,播到末帧就停在那里——终态不再推进帧、不再响应受击、不再参与排序渲染的更新,只留一张静态图。进入终态那一刻要做一揽子注销:撤掉血条、撤掉选中框、停掉所有循环特效、把逻辑实体从更新列表挪进尸体列表,等消散时间或清理半径再真正移除。
mir8-monster
三、正确写法:终态进入与一揽子注销
function gameActorMonster:EnterDeadState()
self._dead = true
self._keepLastFrame = true -- 死亡播完停末帧
self:StopAllLoops() -- 循环特效/光环/头顶牌全停
self:PlayAction(global.MMO.ANIM_DIE)
self:UnregisterHitListener() -- 尸体不再挨打
global.Facade:sendNotification(global.NoticeTable.ActorDead, { id = self:GetID() })
PerformWithDelayGlobal(function()
if tolua.isnull(self._root) then return end
self:FadeoutAndRemove() -- 消散: 渐隐后真正移除
end, global.MMO.CORPSE_KEEP_TIME)
end
function gameActorMonster:OnAttackHit(data)
if self._dead then return end -- 终态不响应
-- ...受击表现
end
四、四个必须钉死的细节
五、心法:每个状态都要有进有出有终态
站、走、攻、受、死五个动作,其实是一张状态图:四个活态共享一套规则,一个终态独占一套注销。写怪物表现系统的检查清单就三问——进了这个状态挂了什么?出这个状态要摘什么?有没有哪个状态永远出不去?第三问的答案如果有,那就是下一具还在挨打的尸体。
再补一个实战里的鉴别技巧:挂机帧率缓慢下降、重进地图立刻恢复,九成是终态没注销干净——活体的更新开销是恒定的,只有"死了还在跑逻辑"的尸体会让成本随时间累积。反过来,尸体保留时长也別设得吝啬,传奇的尸体是拾取锚点,玩家打完一波回头捡装备,尸体没了装备就悬空了。保留时长、清理半径、场景切换强制清尸,三个参数一起调,终态才能既干净又不误事。
全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →
【语法算法】 元方法 __close 的错误传递就这两行: to-be-closed 变量离开作用域时自动调用 __clos…
【语法算法】 goto 向后跳的控制就这一行: ::retry:: 定义标签, goto retry 跳回标签——向后跳的全…
【语法算法】 元方法 __close 的多资源清理就这两行: 两个 to-be-closed 变量——声明的逆序清理——先关…
【语法算法】 随机种子的确定性本质就这一行: 固定种子意味着随机数序列完全确定——同样的种子产生同样的序列——随机种子的全部…
【语法算法】 协程的状态机就这一行: status 返回四种字符串之一:"suspended"、"running"、"nor…
【语法算法】 xpcall 的错误对象就这两行: xpcall 和 pcall 的本质区别就是第二个参数——错误处理函数——…