可视化条件树
用事件参数、场景数值、随机结果与常量构建可视化条件门,全程无需编写代码
面向希望为事件加上运行时门控、并偏好可视化编辑器而非手写 if 语句的开发者添加 AND 或 OR 组,可在动作或流程步骤执行前检查运行时数值

上图的分组包含多个比较块,打开比较块选择数值来源和运算符,再通过分组决定这些比较如何组合
概览
可视化条件树是一个逻辑门系统,在执行事件动作之前评估运行时条件
它解决的问题
传统方法(分散的逻辑):
// 逻辑埋藏在脚本中,难以修改
if (damageInfo.amount > 20 &&
(damageInfo.isCritical || damageInfo.type == DamageType.Fire) &&
playerController.Health < 50 &&
gameManager.IsInCombat()) {
// 执行动作
}
可视化方法:

构建示例
事件:OnPlayerDamaged
事件类型:
GameObjectDamageInfoGameEvent(GameObject sender)PlayerStatsDamageInfoGameEvent(自定义sender)
数据结构:
// 伤害类型枚举
public enum DamageType {
Physical,
Fire,
Void
}
// 伤害信息有效载荷
public class DamageInfo {
public float amount; // 伤害值
public bool isCritical; // 暴击标志
public DamageType type; // 伤害类型
public Vector3 hitPoint; // 撞击位置
public string attacker; // 攻击者名称
}
// 自定义sender类型(GameObject的替代方案)
public class PlayerStats {
public string playerName;
public int level;
public int factionId;
}
目标:在特定条件下,当玩家受到重大伤害时触发警告效果
核心优势
| 功能 | 好处 |
|---|---|
| 🎨 可视化构建 | 设计师无需代码即可创建逻辑 |
| 🚀 AOT 安全运行时 | IL2CPP 使用生成的条件求值器 |
| 🔄 可重用 | 相同条件适用于所有事件动作 |
| 🧪 实时测试 | 在Inspector中调整值,立即查看结果 |
| 🔒 类型安全 | 自动验证类型兼容性 |
树结构
条件树由两种节点类型构建:

组节点
使用AND/OR逻辑组合多个条件
逻辑类型:
- AND逻辑
- OR逻辑
所有子条件必须评估为TRUE
可视化:🟢 绿色边框
用途:"必须满足所有要求"
任何子条件可以评估为TRUE
可视化:🟠 橙色边框
用途:"满足任何要求"
切换逻辑:点击逻辑按钮(AND/OR)进行切换
嵌套:组可以包含其他组——构建无限深度的复杂逻辑
添加节点:在每个组上使用**+ 条件或+ 组**按钮
比较节点
比较节点是事件逻辑的基本构建块
每个节点执行单个布尔检查(True/False)以确定事件动作是否应该继续
比较的结构
每个节点遵循标准的三部分结构,使复杂逻辑一目了然
[ 🟦 源(左操作数)] [ 运算符 ] [ 🟧 目标(右操作数)]
实际示例: 想象一个只有在命中足够强大时才触发的事件: Argument.amount > 20.0
- 🔍 源: Argument.amount —
SingleGameEvent传递的原始伤害值 - 📐 运算符: > — 逻辑规则(大于)
- 🎯 目标: 20.0 — 要比较的常量阈值或另一个变量
查看模式
编辑器UI适应您的需求,平衡可读性与精确控制
| 查看模式 | 可视化样式 | 最适合... |
|---|---|---|
| 📖 折叠 | 摘要文本(例如,Health < 50) | 快速概览复杂逻辑链 |
| 🛠️ 展开 | 详细编辑器(下拉菜单、字段) | 修改特定参数和源 |
只需点击任何比较块即可在这两种视图之间切换
这允许您保持工作区整洁,同时保留深入设置的能力
结构配置
📌 源
- 事件参数
- 场景类型
- 随机类型
事件参数(数据有效载荷)
Argument系统允许您深入事件的数据有效载荷以提取特定值用于条件和动作
数据访问专属于类型化事件:GameEvent<T> 或 GameEvent<TSender, TArgs>
单参数事件
签名: DamageInfoGameEvent
当事件携带单个对象时,您可以访问对象本身或其任何公共成员
数据结构示例:
📦 (this Argument) ➔ 完整的DamageInfo对象
├── 🔢 amount ➔ float
├── ✅ isCritical ➔ bool
├── 🏷️ type ➔ DamageType(枚举)
├── 📍 hitPoint ➔ Vector3
└── 👤 attacker ➔ string
Sender事件(上下文感知)
Sender事件为数据访问提供两个不同的根:Sender(谁)和Argument(什么)
情况A:GameObject Sender
签名: GameObjectDamageInfoGameEvent
| 根 | 路径示例 | 数据类型 |
|---|---|---|
| Sender | Sender.Transform.position | Vector3 |
| Argument | Argument.amount | float |
可视化层次结构:
👥 Sender
├── 🆔 tag ➔ string
├── 🟢 activeSelf ➔ bool
└── 📐 Transform
├── 📍 position ➔ Vector3
└── 📂 childCount➔ int
🔢 Argument
├── 🔢 amount ➔ float
└── ✅ isCritical ➔ bool
情况B:自定义C# Sender(高级)
签名: PlayerStatsDamageInfoGameEvent
🚀 为什么它特别: 与传统系统不同,您不受GameObject的限制
使用任何纯C#类作为sender,实现解耦的逻辑优先架构
- 💎 纯逻辑 — 适用于非MonoBehaviour类
- 🌐 网络就绪 — 适用于PlayerData或NetworkAgent同步
- 🤖 AI代理 — 跟踪内部状态而无需场景依赖
深度属性访问
**精确导航
** 使用高性能反射导航最多5层深的嵌套结构
示例:方向检查
- 路径: Argument.hitPoint.normalized.x
- 条件: > 0.5
- 结果: 🎯 "命中来自右侧"
面包屑逻辑: Argument (DamageInfo) ➔ hitPoint (Vector3) ➔ normalized (Vector3) ➔ x (float)
支持的类型
系统自动支持以下类型:
| 类别 | 支持的类型 |
|---|---|
| 基础类型 | int、float、double、long、bool、string |
| 数学 | Vector2、Vector3、Quaternion、Color |
| Unity | GameObject、Transform、Component引用 |
| 逻辑 | 枚举(带下拉菜单)、[Serializable] C#类 |
[Serializable]类中的任何公共字段或属性都会自动在深度链接选择器中公开
场景类型
从场景中的GameObject或Component访问运行时数据
使用方法
步骤1:从层级视图将GameObject或Component拖到对象字段
步骤2:点击"选择属性..."浏览可用成员
GameObject示例
拖动PlayerController GameObject:
📦 GameObject(实例)
├─ 📦 (this GameObject) ➔ 引用本身
├─ ✅ activeSelf ➔ bool
├─ 🔤 tag ➔ string
└─ 🔢 layer ➔ int
📐 Transform(组件)
├─ 📍 position ➔ Vector3
├─ 📏 localScale ➔ Vector3
└─ 📂 childCount ➔ int
🧩 PlayerController(脚本)
├─ 🔢 Health ➔ float
├─ 🛡️ Shield ➔ float
├─ 🏅 Level ➔ int
├─ ✅ HasFireResistance ➔ bool
│
├─ ⚡ IsInDangerZone() ➔ bool(方法)
└─ ⚡ IsCriticallyWounded() ➔ bool(方法)
用法:
Player.Health < 50 → 生命值检查
Player.Level >= 10 → 等级要求
Player.IsInDangerZone() == true → 通过方法进行复杂检查
Bool方法支持
返回bool的零参数方法出现在下拉菜单中!
示例:
// 在您的组件中
public bool IsInDangerZone() {
return Health < 20 && Shield == 0 && !IsInvincible;
}
在条件树中:
Player.IsInDangerZone() == true
这将复杂逻辑封装在单个方法调用中,而不是可视化构建它
Component示例
拖动GameManager Component:
🏛️ GameManager(全局系统)
├─ 🔄 CurrentState ➔ GameState(枚举)
├─ 🌊 CurrentWave ➔ int
├─ 🏅 DifficultyLevel ➔ int
│
├─ ⚡ IsInCombat() ➔ bool(方法)
└─ ⚡ IsHardMode() ➔ bool(方法)
用法:
GameManager.CurrentState == Playing
GameManager.IsInCombat() == true
GameManager.DifficultyLevel >= 3
重要限制
⚠️ 场景类型要求对象在场景加载时存在
✅ 有效:场景层级中的对象(在初始化时存在)
❌ 失败:运行时实例化的对象(尚不存在)
解决方案:对运行时对象使用事件参数
📌 运算符
可用运算符
数值(6个)
用于数字(int、float、double、long):
| 运算符 | 符号 | 示例 |
|---|---|---|
| 等于 | == | Health == 100 |
| 不等于 | != | Health != 0 |
| 大于 | > | Damage > 20 |
| 小于 | < | Health < 50 |
| 大于或等于 | >= | Level >= 10 |
| 小于或等于 | <= | Shield <= 0 |
自动转换:兼容的数值类型自动转换(int ↔ float)
字符串(4个)
用于文本值:
| 运算符 | 符号 | 示例 |
|---|---|---|
| 等于 | == | Name == "Hero" |
| 不等于 | != | Tag != "Enemy" |
| 以...开头 | Starts With | Name Starts With "Player_" |
| 以...结尾 | Ends With | File Ends With ".png" |
| 包含 | Contains (⊃) | Message Contains "error" |
⚠️ 区分大小写:"Hero" ≠ "hero"
枚举支持
完全枚举支持,带下拉选择!
示例:
public enum DamageType { Physical, Fire, Void }
在条件树中:
源:Argument.type(DamageType)
运算符:==
目标:DamageType.Fire(下拉菜单显示Physical/Fire/Void)
使用列表:
Argument.type In List [Fire, Void]
结果:如果type是DamageType.Fire或DamageType.Void,则为TRUE
支持的运算符:==、!=、In List (∈)
集合(1个)
检查列表/数组成员资格:
| 运算符 | 符号 | 目的 |
|---|---|---|
| 在列表中 | ∈ | 检查值是否存在于列表中 |
结构:
源:单个值
运算符:在列表中(∈)
目标:列表/数组(匹配类型)
示例:
Argument.attacker In List ["Dragon", "Demon", "Lich"]
Player.Level In List [10, 20, 30, 40, 50]
Argument.type In List [Fire, Void]
📌 目标
类型验证
系统自动验证类型兼容性
有效比较:
✅ int == int
✅ float > int(自动转换)
✅ string Contains string
✅ DamageType == Fire(枚举)
✅ int In List<int>
无效比较:
❌ string > int(不兼容类型)
❌ bool Contains string(无意义)
❌ float In List<string>(类型不匹配)
视觉反馈:不兼容类型上的红色轮廓+警告文本
Bool方法 vs 可视化树
两种构建条件的方法——何时使用每种?
方法1:Bool方法
最适合:复杂的多步逻辑
示例:
public bool IsInDangerZone() {
bool lowHealth = Health < 20;
bool noShield = Shield == 0;
bool hasEnemies = Physics.OverlapSphere(
transform.position, 10f, enemyLayer
).Length > 0;
return lowHealth && noShield && hasEnemies;
}
在树中:Player.IsInDangerZone() == true
优点:
- 封装复杂性
- 可以使用Physics、raycast
- 单元可测试
- 代码可重用
缺点:
- 需要C#编码
- 设计师无法修改
方法2:可视化树
最适合:设计师应该控制的简单检查
示例:
优点:
- 无需编码
- 设计师友好
- 可视化表示
- 快速迭代
缺点:
- 无法使用Physics/算法
- 大型树变得复杂
混合方法(推荐)
结合两者以获得最佳结果:
指南:
- 可视化树:阈值、枚举、简单属性
- Bool方法:Physics查询、复杂算法、跨系统检查
拖放重新排序
更改执行顺序:拖动任何条件左边缘的句柄(☰)
为什么顺序很重要:
AND组:顺序不影响结果(所有必须通过)
OR组:顺序影响短路评估(在第一个TRUE处停止)
优化示例:
❌ 慢:
OR组
├─ ExpensivePhysicsCheck() ← 首先运行(慢!)
└─ SimpleBoolCheck ← 可能永远不运行
✅ 快:
OR组
├─ SimpleBoolCheck ← 首先运行(快!)
└─ ExpensivePhysicsCheck() ← 仅在需要时
在OR组中首先放置廉价检查以获得更好的性能
性能
烘焙与执行过程
编辑器烘焙
可视化树 → 稳定条件 ID → 生成求值器注册表
运行时执行:
事件触发 → 查找已烘焙求值器 → 返回 TRUE/FALSE
条件执行与构建检查
生成注册表:Editor 为条件生成稳定 ID,并在 Player Build 前生成求值器注册源码
构建检查:条件烘焙数据缺失或过期时,Player Build 停止,并列出需要重新烘焙的资产与属性
未烘焙条件:Mono 与 Editor 可直接求值,IL2CPP Player 会报告错误并返回 false
树管理
-
启用/禁用:切换复选框以绕过所有条件(始终为TRUE)
-
重置树:点击"重置树"按钮清除所有节点并重新开始
-
折叠/展开:点击比较块在摘要和详细视图之间切换
故障排除
条件始终返回False
检查清单:
- ✅ "启用条件"切换是否已勾选?
- ✅ 是否有红色类型不匹配警告?
- ✅ 场景类型引用是否仍然有效(未销毁)?
- ✅ bool方法是否返回预期值?(添加Debug.Log)
属性不在下拉菜单中
对于事件参数:
- 必须是公共字段或属性
- 必须是受支持的类型
对于场景类型:
- GameObject必须在编辑器时存在于场景中
- Component必须启用
- 属性必须是公共的
- 方法必须:返回bool、零参数、公共、实例(非静态)
对于运行时对象:使用事件参数而不是场景类型
更改未保存
常见原因:
- 打开了多个行为窗口(关闭重复项)
- 编辑期间脚本编译(等待完成)
- Unity未应用SerializedProperty更改(关闭前等待)
使用位置
Game Event Behavior 的条件控制管理器绑定的动作
事件触发 → 检查条件 → 执行/跳过动作
FlowGraph 节点的条件控制进入该节点时的执行
流程到达节点 → 检查条件 → 执行/跳过节点
Receiver 动作条件
GES 2.3.0 起,每个 Receiver 可以在 Inspector 中检查自己的条件,失败时只跳过该组件的 On Triggered 动作,同一事件上的其他监听器仍按各自的规则执行
- 选择挂有 Receiver 的 GameObject,在 Event Reference 中指定 Game Event,再到 Event Action 添加 UnityEvent 回调
- 在 Action Condition 的 Execution Gate 下勾选 Enable Conditions,用 + Condition 添加比较,或用 + Group 添加分组
- 点击分组的 AND 或 OR 切换逻辑,点击彩色条件块可编辑 Source Value、Operator 和 Target Value,拖动行首的手柄调整组内顺序
Event Action 使用 Unity 原生回调列表,标题显示参数类型,有已绑定的动作时可用 Clear All 一次清除
| Receiver | 条件树可以读取 |
|---|---|
无参数 GameEventReceiver | 场景数值、常量和随机值 |
单参数 Receiver,例如 Int32GameEventReceiver | 场景数值、常量、随机值,以及事件参数和参数成员 |
| Sender + Args Receiver | 场景数值、常量、随机值,以及发送者、参数及它们的成员 |
将 Int32GameEventReceiver 的条件设为 Event Argument > 5,分别用 3 和 8 触发事件,启用条件的组件只对 8 执行动作,另一个未启用条件的 Receiver 会响应两次
条件默认关闭,原有 Receiver 在升级后沿用当前动作配置,启用后空条件树也会通过检查,带参数的生成组件和自定义 Receiver 继承同样的运行时条件,无需重新生成脚本
保存场景或 Prefab 后,在 IL2CPP 构建前运行 Bake AOT Conditions,Receiver 条件也会参与缺失和过期检查



