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

挖一下和砍一刀是同一个函数:Mining采集循环生态

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

挖矿是传奇玩家的第二职业,不少老玩家砍怪的时间还没挖矿时间长。这套采集系统在引擎里的入口是 actor/gameActorStatePlayerMining.lua,四十来行,可能是全引擎最短的行为状态——但正是它的短,暴露了一个精妙的设计决定:挖一下和砍一刀,在引擎眼里是同一个动作。今天从这个"偷懒"讲起,把采集循环的完整生态讲透。

采集家族在引擎里不止挖矿一个成员:挖肉(对尸体)、种树、钓鱼这类"对着一个点反复劳动"的玩法,全都长在同一个模式上——短状态、外部循环、服务端判定。学会挖矿这一个,等于拿到采集类玩法的万能钥匙。二开想加"采药""锯木",照着 Mining 的骨架填自己的动画和判定就行,这也解释了为什么这个状态只有四十行:它已经当过四次模板了。

模板复用的尽头是"模板也该退休"。四年里第四次往这个骨架上挂玩法的时候,我们认真讨论过要不要把 Mining 泛化成 CollectBase——讨论的结果是不泛化:四个玩法的动画、判定频率、耐久规则各不相同,泛化出来的基类会塞满 if-else,比四份相似代码更难维护。模板复制三次可以,第四次开始警惕——抽象的时机比抽象的能力难拿捏,这个案例是活的教材。

一、证据:挖矿的 OnEnter 是抄攻击的

把 Mining 的 OnEnter 和攻击的 OnEnter 摆一起看:

lua
function gameActorStatePlayerMining:OnEnter(player)
    local animTime      = player:GetAttackStepTime() / player:GetAttackSpeed()
    player.mCurrentActT = animTime        -- 单次挖掘时长 = 攻击步长 ÷ 攻速

    -- reset some data
    player:SetAnimPrepare(0.0)
    player:DirtyAnimFlag()
end

GetAttackStepTime / GetAttackSpeed——一字不差,连注释都懒得改。挖掘的节奏参数完全借用攻击的:攻速装备影响挖矿速度,攻击前摇配置影响挖掘前摇。第一眼是偷懒,细想是天才:采掘类动作和攻击类动作的时间模型本来就同构——都是"一次挥动 + 命中判定 + 循环"。引擎不为挖矿单独发明一套参数,意味着:攻速 buff 白送挖掘提速(合情合理)、动画表里挖矿动作直接复用攻击的调参经验、玩家从战斗切采集零学习成本。

这个"借用"还省下了一类需求:玩家再也不用问"为什么挖矿不受攻速影响"。参数同源,行为自然一致。当两个系统的时间模型相同时,共享参数不是偷懒,是消除未来矛盾——这个判断力比多写四十行代码值钱。

顺带一句 Tick 里的熟人:0.00001 的归零阈值又出现了。挖矿、攻击、死亡三个状态全用它做收尾判定——浮点倒计时的最后一帧永远差那么一点,这个十万分之一就是全引擎公认的"算完了"。看到陌生的代码里冒出这个数字,基本可以断定作者读懂过这套状态机:0.00001 是这套引擎的方言。

OnExit 也和攻击完全同款:SetAnimPrepare(3.0) 归位预备帧、SetAnimAct(ANIM_READY) 切回待机。挖掘状态是攻击状态的孪生兄弟,连退场仪式都共用。

二、ChangeState 不设防:挖矿随时可以被带走

再看这个状态的 ChangeState:

lua
function gameActorStatePlayerMining:ChangeState(newState, player, isTerminate)
    player.mCurrentState = newState
    return true          -- 不设防:谁来都放行
end

对比死亡状态的"只放行 IDLE"、待机状态的"拒绝自我切换",挖矿的 ChangeState 裸奔到底:任何状态想接管,立刻放行。为什么敢这么松?因为挖矿是低价值可中断动作——挖到一半去打怪、去捡装备、去回城,全都合情合理,没有任何理由拦。状态机的设防等级和动作的价值成正比:死亡全设防(身份不可逆)、攻击半设防(等确认)、挖矿零设防(随时可弃)。

把全引擎状态按设防等级排一排,规律一目了然:死亡和换装是全设防(身份变更不可逆),走路攻击是半设防(要等服务器对账),挖矿摆摊是零设防(随时可弃的劳动)。你往引擎里加新状态时,第一道题就是给自己定位设防等级——定位错了,要么用户被锁死在动作里出不来,要么关键动作被随手打断。定位的标准就一个问题:这个动作被中途打断,代价是什么?代价越大,设防越高。

零设防带来一个必须处理的细节:被打断的那一挥已经挥到一半,mCurrentActT 里残留着半截时间。引擎的处理是 OnExit 里 mCurrentActT 不清也不管,下一次进任何状态时 OnEnter 都会重设——每个状态的时长自己管自己的,半截时间谁在乎谁清理,没人清理就自然失效。这种"数据寿命跟随状态"的纪律,省掉了全局重置的仪式。

mermaid
flowchart TD
A[点击矿脉] --> B[寻路走到矿前]
B --> C[OnEnter: ACTION_MINING]
C --> D[animTime = 攻击步长 ÷ 攻速]
D --> E[Tick 倒计时]
E --> F{归零 0.00001?}
F -->|是| G[handleActionCompleted = 一次采集判定]
G --> H{服务端: 出矿? 矿脉耐久?}
H -->|继续挖| D
H -->|矿枯竭| I[MINING 结束]
E -->|玩家任何新输入| J[ChangeState 全放行 → 干净离场]

三、一次挥动 = 一次判定:外层循环在谁手里

Mining 的 Tick 和攻击一样,mCurrentActT 归零就上报 handleActionCompleted。但挖矿和攻击有个本质区别:攻击的一刀是独立事件,挖矿的一挥是连续劳动的一格。挥完这一下接不接下一挥?这个决策不在状态机里,在外层的采集控制器:只要玩家还站在矿前、矿还有耐久、挂机开关还开着,控制器就再次发起 MINING——状态机负责"挥一下",控制器负责"挥多少下"。

"挥多少下"的决策权分布还能再拆细一层。站在矿前由移动系统确认、矿的耐久由服务端同步、挂机开关由 AutoProxy 查询——三个条件分属三个系统,采集控制器只是把它们拼成一句"继续"。这个拼装点选得极好:任何条件的变更都天然反映到下一挥(Buff 篇的 autoRemove、挂机篇的 CheckAutoPick 都在这条链上插过队),采集控制器自己不用订阅任何通知。把决策拼装放在各系统数据的交汇口,比到处订阅通知干净十倍,这是行为树那篇"决策与执行分层"的又一处落地。

服务端在这条链路里的角色也值得说清:每次 handleActionCompleted 对应一次采集判定请求,出不出矿、出几块、矿脉还剩多少耐久,全是服务端说了算。客户端的挥动只是节拍器——节奏在客户端,判定在服务端,和攻击的表现判定分离是一脉相承的。外挂把挥动频率拉到天上也没用,服务端按自己的节奏发矿。

出矿的随机性也全压在服务端:同一挥,有人出三块黑铁有人空手,概率表在服务端的掉落系统里。客户端只负责把结果演出来(飘字、矿石落地)。你要调爆率,去服务端的掉落表;你在客户端动歪脑筋,除了让自己看得开心什么也得不到。这条纪律说了十篇了,因为每个新接触引擎的人都会在这里试一次手气。

矿脉枯竭的处理也体现这套分工:客户端这边只是收到"耐久耗尽"的结果后停止循环,矿脉的刷新(几分钟一刷)完全是服务端的计时器。客户端永远不需要知道矿什么时候再长出来——下次走到矿前,能挖自然会挖。

下面这个演示把采集循环跑了起来:点"开始挖矿"看角色一挥接一挥,进度环是单挥的倒计时,出矿判定带品质概率;点"走开"体验零设防打断——挥到一半照样干净离场;拉攻速滑块,挖掘节奏跟着攻击参数走:

demo
skill-mining-loop-1003e

四、实战案例:一次"挖矿宏"的软治理

挖矿挂机是每个服的灰区:玩家用按键宏按住挖掘键,人不在电脑前矿照挖。硬封不近人情(挖矿本来就该挂机),放任又挤占矿脉资源。某服的治理方案打了个巧仗,全程没有封一个号。

方案是给矿脉加"疲劳":同一玩家对同一矿脉连续采集超过 60 挥,出矿率从 100% 阶梯衰减到 30%,换一个矿脉立即恢复。玩家侧的感受是"这矿挖疲了,换个矿",引导多点位游走采集;矿脉的刷新压力被自然分散到全图。配合服务端按"活跃采集人数"动态调矿脉总量,矿价一周内稳定 15% 波幅,比之前单点矿被宏霸占时的暴涨暴跌健康得多。实现成本:服务端一个衰减函数,客户端零改动(本来就只管挥)。

这个案例的启示是:对灰色自动化,生态手段优于封禁手段。挖矿宏挡不住,就设计一个"宏挂机和真人挂机收益趋同"的经济结构——真人本来也要挂机挖,宏玩家的优势被压缩到"不用守在电脑前",而这个优势本来就不该禁。规则设计者与工具使用者的长期博弈里,改收益曲线的人永远比封号的人体面。

那次治理还有个伴生发现:疲劳机制上线后,玩家自发形成了"矿脉轮耕"的路线文化,论坛出现了矿区路线图帖子,浏览量是该服当年第一。一个反宏的防腐设计,意外成了社区内容生产的燃料——生态手段的最高境界就是产生玩法之外的文化副产品。这条经验被我们写进了运营手册的灰色行为治理章节,标题就叫"别封,改曲线"。

追加一个收尾数字:疲劳机制运行一年后复盘,活跃挖矿玩家数不降反升 18%——防腐没有赶走挂机党,反而因为矿价稳定让挖矿收益变得可预期。这个结果印证了曲线哲学的完整逻辑链:封禁制造对立,曲线制造秩序,秩序带来留存。处理灰色行为时把这条链想通,很多"封不封"的争论自然就消解了。

五、常见疑问

问:挖矿时被怪攻击会怎样?
受击走 ChangeState 全放行,MINING 直接让位——挖矿人被怪拍死是传奇的经典死法之一,状态机不背锅。要保护采集体验,挂机设置里开"被攻击自动反击",反击和挖矿的优先级由行为树裁决,和手动输入同一套仲裁。

问:为什么挖掘借攻击参数,跑步却不借?
挖掘和攻击是同类动作(挥动+判定+循环),跑步是移动类动作(逐格+确认)。参数借用只在时间模型同构的系统之间进行,不是就近原则——最近的系统如果模型不同,借了才是事故。

问:矿脉耐久是谁在扣?
服务端。客户端看到的"耐久"只是服务端同步下来的展示值,本地扣的每一刀都会被服务端的对账纠正。展示可以乐观,判定必须权威——这条铁律从第一篇贯穿到这一篇,后面还会继续贯穿。

问:两个人同时挖同一个矿怎么算?
各自挥各自的,服务端按各自的判定请求独立发矿——传奇的矿不是"你挖一下我挖一下"的接力,是"每人一份、互不干扰"的平行世界,耐久是矿脉的公共计数先到先得。多人采集的公平性设计全在服务端的判定顺序里,客户端的挥动快慢不影响谁多挖一块。

👉 完整课程入口: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 技术组