摘要:本文聚焦「决策系统综合指南」,梳理核心概念、关键方法与落地实践。
🧠 通用加权决策系统
本文档旨在抽象 Project Vampirefall 中多个核心系统的底层逻辑,构建一个通用的、基于上下文的加权选择器 (Context-Aware Weighted Selector)。 通过统一仇恨 (Aggro)、塔防索敌 (Tower Targeting) 和 肉鸽抽卡 (Perk Drafting) 的决策代码,我们可以减少重复逻辑,提高系统的可维护性和扩展性。1. 系统概述 (Overview)
在游戏中,我们经常面临这样的问题:“从一堆选项中,根据当前情况,选择最合适的一个(或几个)。”- 仇恨系统: 从一堆怪物中,选出威胁最大的攻击。
- 塔防索敌: 从射程内的敌人中,选出价值最高的击杀。
- Perk 抽取: 从几百个强化词条中,选出最适合玩家当前流派的展示。
Input -> Scoring -> Selection 的模式。
2. 核心架构 (Core Architecture)
2.1 流程图 (Flowchart)
2.2 核心组件 (Components)
-
候选人 (Candidate
T): 待选择的对象(Enemy, Tower, PerkData)。 -
上下文 (Context
C): 决策时的环境信息(距离、玩家 HP、已拥有的 Tags)。 -
评分器 (Scorer
IScorer<T, C>): 一个独立的逻辑单元,负责计算单项分数。 - 选择器 (Selector): 负责运行所有评分器并汇总结果。
3. 评分器策略库 (Scorer Strategy Library)
通过组合不同的评分器,我们可以“拼装”出不同的 AI 行为,而无需重写代码。3.1 基础评分器
| 评分器名称 | 逻辑描述 | 适用场景 |
|---|---|---|
| DistanceScorer | 距离越近,分数越高 (线性或指数衰减)。 | 仇恨(近战怪)、塔防(近程塔) |
| HealthScorer | 生命值越低,分数越高 (斩杀逻辑)。 | 刺客型怪物、收割型防御塔 |
| TagSynergyScorer | 拥有相同标签 (Tag) 数量越多,分数越高。 | Perk 抽取、战利品生成 |
| FixedPriorityScorer | 基于硬编码的优先级 (Boss > Elite > Minion)。 | 塔防(优先打大怪) |
| MemoryScorer | 之前互动过 (造成伤害/被选中) 则加分。 | 仇恨(反击逻辑)、连击系统 |
3.2 评分公式
标准的归一化评分公式:- Multiplier (乘区): 用于调整权重(例如:刺客怪的
HealthScorer权重是 5.0,而DistanceScorer权重是 0.5)。 - FlatBonus (加算): 用于强制覆盖(例如:嘲讽状态直接 +10000 分)。
4. 实战应用配置 (Configuration Examples)
Case A: 怪物仇恨 (Aggro System)
- 目标: 选一个攻击目标。
- 选择模式:
Top 1(确定性)。 - 配置:
DamageReceivedScorer: 权重 1.0 (谁打我,我打谁)。DistanceScorer: 权重 2.0 (谁离我近,我打谁)。TauntStatusScorer: 权重 100.0 (嘲讽强制最高)。
Case B: 狙击塔索敌 (Sniper Tower Targeting)
- 目标: 选一个敌人开火。
- 选择模式:
Top 1(确定性)。 - 配置:
DistanceScorer: 权重 -1.0 (反向,优先打远的)。HealthScorer: 权重 2.0 (优先打残血,确保击杀)。ArmorTypeScorer: 若目标是重甲,权重 0.5 (打不动);若轻甲,权重 1.5。
Case C: 肉鸽 Perk 抽取 (Perk Drafting)
- 目标: 选 3 个 Perk 给玩家。
- 选择模式:
Weighted Random(加权随机)。 - 配置:
RarityBaseScorer: 传说(5) < 史诗(15) < 稀有(30) < 普通(50)。TagSynergyScorer: 玩家若有[Fire],火系 Perk 权重 x 1.5。BanListFilter: 若玩家选了[NoMagic],剔除所有法术 Perk。PityTimerScorer: 若连续 10 次没出传说,传说权重 x 10。
5. 代码实现参考 (C# Implementation)
为了保证性能(避免每帧 GC),建议使用结构体或预分配内存。6. 性能优化指南 (Optimization)
由于 AI 决策可能每一帧都在跑,必须注意开销。- 分帧计算 (Time-Slicing): 不要让所有怪物在同一帧跑决策逻辑。将怪物分组,每帧只更新一组。
-
空间划分 (Spatial Partitioning): 在运行
DistanceScorer之前,先通过四叉树 (QuadTree) 或网格系统获取附近的候选人,避免遍历全图。 - 脏标记 (Dirty Flags): 对于 Perk 系统,只有当玩家获得新 Perk 或进入新房间时才重新计算权重,而不是每帧计算。
-
提前退出 (Early Exit): 在寻找
SelectBest时,如果发现一个“绝对优先”的目标(如嘲讽),直接返回,跳过后续计算。
🏗️ 通用决策系统架构图
本文档作为系统的工程蓝图,详细定义了类结构、接口关系及运行时序。1. 类图结构 (Class Diagram)
该图展示了核心泛型引擎与具体业务系统(仇恨、塔防、Perk)的继承与组合关系。2. 运行时序图 (Sequence Diagram)
2.1 怪物索敌流程 (AI Select Best)
展示了怪物如何通过多重评分器选出最佳攻击目标。2.2 Perk 抽取流程 (Weighted Random Draft)
展示了如何根据玩家流派权重抽取 Perk。3. 数据流设计 (Data Flow Specs)
为了支持通用的Context,我们需要一个灵活的黑板机制。
3.1 Context Blackboard 结构
DecisionContext 不仅仅是位置信息,它包含了一个 Dictionary<string, object> 或强类型的 Blackboard 结构,用于传递特定业务参数。
| Key (String) | Type | Description | Used By |
|---|---|---|---|
"AttackerPos" | Vector3 | 发起者的位置 | DistanceScorer |
"PlayerHP" | float | 玩家当前血量百分比 | MercyScorer (低血量降低怪物攻击欲望) |
"PlayerTags" | List<string> | 玩家拥有的流派标签 | SynergyScorer |
"PityCounter" | int | 保底计数器 | RarityScorer |
"LastTarget" | Entity | 上一次攻击的目标 | StickinessScorer (粘性评分,防止频繁切换) |
4. 优化策略 (Optimization Plan)
在架构层面预留性能优化接口。IJob兼容性: 设计IScorer时尽量使用struct和NativeArray,以便未来可以将计算繁重的评分逻辑放入 Unity Job System 并行处理。- 预分配列表 (Pre-allocation):
DecisionEngine内部维护静态或对象池化的List<float> scores,避免在SelectBest中产生 GC Alloc。
💻 核心代码定义
本文档定义了通用决策系统的关键 C# 接口与类结构,包括核心引擎和一组标准评分器。0. 基础数据接口 (Core Data Interfaces)
为了让评分器能够通用,候选对象T 需要实现这些接口,以暴露必要的数据。
IPositionable
IHealth
IHasTags
IHasEntityType
1. 基础接口 (Interfaces)
DecisionContext (上下文)
用于在评分过程中传递环境数据。使用 Dictionary 实现灵活的黑板模式,同时也提供常用属性的快捷访问。
IScorer<T> (评分器)
核心逻辑单元。
IFilter<T> (过滤器)
用于在评分前剔除无效目标(硬性门槛)。
2. 核心引擎 (Core Engine)
DecisionEngine<T>
负责组装评分器并执行选择逻辑。
3. 标准评分器实现 (Standard Scorer Implementations)
DistanceScorer (距离评分)
通用性极强,用于 AI 和 塔防。
HealthScorer (生命值评分)
根据生命值高低进行评分。
TagSynergyScorer (标签协同评分)
用于 Perk 系统,根据标签匹配度评分。
PriorityScorer (优先级评分)
根据实体类型给定固定分数。
🚀 决策系统性能优化示例
本文档展示了如何将时间分片 (Time-Slicing) 和空间划分 (Spatial Partitioning) 策略集成到DecisionEngine 的工作流中,以确保游戏在高并发计算时依然流畅。
1. 时间分片 (Time-Slicing)
在游戏中,有数百个 AI 同时进行索敌或决策是非常常见的。如果所有单位都在同一帧内更新其DecisionEngine,会导致帧率骤降。时间分片通过在多帧之间分配计算负载来解决这个问题。
1.1 IDecisionRequester 接口
定义一个接口,任何需要定期执行决策的 AI 或塔都应实现它。
1.2 DecisionScheduler (决策调度器)
一个单例 (Singleton) 或全局服务,负责管理和调度所有 IDecisionRequester。
1.3 AggroAgent (或其他AI) 集成调度器
2. 空间划分 (Spatial Partitioning)
空间划分系统是提供Candidates 列表给 DecisionEngine 的关键优化。它将整个游戏世界划分为多个区域,从而将“遍历所有敌人”的操作变为“查询局部区域的敌人”。
2.1 概念:Grid 或 QuadTree
- Grid System (网格系统):
- 原理: 将世界地图划分为均匀的网格,每个网格单元存储其中的所有单位引用。
- 查询: 塔或 AI 只需查询其射程覆盖的几个网格单元,就能获得潜在目标列表。
- 适用: 地形平坦、单位分布相对均匀的 2D 或伪 3D 游戏(如标准塔防)。
- QuadTree (四叉树/八叉树):
- 原理: 递归地将空间划分为更小的象限,直到每个象限内的单位数量达到某个阈值。
- 查询: 快速定位到包含查询区域的象限,并只检索这些象限内的单位。
- 适用: 单位分布稀疏、地图广阔、有垂直高度的 3D 游戏。
2.2 伪代码:优化 GetAllAggroTargetsInRadius
这个方法现在应该由一个专门的 空间管理服务 提供,而不是遍历一个大列表。
2.3 AggroAgent 中的应用
当 AggroAgent.PerformDecision 被调用时,它不再依赖一个全局的 _allAggroTargets 列表。