游戏系统与数值设计:从状态机到战斗循环的实战框架

发布时间:2026/10/9 7:36:35
游戏系统与数值设计:从状态机到战斗循环的实战框架
这一章聊的东西放在游戏项目里往往最容易被低估。很多团队早期靠玩法创意跑得很快到后期开始频繁返工问题通常不出在美术或程序而是系统逻辑和数值支撑没有同步跟上。逻辑撑不住游戏结构数值撑不住长线体验两个问题加在一起就会变成修不完的Bug和调不平的战斗。我是做系统与数值策划出身这类问题我几乎每个项目都会遇到一遍。这里想把一套我自己反复使用的系统构建框架和数值调优方法拆开讲清楚标题里的“第3章”是系列内容中的一节但它独立拿出来照样能解决从原型验证到正式运营的大部分系统设计问题。我默认看这篇文章的读者是游戏策划、独立开发者、或者是刚转行想做系统设计的同学。内容不求面面俱到只求讲清楚系统该用什么思路搭数值该怎么一层层填进去。1. 系统思维从玩法到系统的第一层拆解1.1 游戏系统不是功能列表很多策划一提到系统脑子里浮现的是“背包”“商店”“任务”“战斗”这些功能界面然后开始画UI流程图、列按钮、写交互文档。这套做法能应付简单项目但一旦游戏体量变大问题就出来了功能之间互相调用、数据互相牵扯今天加一个活动系统明天就要在五个系统里都补一套逻辑。时间一长代码和配置都乱成麻。我把游戏系统定义为“一组规则、状态和数据的交互闭环”。它不是界面也不是单个功能模块而是玩家操作与游戏反馈之间被抽象出来的稳定结构。比如一个简单的战斗系统它至少包含出招规则、命中判定、伤害结算、角色状态变更、战斗结束条件这几件事。界面只是把其中一部分结果展示出来真正的系统逻辑隐藏在这套数据流转里面。做系统设计时我第一件事往往不是画原型图而是把整个系统用一句话说明白——“玩家在这里做什么这个行为会触发什么规则最终产生什么反馈”。这句话想不清楚后面接任何功能都是空中楼阁。1.2 三个底层循环操作循环、系统循环、Meta循环游戏系统之所以能撑起体验是因为它内部存在循环。循环是让系统“活”起来的关键没有循环的系统只是一堆静态页面。第一层是操作循环就是玩家几秒钟内反复做的事比如“点击出牌-牌生效-下张牌补入”。这一层决定了游戏的即时手感。第二层是系统循环由多个操作循环组成比如“打一场战斗-获取经验-升技能-挑战更高关卡”它负责在几分钟到几十分钟内给予玩家成长反馈。第三层是Meta循环跨会话持续存在的长期追求例如“收集角色-养成角色-用角色挑战排行榜-为角色解锁更多养成资源”这个循环可能横跨几个月。做系统时三个循环不是独立存在。最经典的做法是先用一个10秒左右的操作循环把玩家留在游戏里再用一个5到10分钟的系统循环给玩家下一个目标最后用一个Meta循环让玩家愿意明天回来。任何一个循环断掉游戏体验都会出现明显的下沉感。我见到很多独立游戏死在这上面手感不错但打完一关不知道还能干什么因为系统循环没有搭起来。1.3 状态和反馈系统说话的语言循环只是系统的时间轴状态和反馈才是系统“表达”的方式。所谓状态就是系统在任何一瞬间可被观测到的数据快照。用人的身体来比方状态是“我现在是站立、奔跑还是受伤倒地”反馈则是所有被玩家感知到的结果包括掉血数字、屏幕抖动、音效、镜头拉扯甚至UI上浮现的光效。这里有一个我早期踩过的坑只顾着设计状态忘了设计反馈。比如我设计过一个角色“眩晕”状态逻辑上写清楚了眩晕1秒、无法行动但程序实现以后玩家完全没反应过来自己被眩晕了。为什么因为缺少视觉和听觉反馈人物只是呆呆站着没有任何特效和音效提示。后来我在状态变更的入口加了一个强反馈事件才把这个问题解决。做任何系统请记住一条原则状态变更必须伴随反馈反馈强度要和状态的重要度成正比。死亡要重反馈buff剩余时间就是轻反馈不能一张脸走天下。2. 构建系统逻辑的骨架状态机、事件、规则2.1 用状态机把复杂逻辑变成一张表游戏系统很容易在逻辑复杂度上翻车。以战斗角色为例一个角色可以有待机、移动、普攻、技能、受击、眩晕、击飞、死亡等多种状态。如果这些状态之间的切换关系全部写在if/else里最后就是一坨谁也不敢改的面条逻辑。我的基本工具是状态机。先枚举一个实体所有可能的状态再定义“从A状态能否直接切到B状态”以及“切换时需要满足哪些条件”。把这个关系整理成状态转移表程序照着表实现策划照着表配置所有人都能看懂。举个简单例子当前状态触发事件条件下一状态待机攻击按钮技能冷却结束攻击攻击动画结束无待机待机受到伤害当前血量0受击受击受击动画结束无待机任意状态敌技能命中当前血量0死亡待机敌技能命中抗性判定成功待机这张表越到后期越值钱。之前做一个项目某个boss要加“免疫眩晕”机制我只花了十几分钟找到对应转移条件并加一条规则就完成了。如果没有状态机要从几百个if里翻出“眩晕状态到底在哪些地方被触发”改动风险会非常大。2.2 事件驱动让系统之间不互相喊话状态机解决的是单个实体内部的状态变化但游戏系统之间需要通信。比如玩家在战斗系统里击杀了一个怪物任务系统要更新进度成就系统可能要弹一个徽章掉落系统要刷一个宝箱音效系统要放一段音效。如果让战斗系统主动去调用这些系统的方法战斗系统会变得越来越臃肿而且每加一个新系统都要改动战斗系统。业界常用的解耦方案是事件驱动。简单说就是战斗系统只管在击杀怪物时向全局抛出一个事件例如“OnMonsterKilled(怪物ID)”谁关心这个事件谁去监听并处理。任务系统监听后记录进度成就系统监听后判断是否解锁掉落系统监听后生成金币。战斗系统不需要知道有多少个系统在等这个事件只需要负责发出通知。写配置和规则时我也会用事件驱动思路。系统内部所有逻辑都尽量通过“条件-动作”规则表表达。例如“如果怪物被击杀且击杀者拥有某被动技能则在尸体位置生成治疗道具”。把这类规则收拢到一张可配置的规则表里策划就能在不改程序的情况下迭代玩法。这个思路越早做后面接线上活动就越从容。2.3 分层设计别把逻辑写进UI系统架构里还有一个反复强调的分层原则表现层、业务逻辑层、数据层要分离。我在早期项目里犯过特别典型的错误为了快速出效果把掉落逻辑写在了UI点击事件里。结果一旦想换一个入口整段逻辑都要跟着界面搬一次。正确做法有两种。一种是采用严格的MVC或MVVM架构UI只是数据的投影玩家的按钮事件只向业务层发送请求业务层处理完以后再广播新状态UI监听状态变化做更新。另一种是至少做数据驱动让所有系统配置走数据表逻辑代码只读表不在代码里写死数值。我自己最看重的是数据层独立。数值、文案、掉落概率、成长曲线这类内容全部放到配置表里不改代码就能调数值。很多团队觉得这一步麻烦觉得“写死在代码里不是更快吗”但等到版本上线后要热修数值、要调活动概率、要改新手体验你会发现配置表能救命。3. 数值是怎么“支撑”起系统的3.1 数值四要素属性、公式、成长、资源数值不是一堆随机数字它有四根支柱属性、公式、成长、资源。这四个词对应到实际游戏里就是角色有什么底子伤害怎么算能力怎么变强以及玩家用什么来换区成长。属性是角色的底层画像例如攻击力、生命值、防御力、暴击率。属性设计决定了游戏里“什么更有价值”。在一款以进攻为爽点的动作游戏里攻击和暴击应该比生命更有成长权重在生存类游戏里有效生命和减伤反而是配套重点。公式是属性转化成结果的过程。伤害公式可以直接改变属性权重。比如伤害 攻击力-防御力那么攻击力和防御力是线性抵消堆防御永远有固定收益但如果用伤害 攻击力^2 / (攻击力防御力)防御力在攻击力较高时提供的收益会递减同时还能约束PVP中攻击溢出。成长决定了玩家获取属性和能力的时间路径。我们常说的“拍脑袋升级曲线”其实就是在定成长。资源则贯穿整个系统无论是金币、经验、体力还是碎片其本质都是“决策的门票”。玩家手里有多少资源能换到什么永远比单纯的战力数值更有意义。3.2 从体验目标反推数值而不是先从公式抄起很多新手做数值时会先找一个公式然后代入数字测试。我的习惯恰好相反先把游戏体验翻译成可量化的指标再反推数值。比如设计一个小怪我希望玩家在3到4次普攻内解决它每次普攻大概1秒普攻间隔加动画时长约1.5秒那么对手单个小怪的时间目标就是4.5到6秒。接下来再定玩家属性。如果玩家基础攻击力是80普攻攻击系数是1.0攻速是每秒0.8次那么单次伤害就是803到4次攻击意味着怪物血量应该在240到320之间。再考虑玩家可能会穿插技能技能秒伤大约是普攻的1.5倍所以怪物血量可以适当再调高一些。这套从时间感到属性再从属性到数值的过程能够让游戏体验和目标数值高度对齐。万一后面想调整普攻手感速率只需要重新看看时间目标所有数值都能跟着推演而不是从中间某一环凭空拍出一个新数字。3.3 线性、指数、对数三种常见成长曲线成长曲线是数值体系里最影响长线感受的部分。三种常见曲线适用的场景差别很大。线性成长是指每级固定加相同数值例如每级攻击力10。这种曲线简单直观但会造成后期战力膨胀感明显因为百分比增幅越来越小玩到后期升一级感觉不到变化。线性曲线适合商店价格、单局内的资源积累不适合需要长期养成感的等级数值。指数成长则是指属性随等级以固定比例增长例如每级攻击力提升15%。它能带来明显的升级爽感但也容易快速突破数值上限导致游戏内数字从千位一路膨胀到万位、亿位。适合不追求长线平衡、只追求短时爽感的放置游戏。对数成长是增长幅度逐渐减弱例如每级增加的攻击力越来越小或者成长率递减。这类曲线能有效控制终局数值让后期提升主要靠“质变”而不是“量变”比如额外解锁一个技能而不是多几千点攻击。适合竞技类、MOBA类和需要严格平衡的长线游戏。实际项目里通常不是只用一条曲线而是混合搭配。比如养成线用指数给玩家升级快感战斗属性换算用对数约束最终强度这就是“体验层用指数平衡层用对数”的典型组合。4. 一个完整案例战斗循环与数值调优4.1 战斗系统的逻辑结构为了把逻辑和数值串起来我拿一个精简的战斗系统举例。假设这是一个2D横版动作游戏有普攻、技能、受击和死亡状态。战斗循环的过程可以描述为玩家输入攻击指令。系统检查当前状态是否为“可行动”状态检查技能冷却是否结束。命中判定计算攻击方命中率与防守方闪避率掷随机数。命中后进入伤害结算读取攻击力、技能倍率、暴击率/暴击伤害、防御穿透等属性。对防守方伤害并附加受击效果。更新防守方血量若血量归零则进入死亡状态。战斗结算广播事件供任务、掉落、成就系统监听。这个逻辑结构用状态机和事件驱动都能很好承接。战斗系统的核心状态转移就是“待机-施放-结算-回到待机”玩家的按键输入只是触发切换的事件不能直接修改生命值。所有修改生命值的路径都收敛到“伤害结算”这一处这样后期加buff、加debuff、加穿透的时候都只改结算层的规则不会动到其他地方。4.2 伤害公式、属性投放和成长曲线如何协作在这套战斗中我用了一个比较常见的伤害公式最终伤害 技能倍率 × 攻击方ATK² / (ATK 防御方DEF) × 受击修正为什么用ATK²/(ATKDEF)而不是ATK-DEF因为它有两个优点。其一在ATK小于DEF时不会出现负数或零伤害保底机制天然存在其二当攻击方属性远高于防御方时公式会趋近于ATK相当于“碾压”当两者相当时伤害大约是ATK的一半不会立刻秒杀。属性投放则和等级成长挂钩。我给每个等级规划一个基础属性表假设是等级基础ATK基础DEF基础HP1100608001022013024002041023056003070038010500这里基础ATK的成长用了近似指数曲线10级时120%20级时较10级86%30级时较20级70%能够保证每一大段等级都有可感知的提升但又不会爆炸性溢出。DEF的成长相对比ATK慢因为DEF在伤害公式里承担的不是等额抵消而是减伤比例。HP则按更高指数成长给战斗留出更多回合让玩家有时间打完整套技能Combo。技能倍率由技能本身提供但它的数值绝对值必须和上述基础属性匹配。我通常会把所有技能倍率先归一化到“相对普攻的多倍”然后再放到公式里测试单个技能在不同等级下击败同样等级小怪所需次数以此判断倍率是否合理。4.3 用脚本模拟批量战斗别靠手玩去调平衡纯靠手感玩几局很难发现数值问题。我在项目里比较依赖批量战斗模拟。做法很简单把战斗规则抽象成脚本逻辑在本地循环十万次让两个相同等级的虚拟角色按各自技能循环自动打统计胜率、平均战斗时长、血线变化等数据。下面是一段用Python描述战斗模拟的核心逻辑示例实际项目里可以直接把数值表换成策划表。import random def simulate(atk_a, def_a, hp_a, atk_b, def_b, hp_b, rounds10000): wins_a 0 avg_rounds 0 for _ in range(rounds): hp_a_cur, hp_b_cur hp_a, hp_b r 0 while hp_a_cur 0 and hp_b_cur 0: r 1 dmg_a atk_a * atk_a / (atk_a def_b) hp_b_cur - dmg_a if hp_b_cur 0: wins_a 1 break dmg_b atk_b * atk_b / (atk_b def_a) hp_a_cur - dmg_a if hp_a_cur 0 else dmg_a # 这里体现受到伤害 hp_a_cur - dmg_b avg_rounds r return wins_a / rounds, avg_rounds // rounds当然真实战斗模拟会比这个复杂还会包含暴击、闪避、技能循环和buff覆盖但核心思路一致把战斗过程做成可重复的随机试验观察概率分布。最近一次调PVP平衡时我发现两个技能循环完全不同的角色对打胜率从理论计算的51%直接偏到了62%因为某个技能的冷却刚好让它在循环中覆盖率过高。这种问题纯靠人工测试很难稳定复现但用模拟跑十万次两三分钟就能定位。5. 游戏系统数值常见的坑与排查5.1 数值膨胀所有系统都在投放战力长线运营游戏最常见的崩溃方式是数值膨胀。每个系统都觉得自己投放的战力是“合理范围内”但多个系统叠加起来玩家一个版本获得的属性增量可以达到50%甚至更多。新玩家永远追不上老玩家老玩家对上一版本的内容又觉得不痛不痒。战斗系统加入新的养成系统后总伤害放大倍率可能就是版本前的好几倍。对策也很简单把属性投放统一到少数几个核心锚点。比如所有养成系统都要消耗“金币”“体力”“进阶石”这三种资源且所有投放都要折算成“对基础ATK/HP的影响比例”。这样即使加新的养成模块也能快速算出它对总战力倍率的贡献。另一个思路是该用百分比换算时坚决不用绝对值。比如新活动给“攻击力500”就很容易破坏曲线改成“当前攻击力5%”会安全很多。5.2 技能组合“11大于2”乘区暴走战斗数值里最隐蔽的坑是技能组合。单独看每个技能都很合理但把它们放在一起就会乘法叠加直接失控。举例某个角色自带增伤30%的buff目标身上有一个承受伤害增加25%的debuff该角色又能触发150%的暴击伤害。这三者同时成立的瞬间单次伤害倍率就是1.3 × 1.25 × 1.5 2.4375倍相当于纯数值加成接近144%。如果这个伤害在技能循环中又能被频繁触发那么该组合就变成无解的存在。我排查这类问题时会做一个最简单的事列技能效果乘区表把增益按“攻击力提升区”“属伤提升区”“易伤区”“暴击区”等维度分类限定同一乘区的独立来源数量。一个成熟的数值体系不会让玩家同时堆满五个乘区通常最多两到三个乘区可以局部叠加其余必须是互斥或存在覆盖关系。5.3 玩家感知与数值不对齐最后说一个很常见的“隐形问题”玩家觉得游戏数值不合理但实际测算数据完全平衡。原因往往出在感知层不是数值层。比如角色闪避率30%从数据期望上看闪避一次的概率很正常但如果连续三次攻击全部被闪避玩家会觉得“怎么可能这么生气”。这种极端情况在概率分布中完全可能却没有被数值表考虑到。处理方式之一是给随机加“保底机制”比如连续两次未闪避后第三次闪避率提升到50%这叫“记忆性概率”或“伪随机分布”。玩家“打不动怪”也是同理。有时候不是伤害不够而是伤害数字和怪物血条视觉长度不匹配。单次打掉怪物血量10%按理说十下就是一条命但怪物血条图标太短玩家看到一次只掉一小格就误判为“没伤害”。这种时候调数值不如调表现。把血条改为网格分段每格代表20%血量玩家打一下就能看到明显跳变体感会好很多。另一个容易出问题的是文案描述和实际效果不一致。我见过技能写着“造成大量伤害”实际上只有普攻的80%。这种描述会导致玩家对系统的信任崩塌。数值表之外还需要一个简单的描述审查流程所有文案都要与实际数值挂钩。5.4 写在第三章之后的一点体会做到这里“系统逻辑”和“数值支撑”看起来像两件事但在实际项目中它们几乎无法分开推进。我个人的标准流程是先做系统循环原型再用最简数值验证一次“这个循环是否成立”跑通以后再开始堆数值和系统细节。千万不要任何一环追求一步到位系统逻辑太细会卡住数值验证数值太早定死又会让系统逻辑没弹性。每次版本调完数值我都会让程序和策划一起过一遍“状态转移表和数值表是否同步”因为二者只要错一处线上问题就会表现为“角色卡在某个状态无法行动”或“技能伤害和配置不一致”。如果项目没有自动同步工具那就用人工复查清单兜底。用这一套做下来我项目里的系统烂账和数值暴走都少了很多希望能帮你在自己的项目里少踩几个同样的坑。