尧图建网站 尧图建网站 YAOTU WEB BUILD 免费咨询
ARTICLE DETAIL

资讯详情

深耕网站建设与建站编程的一线实战洞察。

Unity回合制战斗框架TBST:从网格寻路到AI决策的完整解决方案

Unity回合制战斗框架TBST:从网格寻路到AI决策的完整解决方案 1. 项目概述为什么我们需要一个现成的回合制战斗框架做战棋或者策略类游戏听起来很酷但真正动手开发过的人都知道这里面的水有多深。从最基础的网格地图生成、单位移动范围计算到复杂的AI寻路、技能释放逻辑、状态机管理再到战斗UI的血条、行动顺序条每一个环节都能让开发者掉一层皮。很多时候项目还没到核心玩法验证阶段就已经被这些底层框架的“脏活累活”拖垮了。这就是为什么当我看到Turn-Based Strategy Toolkit (TBST)这个Unity插件时会感到眼前一亮。它不是一个简单的脚本集合而是一个完整的、开箱即用的回合制战斗解决方案。它把战棋游戏比如《火焰纹章》、《XCOM》和策略游戏比如《文明》系列中最核心、最通用的那部分系统提前帮你搭建好了。你不再需要从零开始写一个A*寻路算法也不用头疼如何优雅地管理几十个单位的状态。TBST提供了一个经过验证的架构让你能把宝贵的开发时间集中在打磨你游戏独一无二的玩法、剧情和美术表现上。简单来说TBST就是一个“战棋游戏引擎中的引擎”。它封装了底层复杂性暴露了高度可配置的接口。无论你是想做一个致敬经典的日式SRPG还是一个充满随机性的Roguelike策略游戏甚至是带有战棋元素的RPG这个工具箱都能提供一个坚实的起点。接下来我就结合自己实际使用的经验把这个插件的里里外外拆解清楚告诉你它到底能做什么以及怎么用它来高效地搭建你的策略世界。2. 核心模块深度解析TBST的四大支柱TBST的架构非常清晰主要围绕四个核心模块展开网格地图系统、单位管理系统、AI逻辑系统和战斗UI系统。这四个模块相互独立又紧密协作共同构成了一个完整的回合制战斗循环。2.1 网格地图系统战场的基石所有战棋游戏的舞台都是一个被划分好的网格。TBST的网格系统是其最核心的组件之一它不仅仅是视觉上的格子更承载了地形、移动成本、视野阻挡等丰富的游戏逻辑数据。网格数据与生成TBST通常提供两种网格生成方式基于Tilemap适合2D或2.5D游戏和基于Mesh适合3D游戏。我个人更常用基于Tilemap的方式因为它与Unity的2D工作流集成得更好美术资源制作也方便。// 示例通过代码动态创建一个10x10的网格 public class BattleGridManager : MonoBehaviour { public Grid grid; // Unity的Grid组件 public Tilemap walkableTilemap; // 可行走层 public Tile walkableTile; // 可行走格子的Tile void GenerateGrid(int width, int height) { for (int x 0; x width; x) { for (int y 0; y height; y) { Vector3Int tilePosition new Vector3Int(x, y, 0); walkableTilemap.SetTile(tilePosition, walkableTile); // 同时需要向TBST的网格管理器注册这个格子并设置其属性 // 例如移动成本为1地形类型为“平原” } } } }每个网格节点Node在TBST内部都是一个数据结构至少包含以下信息坐标在网格中的位置。移动成本单位进入这个格子需要消耗的移动力。平原是1森林可能是2山脉可能是3甚至不可通行。地形效果可能提供的防御加成、回避加成或特殊效果如治疗泉。占据状态当前是否有单位站在上面。寻路与移动范围计算这是网格系统的灵魂。TBST内置了高效的寻路算法通常是A*算法的变种你只需要关心规则。例如计算一个移动力为5的单位可以到达的范围注意寻路计算是性能敏感点尤其是单位众多或地图很大时。TBST通常会做优化比如缓存移动范围、使用跳跃点搜索JPS优化大范围空旷地图。在自定义地形成本非常复杂的场景中你需要关注算法的性能表现。实际操作中你调用一个类似CalculateMovementRange(unit, startNode)的方法它会返回一个所有可达节点的列表并且通常会自动排除被敌方单位占据的格子除非有“穿越”技能。更高级的是它还能计算“攻击范围”——即从每个可达格子出发单位武器能攻击到的区域这常用于显示单位的威胁范围。2.2 单位管理系统战场上的棋子单位Unit是玩家的化身也是所有策略的执行者。TBST的单位管理系统提供了一个可扩展的基类用于定义单位的属性和行为。单位属性与组件化设计一个标准的TBST单位通常会包含以下核心属性基础属性生命值HP、魔法值MP、攻击力、防御力、速度等。战斗属性命中率、暴击率、格挡率等。状态信息当前是否处于“待机”、“移动中”、“行动结束”或“异常状态”如中毒、眩晕。TBST通常采用组件Component模式来组织这些功能。例如MovementComponent: 处理移动逻辑包含移动力属性。AttackComponent: 处理攻击逻辑包含攻击范围、伤害计算公式。SkillComponent: 管理单位所拥有的技能列表。StatusEffectComponent: 管理单位身上的增益和减益效果。这种设计的好处是灵活。如果你想给单位添加一个“潜行”技能不需要修改核心的Unit类只需要创建一个StealthComponent并挂载上去即可。单位行动流程回合制战斗的核心是“行动点”或“行动阶段”。TBST规范了一个单位的典型行动流程回合开始触发“回合开始”事件用于回复少量HP/MP或处理持续伤害。选择行动玩家或AI为单位选择移动、攻击、使用技能、道具或待机。执行移动如果选择了移动单位沿计算好的路径移动到目标格。这个过程通常有平滑的动画插值。执行主要行动移动后或不移动执行攻击或技能。此时会进入一个子状态比如选择目标、播放攻击动画、计算伤害。回合结束行动执行完毕后单位状态变为“已行动”触发“回合结束”事件。TBST通过一个状态机来管理这个流程确保逻辑不会乱套。比如一个单位在“移动中”状态时是不能接收攻击指令的。2.3 AI逻辑系统与聪明的电脑对战没有AI的战棋游戏是没有灵魂的。TBST的AI系统提供了一套框架让你能够相对轻松地构建具有挑战性的电脑对手。基于目标的AI决策树TBST的AI通常不是深度学习那种黑盒而是基于规则和目标的、可预测的“脚本化”AI这正适合策略游戏。其决策过程可以简化为评估目标AI单位当前的首要目标是什么是攻击最脆弱的后排还是去占领某个据点TBST允许你为AI设置不同的“目标权重”。生成可行方案AI会枚举所有可能的行动组合。例如“移动到A点攻击玩家牧师”、“移动到B点使用范围治疗技能”、“原地不动进行防御”。方案评分对每个方案进行打分。评分规则可以自定义例如攻击方案得分 预期伤害 * 伤害权重 (目标单位剩余HP百分比低) * 击杀权重 - 自身风险系数。治疗方案得分 可恢复生命值总量 * 治疗权重。执行最高分方案AI选择得分最高的方案并执行。可配置的AI人格你可以通过调整评分规则的权重来塑造不同的AI“人格”。比如激进型伤害权重极高倾向于换血无视自身风险。保守型自身风险系数权重高优先保证生存喜欢待在治疗单位旁边。战术型击杀权重高优先攻击残血单位或者对“占领据点”这个行为赋予高权重。在TBST中实现一个简单的攻击AI可能像下面这样public class AggressiveAI : UnitAIBase { public override Action DecideAction(Unit aiUnit, ListUnit playerUnits) { Action bestAction null; float bestScore float.MinValue; foreach (Unit target in playerUnits) { // 1. 检查是否能攻击到目标 if (CanAttackTarget(aiUnit, target, out AttackData attackData)) { // 2. 为这次攻击评分 float score CalculateAttackScore(aiUnit, target, attackData); // 3. 更新最佳行动 if (score bestScore) { bestScore score; bestAction new AttackAction(aiUnit, target, attackData); } } } // 如果没有找到攻击目标则移动到离最近敌人最近的位置 if (bestAction null) { bestAction CalculateMoveToNearestEnemyAction(aiUnit, playerUnits); } return bestAction; } private float CalculateAttackScore(Unit attacker, Unit defender, AttackData data) { float score data.estimatedDamage; // 基础分预期伤害 score (1f - defender.HPPercentage) * 50f; // 奖励分目标血量越低分越高鼓励补刀 score - attacker.CalculateRiskAfterAttack(defender) * 20f; // 惩罚分攻击后自身风险 return score; } }实操心得AI的调试是个耐心活。TBST通常提供AI行为可视化工具比如在编辑器中显示AI单位的“视野”、“可攻击范围”和“当前最佳目标”。善用这些工具能极大提高调试效率。另外别忘了给AI加入一定的随机性让玩家的体验不会每次都一样。2.4 战斗UI系统信息的窗口清晰、及时的战斗UI是策略游戏体验的重要组成部分。TBST提供了一套可定制的UI预制件和绑定逻辑。核心UI组件单位信息面板当鼠标悬停或选中一个单位时显示包含其头像、HP/MP条、属性数值和状态图标。行动菜单单位选中后出现的环形或列表菜单包含“移动”、“攻击”、“技能”、“道具”、“待机”等选项。战斗预测窗口在玩家选择攻击或技能目标时弹出显示命中率、预期伤害范围、暴击概率等关键信息。这是减少玩家挫败感的关键设计。回合指示器显示当前是“玩家回合”还是“敌方回合”以及行动顺序列表如果游戏有速度属性决定出手顺序。数据驱动与事件绑定TBST的UI系统通常是数据驱动的。UI元素不直接操作游戏逻辑而是监听游戏状态的变化。例如Unit的OnHPChanged事件被触发 →HealthBarUI组件接收到事件 → 更新血条填充值和平滑动画。BattleManager的OnTurnStarted事件被触发 →TurnIndicatorUI组件接收到事件 → 高亮当前行动的单位。这种松耦合的设计让你可以随意替换UI美术资源而无需修改核心游戏代码。你只需要确保新的UI预制件上有相同的脚本组件并订阅了正确的事件。3. 实战构建从零搭建一个简易战棋关卡理论说了这么多我们来点实际的。假设我们要用TBST创建一个最简单的关卡一张小地图一个玩家英雄一个敌方小兵。3.1 环境准备与基础设置首先在Unity中导入TBST插件。通常它会创建几个重要的管理器预制件比如BattleManager、GridManager、UnitManager。你需要把这些拖入场景或者让它们在运行时动态生成。场景搭建创建一个Grid游戏对象添加Unity的Grid组件。创建两个Tilemap作为子对象一个叫WalkableLayer可行走层一个叫ObstacleLayer障碍层。在TBST的GridManager组件上将这两个Tilemap分别赋值给“可行走图层”和“障碍图层”字段。这样TBST就能自动从Tilemap读取地图数据。单位创建创建一个3D模型或2D精灵作为英雄的视觉表现。给这个游戏对象添加TBST提供的Unit组件或继承自它的HeroUnit脚本。在Unit组件上配置基础属性HP30攻击力8防御力3移动力5。添加MovementComponent和AttackComponent并在攻击组件上设置攻击范围为“相邻一格”伤害公式为简单的“攻击力-防御力”。重复上述步骤创建一个属性稍弱的EnemyUnitHP20攻击力6防御力2移动力4。3.2 配置战斗流程与规则接下来需要配置战斗如何开始、进行和结束。战斗初始化 在BattleManager中你需要指定玩家单位列表和敌方单位列表。TBST会在战斗开始时根据单位的“队伍”属性将它们分配到对应的阵营。// 在一个初始化脚本中 void StartBattle() { ListUnit playerTeam new ListUnit { playerHeroUnit }; ListUnit enemyTeam new ListUnit { enemySoldierUnit }; BattleManager.Instance.InitBattle(playerTeam, enemyTeam); BattleManager.Instance.StartPlayerTurn(); // 玩家先手 }胜利/失败条件 TBST允许你自定义胜利条件。最常见的是“全歼敌人”。你需要在BattleManager中订阅单位死亡事件并检查条件。void OnUnitDied(Unit deadUnit) { if (deadUnit.team Team.Enemy) { bool allEnemiesDead UnitManager.Instance.GetAllUnits(Team.Enemy).Count 0; if (allEnemiesDead) { Debug.Log(战斗胜利); // 触发胜利UI、奖励发放等 } } else if (deadUnit.team Team.Player) { bool allPlayersDead UnitManager.Instance.GetAllUnits(Team.Player).Count 0; if (allPlayersDead) { Debug.Log(战斗失败...); } } }3.3 连接UI与实现玩家输入现在我们需要让玩家能控制他的英雄。鼠标交互 TBST通常提供一套鼠标输入处理器。你需要做的是确保场景中有CameraController控制镜头和MouseInteractionManager处理鼠标悬停、点击。当鼠标悬停在一个可移动格子上时MouseInteractionManager会高亮该格子。当玩家点击一个已选中的单位然后点击一个高亮的可移动格子时触发单位的移动命令。UI绑定 将TBST提供的UnitInfoPanel预制件放入Canvas。在UnitInfoPanel组件上它会自动查找场景中的MouseInteractionManager或SelectionManager当有单位被悬停或选中时自动更新面板上的信息。行动菜单 当玩家选中自己的单位后实例化ActionMenu。菜单上的每个按钮都绑定了一个具体的Action命令如MoveAction,AttackAction。点击“攻击”按钮后游戏状态进入“选择攻击目标”模式此时所有敌方单位会被高亮点击其中一个即执行攻击。4. 进阶技巧与性能优化当你的游戏规模变大单位增多技能效果复杂后一些潜在的问题就会浮现。这里分享一些TBST框架下的进阶处理技巧。4.1 复杂技能与状态效果系统TBST的基础攻击组件可能只支持简单的单体攻击。要实现一个“对3x3范围内所有敌人造成伤害并附加中毒效果”的技能你需要扩展技能系统。技能数据脚本化 建议使用ScriptableObject来定义技能数据。这是一个资源文件可以在编辑器里配置无需写死代码。[CreateAssetMenu(fileName NewAreaPoisonSkill, menuName TBST/Skills/AreaPoison)] public class AreaPoisonSkillData : SkillData { public int range; // 施法范围 public DamageData damage; public StatusEffectData poisonEffect; // 中毒效果的数据 public GameObject castVFX; // 施法特效 public override void Execute(Unit caster, Vector3Int targetCell) { // 1. 播放施法动画和特效 Instantiate(castVFX, GridManager.GetWorldPosition(targetCell), Quaternion.identity); // 2. 获取目标区域所有单位 ListUnit targets GetUnitsInArea(targetCell, 3, 3, Team.Enemy); // 3. 对每个目标应用伤害和效果 foreach (Unit target in targets) { CombatCalculator.CalculateDamage(caster, target, damage); target.StatusEffectComponent.ApplyEffect(poisonEffect); } // 4. 消耗MP结束单位行动 caster.ConsumeMP(this.mpCost); caster.EndTurn(); } }状态效果管理器 中毒、眩晕、防御提升等状态效果需要持续多个回合。TBST可能有一个基础的StatusEffect类你需要实现一个StatusEffectManager来负责效果的添加、移除、持续时间和回合触发。关键点是效果的叠加规则和刷新机制。例如同一个单位被施加两次“攻击力提升50%”的效果是叠加为100%还是取最大值还是持续时间刷新这需要在StatusEffect基类中设计好。4.2 寻路与范围计算性能调优当地图达到100x100单位有几十个时每回合为每个单位计算移动和攻击范围会成为性能瓶颈。缓存是王道移动范围缓存如果一个单位本回合没有移动且地图上的障碍物没有变化那么它的移动范围是不会变的。可以在单位移动或地图改变时才重新计算。攻击范围缓存攻击范围通常只取决于单位的武器和自身位置。如果单位没移动攻击范围也可以缓存。使用更高效的算法TBST内置的A*算法对于标准网格通常足够快。但对于超大地图可以研究插件是否支持切换为Dijkstra用于计算所有格子的移动成本或BFS用于计算固定移动力的范围它们在特定场景下可能更快。分层寻路对于大地图可以先在由“房间”或“区域”组成的粗粒度网格上寻路再在目标区域内进行精细寻路。减少不必要的计算只在需要显示如鼠标悬停或AI决策时才计算单位的移动/攻击范围。使用Job System或Burst Compiler进行并行计算。一些高级的TBST插件或自己实现的扩展可以考虑将寻路这种纯计算任务放到Job中能极大提升性能。4.3 网络同步与存档读档考虑虽然TBST主要面向单机游戏但你的项目可能有多人模式或需要稳定的存档功能。确定性逻辑 回合制游戏是实现网络同步和确定性回放的绝佳类型因为所有输入都是离散的。关键在于确保整个游戏逻辑是确定性的——在相同的随机种子下相同的操作序列必须产生完全相同的结果。避免使用UnityEngine.Random使用自定义的、可序列化的伪随机数生成器PRNG并将随机种子存入存档或通过网络同步。浮点数精度不同平台CPU架构的浮点数运算可能有细微差异。对于关键计算如伤害公式考虑使用定点数或确保运算顺序一致。存档数据设计 存档需要保存整个战斗的状态。TBST的各个管理器GridManager,UnitManager,BattleManager应该提供序列化接口。单位数据保存每个单位的唯一ID、位置、当前HP/MP、状态效果列表等。战场数据保存当前回合数、当前行动方、随机数种子等。序列化方式可以使用JsonUtility、Newtonsoft.Json或二进制格式。记住只保存必要的数据不保存对MonoBehaviour或GameObject的引用。5. 常见问题排查与避坑指南在实际使用TBST的过程中你肯定会遇到一些“坑”。下面是我和社区里常见的一些问题及解决方案。5.1 单位移动或寻路异常问题现象可能原因解决方案单位无法移动到看似可达的格子1. 该格子的移动成本被设置为无限大不可通行。2. 该格子被ObstacleLayer上的Tile占据。3. 单位的MovementComponent的移动力不足。1. 检查TBSTGridNode数据中该格子的移动成本。2. 检查ObstacleLayerTilemap。3. 在编辑器中选中单位查看其当前移动力。单位寻路绕远路不走直线1. A*算法的启发函数权重设置不当。2. 对角线移动成本设置不正确应为1.414但被近似为1或2。3. 地图中存在移动成本差异巨大的区域算法在“绕开高成本区”和“走直线”间权衡。1. 调整A启发函数的权重如从1.0调到1.1可能更倾向于直线。2. 确认TBST网格设置中是否允许对角线移动及其成本计算方式。3. 这是正常现象A会找“成本最低”路径而非“格数最少”路径。移动路径显示正常但单位“卡住”不动1. 移动动画系统出现问题如动画控制器状态未切换。2. 单位移动的终点坐标与世界坐标有微小偏差导致“到达”判断失败。3. 有其他逻辑如碰撞体、触发器阻止了移动完成事件。1. 检查单位的Animator在收到Move命令时是否正确触发。2. 在移动逻辑的终点判断中增加一个很小的容差epsilon例如if (distanceToTarget 0.1f) StopMoving()。3. 使用Debug.Log或断点检查移动协程或状态机是否正常进入完成状态。5.2 AI行为不符合预期问题AI单位像个“傻子”原地发呆或来回踱步。排查1目标评分函数有缺陷。检查你的CalculateAttackScore或类似函数。是不是所有行动方案的得分都是0或负数可能是计算风险的惩罚分太高导致AI认为任何行动都“太危险”。尝试调整权重参数或者为“待机”行动设置一个基础分比如5分让AI在找不到好目标时至少会选择待机。排查2行动可行性判断过于严格。在CanAttackTarget函数中你是否检查了“攻击路径上不能有友军阻挡”在一些游戏中远程攻击是可以越过友军的。确认你的规则是否符合设计。同样移动可行性也要检查是否因为忽略了“单位占据”而导致AI认为无处可去。排查3没有为AI设置“默认行动”。在决策循环的最后如果bestAction仍然是null必须有一个保底行动比如“向最近敌人移动”或“使用防御技能”。实操心得调试AI时一定要将其思考过程“可视化”。我通常会在AI决策时用Debug.DrawLine画出它评估的攻击路径或者用临时UI文字显示它给每个行动打的分数。这样一眼就能看出AI为什么做出了“愚蠢”的选择。5.3 UI显示不同步或更新延迟问题单位血条不更新或者行动菜单在单位死亡后仍然显示。根源事件订阅与取消订阅。这是Unity事件系统开发中最常见的Bug之一。确保在UI脚本如HealthBar的OnEnable方法中订阅单位的事件如OnHPChanged在OnDisable或OnDestroy方法中取消订阅。否则当单位被销毁如死亡或UI被禁用时事件引用会残留导致错误或内存泄漏。检查数据绑定时机UI刷新的时机应该在数据变化之后。确保是Unit.HP属性先改变然后触发事件最后UI响应事件更新。不要在改变HP的同一帧直接调用UI更新方法这可能导致执行顺序问题。使用Unity的EventSystem有些TBST版本可能深度集成Unity的UI事件系统。检查是否有IPointerEnterHandler这样的接口需要正确实现以及EventSystem.current.IsPointerOverGameObject()是否会阻断你对3D游戏对象的点击。5.4 打包后出现的诡异问题问题在编辑器里运行完美打包Build后单位无法移动或AI不工作。首要怀疑资源引用丢失。检查所有在代码中通过public GameObject prefab;或[SerializeField]拖拽赋值的字段。在打包时确保这些Prefab或ScriptableObject资源被正确包含在构建中。对于动态加载的资源如Addressables确保打包设置和运行时加载代码正确。其次怀疑路径或坐标计算差异。编辑器和打包后某些底层API如Application.dataPath的返回值不同。如果你的代码里硬编码了资源路径打包后肯定会失效。TBST框架本身应该处理好了这些问题但你的扩展代码需要注意。使用Development Build打包时勾选Development Build和Autoconnect Profiler。这样当游戏在玩家电脑上运行时你可以在编辑器的Profiler中远程连接查看错误日志和性能状况这是定位打包后Bug的利器。最后关于TBST插件的学习最好的资料除了官方文档就是其自带的示例项目Example/Scenes。务必仔细研究每一个示例场景从最简单的移动攻击到复杂的技能连锁和AI行为树。尝试修改示例中的参数观察变化这是最快上手的方法。记住框架是为你服务的工具当你熟悉了它的运作方式后就可以大胆地修改和扩展它让它完美适配你那独一无二的策略游戏创意。
返回列表