尧图网络 高端网站定制 · 原创设计
免费咨询热线
400-888-6620
免费获取方案
Cocos Creator回合制战斗系统架构:状态机与数据驱动设计
简介CocoKnightManager 是一套基于 Cocos Creator 的开源回合制游戏系统设计工程面向希望掌握回合制玩法架构的初中级游戏开发者与独立团队帮助其解决战斗流程抽象、角色状态管理与规则扩展等核心问题。压缩包共 22 个文件约 216KB以 json 配置、meta 资源描述、png 贴图、ts 脚本及 fire 场景文件为主配合 README 说明与工程配置构成可直接导入引擎的完整项目骨架。系统围绕回合管理器、行动规则引擎与状态机三大组件展开采用模块化与数据驱动设计属性、效果和事件均可通过 JSON 配置调整便于非程序人员参与内容迭代。目前已有 838 人学习下载读者可从中获取回合流程控制、行动规则自定义与状态切换的参考实现并借鉴其目录组织与模块拆分思路用于自己的回合制项目开发。1. 回合制战斗的骨架比美术更先决定项目生死接手一个回合制游戏项目时最容易被低估的不是战斗数值也不是技能特效而是“回合”这件事本身怎么被系统表达。CocoKnightManager 这套方案要解决的就是一个基于 Cocos Creator 的回合制游戏在系统层怎么搭行动顺序怎么排、状态怎么挂、战斗流程怎么推进、数据怎么配置。它适合正在做游戏毕设、课程设计或者需要交付一份可运行战斗原型的开发者。系统开源的意义在于你能看到一套完整的状态机与数据驱动结构而不是只拿到一堆散落的脚本。回合制看着简单真做起来行动队列和状态结算这两块最容易翻车先把骨架立住后面加骑士、加技能、加关卡才不会推倒重来。2. 需求拆解与总体架构四个子系统撑起一场战斗2.1 从“谁先动”倒推系统边界回合制游戏的核心循环可以拆成一句话按顺序取出一个行动者让它执行一个动作结算动作带来的状态变化再判断战斗是否结束。围绕这句话系统边界自然浮现出四块战斗流程控制、行动者与队伍管理、状态与效果结算、数据配置与加载。CocoKnightManager 的定位就是把这四块拆成独立子系统让它们通过明确接口通信而不是把所有逻辑塞进一个 BattleManager 里。我一般会先画一张职责表把每个子系统“负责什么、不负责什么”写清楚这张表比任何架构图都管用。比如战斗流程控制只负责推进回合和判定胜负不关心某个技能具体扣多少血状态结算只负责根据效果类型修改属性不决定什么时候触发。边界一清楚后面写代码时就不会出现“这个判断到底放哪”的纠结。子系统核心职责不负责的事战斗流程控制回合推进、阶段切换、胜负判定具体伤害公式、技能表现行动者与队伍管理单位创建、属性容器、阵营关系回合顺序的调度策略状态与效果结算增益减益挂载、持续伤害、属性修正何时调用结算数据配置与加载单位/技能/关卡配置解析运行时状态存储这张表的价值在于当需求变更时你能快速定位影响面。比如要加“反击”机制它属于状态结算还是流程控制按上表反击的触发时机归流程控制反击的伤害计算归状态结算两边各改各的不会互相污染。2.2 分层结构与模块依赖方向整体采用三层结构表现层、逻辑层、数据层。表现层基于 Cocos Creator 的节点与组件体系负责动画、血条、飘字逻辑层是纯 TypeScript 的战斗内核不依赖任何渲染 API数据层是 JSON 配置加加载器。关键约束是依赖只能从上往下表现层可以调用逻辑层逻辑层不能反向引用表现层。这样做的直接好处是战斗内核可以脱离引擎单独跑测试。我在项目里通常会把逻辑层做成一个不 import 任何cc模块的目录用纯数据进、纯事件出的方式驱动。表现层订阅逻辑层抛出的战斗事件如onDamage、onUnitDead再决定播什么动画。这条约束如果一开始不立后期想在无渲染环境里跑回归测试基本没戏。// 逻辑层对外只暴露事件不直接操作节点 export interface BattleEvent { type: damage | heal | dead | turnStart | turnEnd; sourceId: string; targetId?: string; value?: number; } export class BattleCore { private listeners: ((e: BattleEvent) void)[] []; // 表现层通过此方法订阅逻辑层不知道订阅者是谁 public onEvent(cb: (e: BattleEvent) void): void { this.listeners.push(cb); } protected emit(e: BattleEvent): void { this.listeners.forEach(cb cb(e)); } }上面这段是逻辑层与表现层解耦的最小骨架。BattleCore只维护一个监听器数组emit时把事件广播出去至于订阅者是 UI 组件还是测试桩它完全不关心。参数上sourceId和targetId用字符串而非对象引用是为了让事件可以被序列化方便录制回放和日志排查。这个设计在后期做战斗回放功能时省了大量返工。2.3 回合状态机的阶段划分回合制最容易写乱的地方是“一个回合里到底有哪几个阶段”。CocoKnightManager 把单个行动者的回合拆成五个阶段回合开始、行动前结算、执行行动、行动后结算、回合结束。每个阶段都有明确的进入条件和退出条件状态机只允许按顺序流转不允许跳阶段。export enum TurnPhase { TurnStart, // 回合开始重置行动次数、触发回合开始效果 PreAction, // 行动前持续伤害、眩晕判定 Acting, // 执行行动等待玩家输入或 AI 决策 PostAction, // 行动后反击、吸血等结算 TurnEnd // 回合结束清理临时状态、递减 buff 回合数 } export class TurnStateMachine { private phase: TurnPhase TurnPhase.TurnStart; public advance(): TurnPhase { // 严格顺序推进越界则回到回合开始 this.phase (this.phase 1) % 5; return this.phase; } public isActing(): boolean { return this.phase TurnPhase.Acting; } }状态机用取模推进看起来简单但配合每个阶段的钩子函数就能覆盖绝大多数回合制规则。PreAction阶段处理持续伤害和眩晕意味着被眩晕的单位在Acting阶段会被跳过这个顺序不能反——如果先判定行动再结算眩晕就会出现“晕着还能打一拳”的玄学 bug。PostAction放在行动之后、回合结束之前是为了让反击和吸血能吃到本次行动的结果同时又不影响 buff 回合数的递减。2.4 数据驱动配置表与运行时状态的分离单位属性、技能效果、关卡配置全部走 JSON运行时只读配置、不写配置。配置加载器在战斗开始前把需要的表一次性解析成内存对象战斗过程中不再读文件。这样做的好处是战斗逻辑可以完全基于内存数据跑帧率稳定也方便做单元测试时注入假配置。{ unitId: knight_01, name: 见习骑士, baseStats: { hp: 120, atk: 18, def: 10, spd: 12 }, skills: [slash, guard], aiProfile: aggressive }配置里baseStats是基础值运行时属性 基础值 装备加成 buff 修正三者分开存储。我见过不少项目把最终属性直接写回配置对象结果一场战斗打完下一场开局属性全是脏的这种坑排查起来极其痛苦。分离存储后每场战斗开始时从配置重新计算一次初始属性脏数据问题从根上消失。3. 核心模块实现行动队列、状态结算与 AI 决策3.1 行动顺序队列速度排序与插队规则回合制的行动顺序有两种常见做法固定顺序轮转或按速度值动态排序。CocoKnightManager 采用后者每个单位维护一个actionValue速度越高行动条涨得越快涨满即行动。这种“行动条”模型比简单的速度排序更灵活能自然支持加速、减速、再行动等效果。export class ActionQueue { private units: BattleUnit[] []; // 每次推进所有单位行动条按速度增长 public tick(): BattleUnit | null { this.units.forEach(u { if (u.isAlive()) u.actionValue u.getStat(spd); }); // 取行动条最高且已达阈值的单位 const ready this.units .filter(u u.isAlive() u.actionValue 100) .sort((a, b) b.actionValue - a.actionValue); if (ready.length 0) return null; const actor ready[0]; actor.actionValue - 100; // 行动后扣除保留溢出值 return actor; } }actionValue阈值设为 100行动后减去 100 而不是清零是为了保留溢出值让高速单位在长期战斗中确实能多行动而不是每次都被截断成相同频率。tick返回null表示本帧无人可行动外层循环继续推进。这里有个细节排序时用actionValue降序相同值时的先后顺序取决于数组原始顺序实际项目里通常再加一个speedTieBreaker字段做稳定排序否则同速单位的出手顺序会随数组变化而漂移测试时很难复现。3.2 状态效果系统用效果类型表替代 if-else 堆叠状态效果如果每个都写一个 if 分支加到第十个技能时方法会长到没法看。更稳的做法是定义效果类型枚举加处理器映射表每种效果类型对应一个处理函数挂载时注册、结算时查表调用。type EffectHandler (target: BattleUnit, effect: ActiveEffect) void; const effectHandlers: Recordstring, EffectHandler { poison: (t, e) { t.takeDamage(e.value); e.remaining - 1; }, atkUp: (t, e) { t.addModifier(atk, e.value); e.remaining - 1; }, stun: (t, e) { t.setFlag(stunned, e.remaining 0); e.remaining - 1; } }; export function resolveEffects(unit: BattleUnit, phase: TurnPhase): void { unit.effects .filter(e e.triggerPhase phase) .forEach(e { const handler effectHandlers[e.type]; if (handler) handler(unit, e); }); // 清理已到期效果 unit.effects unit.effects.filter(e e.remaining 0); }triggerPhase字段决定效果在哪个阶段结算毒在PreAction触发攻击加成在属性计算时生效眩晕在PreAction设置标志位供Acting阶段检查。处理器映射表让新增效果只需要加一个键值对不用动结算主流程。remaining递减放在处理器内部而非统一处理是因为有些效果如一次性护盾不该按回合递减统一递减反而要加例外判断不如让每种效果自己管自己的生命周期。3.3 敌方 AI 决策优先级评分而非硬编码分支敌方 AI 最省事的写法是“血量低于 30% 就治疗否则攻击”但这种硬编码分支一多就互相打架。CocoKnightManager 用评分制每个可选行动算一个分数取最高分执行。评分由多个因子加权比如“目标血量越低分越高”“技能冷却中则分为负”“治疗技能在己方残血时分高”。interface ActionScore { action: BattleAction; score: number; } export function decideAction(ai: BattleUnit, context: BattleContext): BattleAction { const candidates: ActionScore[] ai.availableActions().map(action { let score 0; if (action.type attack) { const target context.lowestHpEnemy(); score 50 (100 - target.hpPercent()) * 0.5; } else if (action.type heal) { const ally context.lowestHpAlly(); score ally.hpPercent() 40 ? 80 : 10; } if (action.cooldown 0) score -1; // 冷却中直接排除 return { action, score }; }); return candidates.sort((a, b) b.score - a.score)[0].action; }评分制的关键是把“策略”变成可调参数而不是散落在 if 里的魔法数字。50 (100 - hpPercent) * 0.5表示攻击基础分 50目标越残血加成越高最高到 100。治疗在己方低于 40% 时给 80 分高于时只给 10 分这样 AI 不会满血乱奶。冷却中的行动直接置 -1 排除避免出现“选了技能却发现放不出”的尴尬。调平衡时只需要改权重不用重写决策逻辑这是评分制相比硬编码最大的优势。3.4 战斗事件总线与表现层对接逻辑层算完表现层要知道发生了什么。事件总线在前面BattleCore里已经埋了骨架这里补上表现层的消费方式UI 组件在onLoad时订阅onDestroy时取消订阅收到事件后按类型分发到动画队列。ccclass(BattleView) export class BattleView extends Component { onLoad() { BattleCore.instance.onEvent(this.handleEvent); } onDestroy() { BattleCore.instance.offEvent(this.handleEvent); } private handleEvent (e: BattleEvent) { switch (e.type) { case damage: this.playDamageAnim(e.targetId!, e.value!); break; case dead: this.playDeathAnim(e.targetId!); break; } }; }订阅和取消订阅必须成对出现这是 Cocos Creator 项目里最常见的泄漏来源之一。handleEvent用箭头函数绑定this保证offEvent时引用一致否则取消订阅会静默失败场景切换后旧组件还在收事件表现为“切了场景还在掉血”的灵异现象。动画播放建议走队列而非直接播放多个伤害事件同时到达时排队执行避免动画互相打断。4. 避坑与排查回合制项目里最容易翻车的五件事4.1 状态结算顺序错乱导致伤害数值对不上现象是同样的阵容、同样的技能两次战斗打出的伤害不一样或者和策划表里的预期值差一截。原因通常是属性修正的叠加顺序不固定有的 buff 在基础值上乘算有的在最终值上加算如果结算时遍历顺序依赖对象键顺序结果就会漂移。解决办法是把修正分成固定三层——基础值、百分比加成、固定值加成——每层内部再按注册顺序稳定排序结算时严格按层计算。我一般会在属性容器里维护三个独立数组而不是一个混合列表从数据结构上杜绝顺序歧义。4.2 回合阶段跳转遗漏导致 buff 永不消失现象是某个增益效果挂上后一直不消失或者眩晕单位永远醒不过来。原因多半是状态机在某个分支里直接跳过了TurnEnd阶段比如单位死亡时提前return导致该单位身上的效果没有走递减流程。解决办法是把“清理到期效果”从阶段钩子里抽出来放到回合推进的统一出口处无论中间怎么跳转出口必执行。另一个常见原因是remaining在多个处理器里被重复递减一个效果一回合掉两层排查时打印每个效果的递减日志就能定位。4.3 事件订阅未解绑造成内存与逻辑双泄漏现象是切换场景后旧战斗还在后台跑或者同一场战斗里伤害飘字播了两次。原因是组件销毁时没有取消事件订阅旧实例仍挂在监听器数组里。解决办法是强制成对写on/off并在onDestroy里做兜底清理。更稳的做法是事件总线内部用WeakMap或给每个订阅打上 owner 标记组件销毁时按 owner 批量移除。血泪经验是只要有一个地方漏了off后期排查成本远高于一开始就写规范。4.4 配置热更新后运行时状态被污染现象是改完配置重新加载战斗中的单位属性变成新旧混合的怪值。原因是配置对象被直接引用进了运行时单位改配置等于改运行时。解决办法是加载配置后做深拷贝再注入单位或者干脆让单位只持有配置的 id属性全部从只读配置表按需查询。我一般会在配置加载器出口加一道Object.freeze深冻结谁不小心写了配置对象开发阶段直接报错比上线后查脏数据省事得多。4.5 行动条溢出值处理不当导致高速单位反而少动现象是速度堆到很高实际出手次数却没有线性增长甚至比中速单位还少。原因是行动后把actionValue直接清零高速单位积累的溢出值被丢弃每次都要从零开始涨。解决办法是行动后减去阈值而非清零保留溢出部分。同时注意阈值判定要用而非否则刚好涨满的单位会被跳过一帧。这个坑很隐蔽因为低速单位不受影响只有堆速度的阵容才会暴露测试时专门造一个高速单位跑长回合就能复现。5. 验收与答辩用可复现的战斗日志证明系统跑通了验收一套回合制战斗系统最忌讳只演示“能打”。评委或验收方真正想看的是规则是否稳定、边界是否覆盖、异常是否可控。我的做法是准备一份战斗日志回放机制把每场战斗的事件流按时间戳记录下来验收时随机抽一段日志逐条核对。日志格式很简单每行一个事件包含回合数、阶段、行动者、事件类型和数值纯文本即可不需要额外工具就能读。# 战斗日志示例每行一个事件字段用竖线分隔 # turn|phase|actor|event|target|value 1|PreAction|knight_01|poison|knight_01|-8 1|Acting|knight_01|damage|goblin_01|-18 1|PostAction|goblin_01|damage|knight_01|-6 1|TurnEnd|knight_01|buffTick|knight_01|atkUp:2验收时按这张表核对三件事阶段顺序是否严格按TurnStart → PreAction → Acting → PostAction → TurnEnd推进每个数值事件是否能由配置表和当前状态反推出来buff 的remaining是否每回合稳定递减一层。这三条过了系统的基本正确性就有底了。答辩时如果被问到“怎么保证没有隐藏 bug”直接拿出日志说“任意一场战斗都能逐行复算”比任何口头保证都有说服力。另一个容易被忽略的验收点是异常路径。我会专门准备几个边界用例单位在PreAction阶段被毒死时Acting阶段是否正确跳过双方同时死亡时胜负判定返回什么行动条刚好等于阈值时是否正常出手。这些用例写成固定脚本每次改完逻辑跑一遍比手动点战斗可靠得多。答辩陈述我一般控制在三段第一段讲系统边界和分层说明为什么逻辑层不依赖引擎第二段现场跑一场战斗并打开日志指着某一行解释数值怎么来的第三段主动说一个踩过的坑和怎么修的比如行动条溢出值那个问题。主动暴露一个已修复的问题比假装完美更能让评委相信你真的动手做过。从那以后我每次交付战斗系统都强制先跑一遍日志回放和边界用例再谈演示效果。希望帮到你。本文还有配套的精品资源点击获取
RELATED

相关推荐

Cocos Creator回合制战斗系统设计:分层架构与核心模块实现

Cocos Creator回合制战斗系统设计:分层架构与核心模块实现

简介:CocoKnightManager 是一套基于 Cocos Creator 的回合制游戏系统设计开源工程,面向希望掌握回合制玩法架构的初中级游戏开发者与独立团队,帮助其解决战斗流程抽象、角色状态管理与规则扩展等核心问题。压缩包共 22 个文件,约 …

📅 2026/10/31 7:12:46
基于Python+Django+jieba的文本相似度计算系统设计与实现

基于Python+Django+jieba的文本相似度计算系统设计与实现

简介:这份DOCX文档面向计算机相关专业毕业设计学生与自然语言处理入门者,围绕基于Python的文本相似度计算系统给出论文式方案参考,可帮助解决选题立意、系统设计和论文结构搭建等问题。压缩包共1个DOCX文件,约749KB,主…

📅 2026/10/31 7:12:46
EEGLab脑电分析全流程:从数据导入到ICA伪迹剔除的实操指南

EEGLab脑电分析全流程:从数据导入到ICA伪迹剔除的实操指南

简介:EEGLab V10.0.0.0b 压缩包面向神经科学、心理学与生物医学工程领域的研究者及学生,提供一套完整的脑电(EEG)分析工具环境,可用于信号质量检查、预处理、事件相关电位分析、频率分析与源定位等任务,也便…

📅 2026/10/31 7:12:46
MORE NEWS

更多资讯

📰

OpenShell 命令行框架实战:命令注册、参数校验与跨语言组合

1. 从零认识 OpenShell:它到底解决什么问题第一次听到 OpenShell 这个名字,很多人会下意识以为它跟某个操作系统内核或者远程终端工具有关。实际上,OpenShell 是一个面向命令行交互场景的开源框架,核心目标是把“人跟机器对话”这…

📰

串联混合动力汽车能量管理与模型预测控制实践

1. 串联混合动力汽车能量管理概述 串联混合动力汽车(Series Hybrid Electric Vehicle, SHEV)作为新能源汽车的重要分支,其能量管理系统的设计直接决定了整车性能和经济性表现。与传统燃油车相比,SHEV的动力系统架构具有显著差异&a…

📰

PostgreSQL并发控制:MVCC与锁机制详解

1. 并发控制的基本概念与PostgreSQL实现 在数据库系统中,并发控制是确保多个事务同时执行时数据一致性的关键技术。PostgreSQL作为一款功能强大的开源关系型数据库,提供了多种并发控制机制来应对不同的业务场景需求。 并发控制主要解决三类问题&#xf…

📰

搜索引擎高级检索的底层逻辑与六大全能语法

1. 这不是“搜索技巧”,而是信息筛选的底层逻辑重构很多人把“高级搜索语法”当成玄学口诀来背:site:、intitle:、filetype:这些符号像咒语一样被反复默念,却始终卡在“搜到了但不是我要的”这个死循环里。我带过不少刚接触信息检索的某高校学…

📰

DirectDraw老源码迁移实战:从编译到双缓冲游戏循环

简介:这份资源是《Windows游戏编程大师技巧(第二版)》配套光盘源码的完整打包,面向有一定C基础、希望系统学习Windows平台游戏开发的中高级读者,可用于对照书中章节动手复现示例、理解底层实现思路。压缩包共收录1035个…

📰

ICU4C 54.1 源码包实战:从编译集成到裁剪优化

简介:这份资源是 ICU4C 54.1 版本的完整 C 源码包,面向需要在 Windows 平台使用 Visual C 编译国际化与 Unicode 支持库的开发者,尤其适合从事跨语言、跨区域应用开发的中高级 C 工程师。压缩包共 4519 个文件,约 25.7MB&#xff…

TODAY

今日更新

THIS WEEK

本周精选

THIS MONTH

本月热门

读完文章,想聊聊您的网站?

告诉我们您的行业与需求,资深顾问一对一梳理方案与报价,全程免费。

📞 💬