CHUAN2 DEV ENGINE
996 正版授权研发中心 · 360 授权合作教学中心 · 抖音传奇直播合作授权 · 快手推广运营商授权
OFFICIAL LICENSED ACADEMY 查验官方授权证书 →
// 威海旷世互娱教学基地 · 技术文章
进阶实战游戏功能996引擎

死亡的十八帧,尸体的三十秒:怪物死亡状态全链

2026-10-03 09:53 作者:996 技术组 996引擎Lua教程传奇脚本进阶实战游戏功能996引擎

怪物倒下的那两秒钟,是全套引擎状态机里锁得最死的一段:不能被打断、不能被移动、连别的状态想进来都要过身份检查。这段逻辑在 actor/gameActorStateMonsterDeath.lua,五十行不到,却回答了一个大问题——死亡到底是一个动作,还是一种身份。今天把死亡与尸体这条链讲完,顺带把挖肉、复活这些沾边的事一次说清。

死亡在状态族里的独特地位,得先从它的"单行道"属性说起。别的状态都有来回:待机可以切走路、走路可以切攻击、攻击可以被打回待机,唯独死亡是单程票——引擎的整条状态族里,能从死亡走出去的路只有一条(复活回待机)。这种拓扑结构上的特殊性,决定了它的守门逻辑必须比任何状态都严。读这个文件之前先记住这一点,你会发现每一行代码都在为"单行道"服务。

再补一个视角:死亡是全引擎"消费者最多"的状态。一次怪物死亡,要通知障碍系统清路(怪物那具尸体不再挡路)、Buff 管理清毒、HUD 摘血条、任务系统记进度、活动系统计数、掉落系统生成包裹——六个系统排队认领。死亡状态本身五十行,但围着它转的消费者有六个,状态的复杂度不在自己身上,在认领它的系统数量上。这也是为什么死亡的守门要最严:它每切一次,六个系统都要跑一遍,乱切就是六倍的乱。

一、死亡的入场:又是动画说了算

死亡状态 OnEnter 的写法你已经在攻击状态见过同款:

lua
function gameActorStateMonsterDeath:OnEnter(monster)
    -- 死亡时长同样问动画管理器要:这套倒地动画一共多久
    monster.mCurrentActT = global.FrameAnimManager:GetAnimTotalDuration(
        monster:GetAnimationID(), global.MMO.SFANIM_TYPE_MONSTER, global.MMO.ANIM_DEATH)
    monster:SetIsArrived(false)
    monster.mActionHandler:handleActionBegin(monster, global.MMO.ACTION_DEATH, monster.mOrient, 0)
end

倒地动画播多久,死亡状态就锁多久。OnAnimLoadCompleted 里也有和攻击同款的补时差逻辑(总时长减 elapsed),动画迟到不扣戏。你该问的是:为什么死亡不用攻速除一下?答案冷幽默但合理——没有哪个策划需要"死亡加速",玩家买攻速装备是为了砍得快,不是为了死得快(自己的怪死得快倒是很乐意)。动画时长即死亡时长,一次除法都省了。

死亡动画的帧数也顺带交个底:传奇系怪物的死亡动画普遍是 4 到 6 帧,加过渡帧撑死 8 帧,按每帧 100 毫秒播,倒地全程不到一秒——这就是标题里"十八帧"的夸张来源(把过渡、抽搐、定帧全算上撑到十来帧)。帧数越少的动画越要在插值上找补:引擎对死亡动画不做帧间插值,一帧一跳反而有"断气"的顿挫感,这属于歪打正着的手感,别好心去平滑它。

OnAnimLoadCompleted 的开头那句判断倒是值得再敲一遍黑板:if animAct ~= global.MMO.ANIM_DEATH then return false end。加载完成回调是全局广播式的,任何动画加载完成都会敲这扇门,门内第一件事是核对"你是我家的事吗"。回调满天飞的系统里,入口先验身份是防串门的铁则,这条在血条班车、动画缓存里全见过同款。

二、ChangeState:死亡的守门逻辑

死亡状态最有性格的是 ChangeState:

lua
function gameActorStateMonsterDeath:ChangeState(newState, monster, isTerminate)
    if not isTerminate then
        -- 非强制切换:只有"复活回待机"这一条路放行
        if newState:GetStateID() ~= global.MMO.ACTION_IDLE then
            return false
        end
    end
    monster.mCurrentState = newState
    return true
end

死了的怪,谁喊都不动——除非来的是 IDLE(复活),或者调用方带了 isTerminate 强制牌(切图销毁、GM 挪怪这类系统级操作)。这个守门逻辑一句话概括:死亡期间拒收一切游戏内请求,只认系统级指令和复活。为什么这么严?想想没有这把锁的场景:死亡动画播到一半,AI 发了个"继续巡逻"的走路段,尸体爬起来走两步再倒下——玩家管这叫"诈尸 bug",能把客服电话打爆。

Tick 里的收尾和攻击状态共用一个模板:倒计时走到 0.00001 阈值以下,mIsArrived 置真,handleActionCompleted 上报完成。死亡的上报和攻击的上报走的是同一个动作处理器,消费者按 GetStateID 分流——处理器是同一个邮筒,信封上的收件人不同。这种"一套管道、多种信件"的设计让动作完成的上报链路只有一条,排查任何"动作完成没触发"的问题都只查一个地方,省心程度五颗星。

对比玩家死亡状态(gameActorStatePlayerDie)你还会发现怪物的死亡连"被补刀"都不允许:血量已经是零,服务端不会再发攻击响应,客户端这边的锁只是把最后一点缝也焊死。多层防御看着冗余,服务器和客户端各把各的门,二十年零诈尸。

mermaid
flowchart TD
A[血量归零] --> B[OnEnter: ACTION_DEATH 上锁]
B --> C[倒地动画时长 = 锁定时长]
C --> D[Tick 倒计时 0.00001 阈值]
D --> E{中途有人请求换状态?}
E -->|是 + 非IDLE + 非强制| F[拒绝: return false]
E -->|是 + IDLE| G[放行: 复活]
D --> H[播完 handleActionCompleted]
H --> I[尸体阶段: zOrder 垫底 DieAddZorder]
I --> J[挖肉/拾取窗口开放]
J --> K[超时: 出管理器/复活]

三、尸体的两件事:垫底与留窗

死亡动画播完,怪物进入"尸体阶段"——引擎没有为它单开状态,而是两个动作收尾:handleActionCompleted 上报完成,setPosition 里的 zOrder 分支把尸体垫到活人脚底(怪物那篇的 DieAddZorder 就是干这个的)。垫底之后,尸体安静地躺在场景里,成为一块"可交互的地物"。

可交互指的就是挖肉。道士挖肉、战士摸尸、任务采集,全发生在尸体停留窗口内。窗口多长?不同版本配置不同,常见的在 15 到 30 秒之间——这个数字是采集玩法和性能的平衡点:留短了玩家赶不到(怪死在远处跑过去要时间),留长了同屏尸体堆积。尸体是唯一带 DieAddZorder 的实体,也是唯一在死亡后还保留可见性的实体,它占的每一份内存都是"给玩家捡东西"的预算,超时清退天经地义。

尸体的渲染成本也值得单独交底:死亡动画播完后,实体定格在最后一帧不再动,每帧的渲染就是一张静贴图——比活怪便宜了一个数量级。真正贵的是尸体身上挂着的"软资产":血条回调、Buff 登记、掉落包裹还没捡。这些不随死亡动画消失,全靠死亡通知的消费者自觉清理。后面实战案例里那场"尸体堆积"事故,崩的就是这批软资产。

挖肉实现上的关键约束:只有 CORPSE 阶段开放采集,DEATH 阶段不放行。刀还没落稳就去挖,动画会抽帧;这个判定做在采集入口而不是死亡状态里,两个系统各管各的门。还有一个容易忽略的体验细节:多具尸体叠在一起时,点击挖肉的命中判定要按 zOrder 从上往下找——垫底的尸体让位给新尸体,叠放的层级和渲染一致,不然玩家对着最上面那具尸体点鼠标,挖到的却是底下那具,这种"点到谁的身上却摸到别人"的错位,是采集类玩法投诉的重灾区。下面演示里你可以在死亡动画播到一半时点"挖肉",会看到活体不能挖的提示——这就是那道门的活演示:

demo
fx-monster-die-1003e

四、实战案例:一次"尸体堆积"的性能治理

某服活动"守塔刷怪潮",一分钟刷 80 只怪,玩家杀得快,尸体堆积同屏 200+。帧率没崩(尸体是静止帧,渲染便宜),内存涨了 300 兆——每具尸体挂着完整的 HUD 引用和 Buff 登记没释放。治理三步,数字都在。

第一刀,把尸体停留从 30 秒砍到 12 秒。实测玩家从击杀点到走到尸体前平均 6.8 秒,12 秒覆盖 95% 的采集需求,砍掉的一半天数里大部分尸体根本没人看。第二刀,尸体进入 CORPSE 时主动摘除 HUD(血条没了,名字保留)——每具尸体省一个定时刷新回调,两百具就是两百个。第三刀,ActorMonsterDie 通知的消费者里,BuffManager 做了批量销登记(死亡怪身上带毒的统一清),避免"死怪身上的毒还在 Tick"的空转。三刀之后同屏两百具尸体的额外内存占用从 300 兆降到 90 兆。

这单的心法:尸体是"半退场"状态,数据要退一半——渲染留个影子,逻辑该撤的撤。引擎的状态机管不到的部分(HUD、Buff、采集),全靠死亡通知的消费者自觉清理,这就是通知机制篇讲的"事实广播、各方认领"在死亡场景的考核:每个认领了 ActorMonsterDie 的系统,都要回答"我挂在这具尸体上的东西怎么办"。

那次治理还有个隐藏收获:守塔活动的怪物刷新器改成了"队列式投放"——上一批尸体没清完就压后下一批的刷新时间,200 兆的内存红线再没碰过。玩法侧让一步(怪刷得稍慢),技术侧活一片(内存稳如老狗),这种"策划改一个数字救程序员一晚上"的协作,比任何优化技巧都值得记录。性能问题的最优解经常不在技术侧,前提是技术侧能把"哪个数字该动"翻译成策划听得懂的话。

翻译的方法也交代一下,别只会说"内存爆了"。给策划看的报告就三行:现在同屏尸体最高 214 具;每具尸体停留 30 秒;如果把停留压到 12 秒,玩家捡东西完全不受影响(95 分位的捡拾用时是 6.8 秒),服务器能省一半尸体开销。全程没有"引用计数""软资产"这类黑话——跨职能沟通的质量,取决于技术侧做翻译的诚意。这份三行报告,比十页优化方案都管用。

五、常见疑问

问:死亡动画播到一半被 isTerminate 强切,尸体去哪了?
切图销毁场景下,怪物连同尸体一起出管理器,不存在残留;GM 挪怪场景下挪的是活体状态。强切牌只在系统级代码里发,业务代码永远拿不到——牌的发放权锁死在引擎层。

问:复活的怪是原对象还是新建的?
原对象。handle_Revive 类消息走 ChangeState 回 IDLE,动画缓存、ID 池里的登记全部原样复用——这也是为什么死亡状态只放行 IDLE:复用一条路,别让半死不活的对象重走出生流程。对象重生走"状态复位"而不是"销毁重建",是这套引擎省资源的暗功夫。

问:玩家死亡和怪物死亡为什么是两个类?
玩家死了要弹 回城按钮、掉装备判定、经验保护一堆交互,怪物死了要开挖肉窗口、尸体计时——两者的"死后生活"完全不同,共用一个死亡状态只会互相拖累。状态按"死后要干什么"分类,不按"死了"这个字面分类,这个分法在读引擎状态族时同样适用。

问:尸体上挖肉挖出来的东西归谁?
服务端按伤害归属和拾取规则结算,客户端尸体只是个交互入口。多队抢一具尸体时,客户端显示的可挖状态可能和服务端判定有短暂不一致——以服务端返回为准,客户端别做"这尸体是我的所以不让别人挖"的本地判断,规则性判断永远在服务端,这条纪律从 Buff 顶替讲到挖肉,从没变过。

👉 完整课程入口:996 全套课程体系(千余节课录) | 想跟浮生梦老师系统学的,看 LUA 高并发商业架构路径。

作者履历与出处

本文由 996 技术组基于 996 引擎官方知识库与浮生梦老师课程体系整理。团队长期从事传奇类引擎 Lua 后端逻辑、客户端界面与商业版本交付,内容以官方知识库与真实项目为出处,按版本持续修订。

← 返回文章地图返回研学路径

幂尔框架 · 实战干货 · 接口调用

LATEST ARTICLES

全站技术干货持续更新:996 引擎 / Lua 实战帖,语法、参数与示例一篇讲透。进入文章地图 · 查看全部 →

进阶实战游戏功能

一把钥匙开全引擎的配置门:MetaValue元变量系统

全服喇叭喊话"恭喜 &<PLAYER_NAME & 获得 &<ITEM_NAME/2001 &",这条公告里的两个占位符是怎…

2026-10-03 11:55 996 技术组
进阶实战游戏功能

ui.按钮名就能拿到控件:LuaExtend元表的八十行魔法

读引擎 UI 代码时你一定会撞见这样的写法: self._quickUI.btnClose 、 SL:GetValue("x…

2026-10-03 11:30 996 技术组
进阶实战游戏功能

断线是谁先发现的:心跳三兄弟与切后台补发

玩家手机锁屏再解锁,游戏还在原地;地铁过隧道断网半分钟,回连后接着玩——这两件"理所当然"背后是 logic/gameWor…

2026-10-03 11:24 996 技术组
进阶实战游戏功能

全引擎的下一拍在这里敲:主循环Update解剖

每个引擎都有一个"心脏":每帧跳动一次、按固定顺序叫醒所有系统的主循环。996 引擎的心脏在 logic/gameWorld…

2026-10-03 11:17 996 技术组
进阶实战游戏功能

元宝数字为什么全屏同步:CostItemCell消耗格台账

玩家买一瓶药,背包角标的元宝、商店界面的元宝、充值面板的元宝三处数字同时跳——这个瞬间几乎没有玩家会注意到,但做客户端的人都…

2026-10-03 11:10 996 技术组
进阶实战游戏功能

每个东西都有专属座位:sceneGraph功能节点树

新接手引擎渲染层的人,打开场景会看到一锅粥:地图、角色、特效、血条、UI 全糊在一起。996 引擎的答案是把场景拆成一张"座…

2026-10-03 11:02 996 技术组