13 运行时API:代码工作流
📋 概述
之前的示例(01-11)演示了可视化工作流——在检查器中绑定监听器、在行为窗口中配置条件以及可视化构建流程图。这种方法非常适合设计师和快速原型开发。然而,程序员通常更喜欢完全的代码控制,用于复杂系统、动态行为或可视化工具变得受限时。
Demo 13证明了一个关键的架构原则: 您在可视化工作流中看到的每个功能都有一个完整的、类型安全的C# API。本示例重新访问所有11个先前的场景,移除所有检查器绑定和图表配置,用运行时代码替换它们。
- 如何以编程方式注册/移除监听器(
AddListener、RemoveListener) - 动态优先级控制(
AddPriorityListener) - 运行时条件注册(
AddConditionalListener) - 调度API(
RaiseDelayed、RaiseRepeating、Cancel) - 在代码中构建流程图(
AddTriggerEvent、AddChainEvent) - 持久化监听器管理(
AddPersistentListener) - 生命周期管理(
OnEnable、OnDisable、清理模式)
🎬 示例结构
📁 Assets/TinyGiants/GameEventSystem/Demo/13_RuntimeAPI/
│
├── 📁 01_VoidEvent ➔ 🔘 [ 基于代码的void事件绑定 ]
├── 📁 02_BasicTypesEvent ➔ 🔢 [ 泛型事件注册 ]
├── 📁 03_CustomTypeEvent ➔ 💎 [ 自定义类绑定 ]
├── 📁 04_CustomSenderTypeEvent ➔ 👥 [ 双泛型监听器 ]
│
├── 📁 05_PriorityEvent ➔ 🥇 [ 代码中的优先级管理 ]
├── 📁 06_ConditionalEvent ➔ 🛡️ [ 基于谓词的过滤 ]
├── 📁 07_DelayedEvent ➔ ⏱️ [ 调度和取消 ]
├── 📁 08_RepeatingEvent ➔ 🔄 [ 循环管理和回调 ]
│
├── 📁 09_PersistentEvent ➔ 🛡️ [ 跨场景监听器存活 ]
├── 📁 10_TriggerEvent ➔ 🕸️ [ 并行图表构建 ]
├── 📁 11_ChainEvent ➔ ⛓️ [ 顺序管道构建 ]
│
├── 📁 12_DynamicDatabase ➔ 🔌 [ 运行时注册/注销/启用数据库 ]
└── 📁 13_GameEventQuery ➔ 🔍 [ 按 GUID / 名称 / 分类 / 类型查找事件 ]
与01-11的关键区别:
- 场景设置: 相同(相同的炮塔、目标、UI按钮)
- 可视化配置: ❌ 移除(无行为窗口配置、无流程图)
- 代码实现: 所有逻辑移至
OnEnable/OnDisable/生命周期方法
🔄 可视化与代码范式转变
| 功能 | 可视化工作流(01-11) | 代码工作流(Demo 13) |
|---|---|---|
| 监听器绑定 | 在行为窗口中拖放 | OnEnable 中的 event.AddListener(Method) |
| 条件逻辑 | 检查器中的条件树 | event.AddConditionalListener(Method, Predicate) |
| 执行优先级 | 在行为窗口中拖动重新排序 | event.AddPriorityListener(Method, priority) |
| 延迟/重复 | 行为窗口中的延迟节点 | event.RaiseDelayed(seconds)、event.RaiseRepeating(interval, count) |
| 流程图 | 流程图窗口中的可视化连接 | event.AddTriggerEvent(target, ...)、event.AddChainEvent(target, ...) |
| 清理 | 游戏对象销毁时自动 | OnDisable/OnDestroy 中手动 |
手动注册 = 手动清理。OnEnable 中的每个 AddListener 必须在 OnDisable 中有对应的 RemoveListener。清理失败会导致:
- 内存泄漏
- 监听器重复执行
- 在已销毁对象上执行监听器(NullReferenceException)
📚 API场景
01 Void事件:基本注册
可视化 → 代码转换:
- ❌ 检查器:将
OnEventReceived拖到行为窗口 - ✅ 代码:在
OnEnable中调用AddListener
RuntimeAPI_VoidEventRaiser.cs:
using TinyGiants.GameEventSystem.Runtime;
public class RuntimeAPI_VoidEventRaiser : MonoBehaviour
{
[GameEventDropdown]
public GameEvent voidEvent; // ← 仍使用资产引用
public void RaiseBasicEvent()
{
if (voidEvent) voidEvent.Raise(); // ← 与可视化工作流相同
}
}
RuntimeAPI_VoidEventReceiver.cs:
using TinyGiants.GameEventSystem.Runtime;
public class RuntimeAPI_VoidEventReceiver : MonoBehaviour
{
[GameEventDropdown]
public GameEvent voidEvent;
[SerializeField] private Rigidbody targetRigidbody;
// ✅ 注册:启用时
private void OnEnable()
{
voidEvent.AddListener(OnEventReceived); // ← 替换检查器绑定
}
// ✅ 清理:禁用时
private void OnDisable()
{
voidEvent.RemoveListener(OnEventReceived); // ← 强制清理
}
// 监听器方法(与可视化工作流相同)
public void OnEventReceived()
{
// 应用物理...
targetRigidbody.AddForce(Vector3.up * 5f, ForceMode.Impulse);
}
}
要点:
- 🎯 事件资产: 仍通过
[GameEventDropdown]引用 - 🔗 注册:
OnEnable中的AddListener(MethodName) - 🧹 清理:
OnDisable中的RemoveListener(MethodName) - ⚡ 签名: 方法必须匹配事件类型(
GameEvent为void)
02 基本类型:泛型注册
演示: 泛型事件的类型推断
RuntimeAPI_BasicTypesEventRaiser.cs:
[GameEventDropdown] public StringGameEvent messageEvent;
[GameEventDropdown] public Vector3GameEvent movementEvent;
[GameEventDropdown] public GameObjectGameEvent spawnEvent;
[GameEventDropdown] public MaterialGameEvent changeMaterialEvent;
public void RaiseString()
{
messageEvent.Raise("Hello World"); // ← 从事件推断类型
}
public void RaiseVector3()
{
movementEvent.Raise(new Vector3(0, 2, 0));
}
RuntimeAPI_BasicTypesEventReceiver.cs:
private void OnEnable()
{
// 编译器从方法签名推断 <string>、<Vector3> 等
messageEvent.AddListener(OnMessageReceived); // void(string)
movementEvent.AddListener(OnMoveReceived); // void(Vector3)
spawnEvent.AddListener(OnSpawnReceived); // void(GameObject)
changeMaterialEvent.AddListener(OnMaterialReceived); // void(Material)
}
private void OnDisable()
{
messageEvent.RemoveListener(OnMessageReceived);
movementEvent.RemoveListener(OnMoveReceived);
spawnEvent.RemoveListener(OnSpawnReceived);
changeMaterialEvent.RemoveListener(OnMaterialReceived);
}
public void OnMessageReceived(string msg) { /* ... */ }
public void OnMoveReceived(Vector3 pos) { /* ... */ }
public void OnSpawnReceived(GameObject prefab) { /* ... */ }
public void OnMaterialReceived(Material mat) { /* ... */ }
要点:
- ✅ 类型安全: 编译器强制签名匹配
- ✅ 自动推断: 无需手动类型规范
- ⚠️ 不匹配错误:
void(int)无法绑定到StringGameEvent
03 自定义类型:复杂数据绑定
演示: 自动生成的泛型类
RuntimeAPI_CustomTypeEventRaiser.cs:
[GameEventDropdown] public DamageInfoGameEvent physicalDamageEvent;
[GameEventDropdown] public DamageInfoGameEvent fireDamageEvent;
[GameEventDropdown] public DamageInfoGameEvent criticalStrikeEvent;
public void DealPhysicalDamage()
{
DamageInfo info = new DamageInfo(10f, false, DamageType.Physical, hitPoint, "Player01");
physicalDamageEvent.Raise(info); // ← 自定义类作为参数
}
RuntimeAPI_CustomTypeEventReceiver.cs:
private void OnEnable()
{
// 将多个事件绑定到同一处理器
physicalDamageEvent.AddListener(OnDamageReceived);
fireDamageEvent.AddListener(OnDamageReceived);
criticalStrikeEvent.AddListener(OnDamageReceived);
}
private void OnDisable()
{
physicalDamageEvent.RemoveListener(OnDamageReceived);
fireDamageEvent.RemoveListener(OnDamageReceived);
criticalStrikeEvent.RemoveListener(OnDamageReceived);
}
public void OnDamageReceived(DamageInfo info)
{
// 解析自定义类字段
float damage = info.amount;
DamageType type = info.type;
bool isCrit = info.isCritical;
// 基于数据应用逻辑...
}
要点:
- 📦 自动生成: 插件创建
DamageInfoGameEvent类 - 🔗 多重绑定: 同一方法可以监听多个事件
- ⚡ 数据访问: 完全访问自定义类属性
04 自定义发送者:双泛型监听器
演示: 访问事件源上下文
RuntimeAPI_CustomSenderTypeEventRaiser.cs:
// 物理发送者:GameObject
[GameEventDropdown] public GameObjectDamageInfoGameEvent turretEvent;
// 逻辑发送者:自定义类
[GameEventDropdown] public PlayerStatsDamageInfoGameEvent systemEvent;
public void RaiseTurretDamage()
{
DamageInfo info = new DamageInfo(15f, false, DamageType.Physical, hitPoint, "Turret");
turretEvent.Raise(this.gameObject, info); // ← 将发送者作为第一个参数传递
}
public void RaiseSystemDamage()
{
PlayerStats admin = new PlayerStats("DragonSlayer_99", 99, 1);
DamageInfo info = new DamageInfo(50f, true, DamageType.Void, hitPoint, "Admin");
systemEvent.Raise(admin, info); // ← 自定义类作为发送者
}
RuntimeAPI_CustomSenderTypeEventReceiver.cs:
private void OnEnable()
{
turretEvent.AddListener(OnTurretAttackReceived); // (GameObject, DamageInfo)
systemEvent.AddListener(OnSystemAttackReceived); // (PlayerStats, DamageInfo)
}
private void OnDisable()
{
turretEvent.RemoveListener(OnTurretAttackReceived);
systemEvent.RemoveListener(OnSystemAttackReceived);
}
// 签名:void(GameObject, DamageInfo)
public void OnTurretAttackReceived(GameObject sender, DamageInfo args)
{
Vector3 attackerPos = sender.transform.position; // ← 访问发送者GameObject
// 响应物理攻击者...
}
// 签名:void(PlayerStats, DamageInfo)
public void OnSystemAttackReceived(PlayerStats sender, DamageInfo args)
{
string attackerName = sender.playerName; // ← 访问发送者数据
int factionId = sender.factionId;
// 响应逻辑攻击者...
}
要点:
- 🎯 上下文感知: 监听器知道谁触发了事件
- 🔀 灵活的发送者: GameObject或自定义类
- ⚡ 签名匹配: 方法参数必须匹配事件泛型
05 优先级:执行顺序控制
可视化 → 代码转换:
- ❌ 检查器:在行为窗口中拖动重新排序监听器
- ✅ 代码:指定
priority参数(越高 = 越早)
RuntimeAPI_PriorityEventReceiver.cs:
[GameEventDropdown] public GameObjectDamageInfoGameEvent orderedHitEvent;
[GameEventDropdown] public GameObjectDamageInfoGameEvent chaoticHitEvent;
private void OnEnable()
{
// ✅ 有序:高优先级首先执行
orderedHitEvent.AddPriorityListener(ActivateBuff, priority: 100); // 第1个运行
orderedHitEvent.AddPriorityListener(ResolveHit, priority: 50); // 第2个运行
// ❌ 混乱:故意错误的顺序
chaoticHitEvent.AddPriorityListener(ResolveHit, priority: 80); // 第1个运行(太早!)
chaoticHitEvent.AddPriorityListener(ActivateBuff, priority: 40); // 第2个运行(太晚!)
}
private void OnDisable()
{
// 必须专门移除优先级监听器
orderedHitEvent.RemovePriorityListener(ActivateBuff);
orderedHitEvent.RemovePriorityListener(ResolveHit);
chaoticHitEvent.RemovePriorityListener(ResolveHit);
chaoticHitEvent.RemovePriorityListener(ActivateBuff);
}
public void ActivateBuff(GameObject sender, DamageInfo args)
{
_isBuffActive = true; // ← 必须在ResolveHit之前运行
}
public void ResolveHit(GameObject sender, DamageInfo args)
{
float damage = _isBuffActive ? args.amount * 5f : args.amount; // ← 检查增益状态
}
要点:
- 🔢 优先级值: 数字越大 = 执行越早
- ⚠️ 顺序重要:
ActivateBuff(100) → ResolveHit(50)= 暴击 - ❌ 错误顺序:
ResolveHit(80) → ActivateBuff(40)= 普通攻击 - 🧹 清理: 使用
RemovePriorityListener(而非RemoveListener)
06 条件:基于谓词的过滤
可视化 → 代码转换:
- ❌ 检查器:行为窗口中的可视化条件树
- ✅ 代码:将谓词函数传递给
AddConditionalListener
RuntimeAPI_ConditionalEventReceiver.cs:
[GameEventDropdown] public AccessCardGameEvent requestAccessEvent;
private void OnEnable()
{
// 使用条件函数注册
// 仅当CanOpen返回true时调用OpenVault
requestAccessEvent.AddConditionalListener(OpenVault, CanOpen);
}
private void OnDisable()
{
requestAccessEvent.RemoveConditionalListener(OpenVault);
}
// ✅ 条件函数(谓词)
// 替换可视化条件树
public bool CanOpen(AccessCard card)
{
return securityGrid.IsPowerOn && (
card.securityLevel >= 4 ||
departments.Contains(card.department) ||
(card.securityLevel >= 1 && Random.Range(0, 100) > 70)
);
}
// ✅ 动作(仅在条件通过时执行)
public void OpenVault(AccessCard card)
{
// 假定所有条件满足
Debug.Log($"ACCESS GRANTED to {card.holderName}");
StartCoroutine(OpenDoorSequence());
}
要点:
- ✅ 谓词函数: 返回
bool,接受事件参数 - 🔒 守门员: 仅在谓词返回
true时运行动作 - 🧹 清理: 使用
RemoveConditionalListener(而非RemoveListener) - ⚡ 评估: 谓词在动作方法之前运行
07 延迟:调度和取消
可视化 → 代码转换:
- ❌ 行为:检查器中"动作延迟 = 5.0秒"
- ✅ 代码:
event.RaiseDelayed(5f)返回ScheduleHandle
RuntimeAPI_DelayedEventRaiser.cs:
[GameEventDropdown] public GameEvent explodeEvent;
private ScheduleHandle _handle; // ← 跟踪已调度的任务
public void ArmBomb()
{
// 5秒后调度事件
_handle = explodeEvent.RaiseDelayed(5f); // ← 返回句柄
Debug.Log("Bomb armed! 5 seconds to defuse...");
}
public void CutRedWire() => ProcessCut("Red");
public void CutGreenWire() => ProcessCut("Green");
private void ProcessCut(string color)
{
if (color == _safeWireColor)
{
// 取消已调度的爆炸
explodeEvent.CancelDelayed(_handle); // ← 使用句柄取消
Debug.Log("DEFUSED! Event cancelled.");
}
else
{
Debug.LogWarning("Wrong wire! Clock still ticking...");
}
}
要点:
- ⏱️ 调度:
RaiseDelayed(seconds)将事件加入队列 - 📍 句柄: 存储返回值以便稍后取消
- 🛑 取消:
CancelDelayed(handle)从队列中移除 - ⚠️ 时序: 如果未取消,事件在延迟后执行
08 重复:循环管理和回调
可视化 → 代码转换:
- ❌ 行为:检查器中"重复间隔 = 1.0秒,重复次数 = 5"
- ✅ 代码:带回调的
event.RaiseRepeating(interval, count)
RuntimeAPI_RepeatingEventRaiser.cs:
[GameEventDropdown] public GameEvent finitePulseEvent;
private ScheduleHandle _handle;
public void ActivateBeacon()
{
// 启动循环:1秒间隔,5次
_handle = finitePulseEvent.RaiseRepeating(interval: 1.0f, count: 5);
// ✅ 钩子:每次迭代触发
_handle.OnStep += (currentCount) =>
{
Debug.Log($"Pulse #{currentCount} emitted");
};
// ✅ 钩子:循环自然完成时触发
_handle.OnCompleted += () =>
{
Debug.Log("Beacon sequence completed");
UpdateUI("IDLE");
};
// ✅ 钩子:手动取消时触发
_handle.OnCancelled += () =>
{
Debug.Log("Beacon interrupted");
UpdateUI("ABORTED");
};
}
public void StopSignal()
{
if (_handle != null)
{
finitePulseEvent.CancelRepeating(_handle); // ← 停止循环
}
}
要点:
- 🔁 有限循环:
RaiseRepeating(1.0f, 5)= 1秒间隔5次脉冲 - ∞ 无限循环:
RaiseRepeating(1.0f, -1)= 无限直到取消 - 📡 回调:
OnStep、OnCompleted、OnCancelled事件 - 🛑 手动停止: 无限循环使用
CancelRepeating(handle)
09 持久化:跨场景监听器存活
可视化 → 代码转换:
- ❌ 检查器:在行为窗口中勾选"持久化事件"
- ✅ 代码:
Awake中的AddPersistentListener+DontDestroyOnLoad
RuntimeAPI_PersistentEventReceiver.cs:
[GameEventDropdown] public GameEvent fireAEvent; // 持久化
[GameEventDropdown] public GameEvent fireBEvent; // 标准
private void Awake()
{
DontDestroyOnLoad(gameObject); // ← 场景加载中存活
// ✅ 持久化监听器(场景重新加载中存活)
fireAEvent.AddPersistentListener(OnFireCommandA);
}
private void OnDestroy()
{
// 必须手动移除持久化监听器
fireAEvent.RemovePersistentListener(OnFireCommandA);
}
private void OnEnable()
{
// ❌ 标准监听器(随场景消亡)
fireBEvent.AddListener(OnFireCommandB);
}
private void OnDisable()
{
fireBEvent.RemoveListener(OnFireCommandB);
}
public void OnFireCommandA()
{
Debug.Log("Persistent listener survived scene reload");
}
public void OnFireCommandB()
{
Debug.Log("Standard listener (will break after reload)");
}
要点:
- 🧬 单例模式:
DontDestroyOnLoad+ 持久化监听器 - ✅ 在重新加载中存活:
AddPersistentListener绑定到全局注册表 - ❌ 标准死亡:
AddListener绑定随场景销毁 - 🧹 清理: 持久化使用
OnDestroy,标准使用OnDisable
10 触发器事件:在代码中构建并行图表
可视化 → 代码转换:
- ❌ 流程图:可视化节点和连接
- ✅ 代码:
OnEnable中的AddTriggerEvent(target, ...)
RuntimeAPI_TriggerEventRaiser.cs:
[GameEventDropdown] public GameObjectDamageInfoGameEvent onCommand; // 根
[GameEventDropdown] public GameObjectDamageInfoGameEvent onActiveBuff; // 分支A
[GameEventDropdown] public GameObjectDamageInfoGameEvent onTurretFire; // 分支B
[GameEventDropdown] public DamageInfoGameEvent onHoloData; // 分支C(类型转换)
[GameEventDropdown] public GameEvent onGlobalAlarm; // 分支D(void)
private TriggerHandle _buffAHandle;
private TriggerHandle _fireAHandle;
private TriggerHandle _holoHandle;
private TriggerHandle _alarmHandle;
private void OnEnable()
{
// ✅ 在代码中构建并行图表
// 分支A:增益(优先级100,条件)
_buffAHandle = onCommand.AddTriggerEvent(
targetEvent: onActiveBuff,
delay: 0f,
condition: (sender, args) => sender == turretA, // ← 仅炮塔A
passArgument: true,
priority: 100 // ← 高优先级
);
// 分支B:开火(优先级50,条件)
_fireAHandle = onCommand.AddTriggerEvent(
targetEvent: onTurretFire,
delay: 0f,
condition: (sender, args) => sender == turretA,
passArgument: true,
priority: 50 // ← 较低优先级(在增益后运行)
);
// 分支C:全息数据(类型转换,延迟)
_holoHandle = onCommand.AddTriggerEvent(
targetEvent: onHoloData, // ← DamageInfoGameEvent(无发送者)
delay: 1f, // ← 1秒延迟
passArgument: true
);
// 分支D:全局警报(Void转换)
_alarmHandle = onCommand.AddTriggerEvent(
targetEvent: onGlobalAlarm // ← GameEvent(void,无参数)
);
// ✅ 钩子:触发器触发时的回调
_buffAHandle.OnTriggered += () => Debug.Log("Buff triggered via code graph");
}
private void OnDisable()
{
// ✅ 清理:动态触发器强制要求
onCommand.RemoveTriggerEvent(_buffAHandle);
onCommand.RemoveTriggerEvent(_fireAHandle);
onCommand.RemoveTriggerEvent(_holoHandle);
onCommand.RemoveTriggerEvent(_alarmHandle);
}
图表可视化(代码定义):
📡 根:onCommand.Raise(sender, info)
│
├─ 🔱 [ 分支:单元A ] ➔ 🛡️ 守卫:`Sender == Turret_A`
│ ├─ 💎 [优先级:100] ➔ 🛡️ onActiveBuff() ✅ 高优先级同步
│ └─ ⚡ [优先级:50 ] ➔ 🔥 onTurretFire() ✅ 顺序动作
│
├─ 🔱 [ 分支:分析 ] ➔ 🔢 签名:`<DamageInfo>`
│ └─ ⏱️ [ 延迟:1.0秒 ] ➔ 📽️ onHoloData() ✅ 延迟数据中继
│
└─ 🔱 [ 分支:全局 ] ➔ 🔘 签名:`<void>`
└─ 🚀 [ 即时 ] ➔ 🚨 onGlobalAlarm() ✅ 立即信号
要点:
- 🌳 并行执行: 所有分支同时评估
- 🔢 优先级: 控制通过分支内的执行顺序
- ✅ 条件: 谓词函数按发送者/参数过滤
- 🔄 类型转换: 自动参数适配
- 📡 回调: 每个句柄的
OnTriggered事件 - 🧹 清理: 需要
RemoveTriggerEvent(handle)
11 链式事件:在代码中构建顺序管道
可视化 → 代码转换:
- ❌ 流程图:线性节点序列
- ✅ 代码:
OnEnable中的AddChainEvent(target, ...)
RuntimeAPI_ChainEventRaiser.cs:
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnStartSequenceEvent; // 根
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnSystemCheckEvent; // 步骤1
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnChargeEvent; // 步骤2
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnFireEvent; // 步骤3
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnCoolDownEvent; // 步骤4
[GameEventDropdown] public GameObjectDamageInfoGameEvent OnArchiveEvent; // 步骤5
private ChainHandle _checkHandle;
private ChainHandle _chargeHandle;
private ChainHandle _fireHandle;
private ChainHandle _cooldownHandle;
private ChainHandle _archiveHandle;
private void OnEnable()
{
// ✅ 在代码中构建顺序链
// 步骤1:系统检查(条件门)
_checkHandle = OnStartSequenceEvent.AddChainEvent(
targetEvent: OnSystemCheckEvent,
delay: 0f,
duration: 0f,
condition: (sender, args) => chainEventReceiver.IsSafetyCheckPassed, // ← 门
passArgument: true,
waitForCompletion: false
);
// 步骤2:充能(1秒持续时间)
_chargeHandle = OnStartSequenceEvent.AddChainEvent(
targetEvent: OnChargeEvent,
delay: 0f,
duration: 1f, // ← 链在此暂停1秒
passArgument: true
);
// 步骤3:开火(即时)
_fireHandle = OnStartSequenceEvent.AddChainEvent(
targetEvent: OnFireEvent,
passArgument: true
);
// 步骤4:冷却(0.5秒延迟 + 1秒持续时间 + 等待完成)
_cooldownHandle = OnStartSequenceEvent.AddChainEvent(
targetEvent: OnCoolDownEvent,
delay: 0.5f, // ← 前置延迟
duration: 1f, // ← 动作后持续时间
passArgument: true,
waitForCompletion: true // ← 等待接收器协程
);
// 步骤5:归档(参数被阻止)
_archiveHandle = OnStartSequenceEvent.AddChainEvent(
targetEvent: OnArchiveEvent,
passArgument: false // ← 下游接收null/默认值
);
}
private void OnDisable()
{
// ✅ 清理:动态链强制要求
OnStartSequenceEvent.RemoveChainEvent(_checkHandle);
OnStartSequenceEvent.RemoveChainEvent(_chargeHandle);
OnStartSequenceEvent.RemoveChainEvent(_fireHandle);
OnStartSequenceEvent.RemoveChainEvent(_cooldownHandle);
OnStartSequenceEvent.RemoveChainEvent(_archiveHandle);
// 替代方案:OnStartSequenceEvent.RemoveAllChainEvents();
}
管道可视化(代码定义):
🚀 [ 根 ] OnStartSequenceEvent
│
├─ 🛡️ [ 守卫 ] ➔ 安全检查
│ └─► ⚙️ OnSystemCheckEvent ✅ 条件通过
│
├─ ⏱️ [ 地板 ] ➔ 持续时间:1.0秒
│ └─► ⚡ OnChargeEvent ✅ 最小节奏满足
│
├─ 🚀 [ 即时 ] ➔ 立即触发
│ └─► 🔥 OnFireEvent ✅ 已执行
│
├─ ⌛ [ 异步 ] ➔ 延迟:0.5秒 | 持续:1.0秒 | 等待:开
│ └─► ❄️ OnCoolDownEvent ✅ 异步恢复完成
│
└─ 🧹 [ 过滤 ] ➔ 阻止参数
└─► 💾 OnArchiveEvent ✅ 数据已清理并保存
要点:
- 🔗 顺序执行: 步骤一个接一个运行,而非并行
- ✅ 条件门: 失败的条件终止整个链
- ⏱️ 持续时间: 链暂停指定时间
- 🕐 等待完成: 阻塞直到接收器协程完成
- 🔒 参数阻止:
passArgument: false发送默认值 - 🧹 清理:
RemoveChainEvent(handle)或RemoveAllChainEvents()
12 动态数据库:运行时数据库管理
静态 → 动态翻译:
- ❌ 检查器:在场景编辑时预先配置
GameEventManager.Databases列表 - ✅ 代码:运行时调用
RegisterDatabase/UnregisterDatabase/SetDatabaseActive
使用场景: 基于场景的事件加载、DLC / Addressables 内容、按构建配置的功能开关、Mod 支持。
RuntimeAPI_DynamicDatabaseEventRaiser.cs:
using TinyGiants.GES.Runtime;
public class RuntimeAPI_DynamicDatabaseEventRaiser : MonoBehaviour
{
[SerializeField] private GameEventDatabase database; // ← 未预先添加到 Manager
[GameEventDropdown] public GameEvent voidEvent; // 必须属于 `database`
private bool _isRegistered = false;
private bool _isActive = true;
// ✅ 运行时添加数据库
public void ToggleRegister()
{
var mgr = GameEventManager.Instance;
if (_isRegistered)
{
mgr.UnregisterDatabase(database); // 清理绑定 + 运行时回调
_isRegistered = false;
}
else
{
mgr.RegisterDatabase(database); // 激活数据库中的所有事件
_isRegistered = true;
_isActive = true;
}
}
// ✅ 不注销也可切换启用状态
public void ToggleActive()
{
if (!_isRegistered) return;
_isActive = !_isActive;
GameEventManager.Instance.SetDatabaseActive(database, _isActive);
}
public void RaiseEvent()
{
if (voidEvent) voidEvent.Raise(); // 仅当数据库已注册且处于激活状态时才触发
}
}
Register 与 SetActive 的区别:
| 操作 | 效果 | 适用场景 |
|---|---|---|
RegisterDatabase(db) | 添加数据库,创建 EventBinding 条目,订阅运行时回调 | 加载新内容(场景加载、DLC 解锁、Mod 导入) |
UnregisterDatabase(db) | 移除数据库,清理绑定 + 运行时回调 | 卸载内容(场景卸载、Mod 禁用) |
SetDatabaseActive(db, false) | 数据库仍保留注册;Raise 时静默无操作 | 临时门控(暂停菜单、过场动画、管理员模式) |
SetDatabaseActive(db, true) | 恢复事件调用 | 退出临时门控 |
要点:
- 🔌 运行时加载: 通过
Resources.Load/ Addressables 加载GameEventDatabase,然后调用RegisterDatabase - 🛑 Inactive ≠ 未注册: 未激活的数据库仍保留条目,只是跳过事件调用
- 🧹 重复保护:
RegisterDatabase会安全忽略已注册的数据库 - 📖 API 参考:
Runtime/IGameEvent.API.cs中的IGameEventManagerDatabaseAPI
13 事件查询:运行时查找 API
静态 → 动态翻译:
- ❌ 检查器:
[SerializeField] GameEvent myEvent—— 强制 MonoBehaviour,在编辑时硬连线引用 - ✅ 代码:运行时通过
GameEventManager.Instance按 GUID / 名称 / 分类 / 类型查询
使用场景: 非 MonoBehaviour 使用方、存档/读档系统、Addressables 动态加载的内容、插件桥接、网络同步。
查询 API 全景:
| 查询方式 | 方法 | 返回值 |
|---|---|---|
| 按 GUID(唯一) | HasGameEvent(guid) / GetGameEvent(...) / TryGetGameEvent(..., out) | 单个事件或 null |
| 按名称(多个) | HasGameEvents(name, category) / GetGameEvents(name, category) | List<T>(无匹配时为空,永不为 null) |
| 按名称(首个) | GetFirstGameEventByName(...) / TryGetFirstGameEventByName(..., out) | 单个事件或 null |
每个方法都有三种泛型参数重载用于严格类型过滤:
- 非泛型 →
GameEventBase(匹配任意事件类型) <T>→ 仅匹配GameEvent<T><TSender, TArgs>→ 仅匹配GameEvent<TSender, TArgs>
RuntimeAPI_GameEventQueryRaiser.cs:
using TinyGiants.GES.Runtime;
public class RuntimeAPI_GameEventQueryRaiser : MonoBehaviour
{
[SerializeField] private string eventGuid;
[SerializeField] private string eventName = "OnJump";
[SerializeField] private string categoryFilter = "Movement";
// ✅ GUID 查找 —— 精确、对存档/读档安全
public void RaiseByGuid()
{
if (GameEventManager.Instance.TryGetGameEvent(eventGuid, out var evt))
evt.Raise();
}
// ✅ 按名称 —— 首个匹配(若有重名则任意选一)
public void RaiseFirstByName()
{
GameEventManager.Instance.GetFirstGameEventByName(eventName)?.Raise();
}
// ✅ 名称 + 分类 —— 指定分类下的首个匹配
public void RaiseByNameAndCategory()
{
if (GameEventManager.Instance.TryGetFirstGameEventByName(eventName, out var evt, categoryFilter))
evt.Raise();
}
// ✅ 批量 —— 触发活跃数据库中所有名称匹配的事件
public void RaiseAllByName()
{
foreach (var e in GameEventManager.Instance.GetGameEvents(eventName))
e.Raise();
}
// ✅ 类型化查询 —— 仅匹配 GameEvent<int>
public void RaiseIntByName(string name, int value)
{
if (GameEventManager.Instance.TryGetFirstGameEventByName<int>(name, out var intEvt))
intEvt.Raise(value);
}
// ✅ 带 Sender 的查询 —— 仅匹配 GameEvent<TSender, TArgs>
public void RaiseSenderByName(string name, GameObject sender, string msg)
{
if (GameEventManager.Instance.TryGetFirstGameEventByName<GameObject, string>(name, out var evt))
evt.Raise(sender, msg);
}
// ✅ 存在性检查 —— 不触发,仅返回布尔值
public bool DoesEventExist(string guid) =>
GameEventManager.Instance.HasGameEvent(guid);
}
类型过滤示例:
数据库内容:
OnJump(void, Movement)OnJump(void, UI)OnScoreChanged(int, Gameplay)
查询结果:
mgr.GetGameEvents("OnJump").Count; // → 2(两个 void 事件)
mgr.GetGameEvents<int>("OnJump").Count; // → 0(严格过滤:OnJump 是 void,不是 int)
mgr.GetGameEvents("OnJump", "Movement").Count; // → 1(仅 Movement 分类)
要点:
- 🆔 GUID 唯一: 用于精确、对加载安全的引用(存档/读档、网络同步、Mod 配置)
- 📛 名称可重复: 复数方法返回
List<T>;First/Try 方法返回遍历时的首个匹配 - 🎯 严格类型过滤:
is GameEvent<T>模式匹配 —— 不会跨类型误中 - 📁 仅活跃数据库: 查询范围限于已注册且激活的数据库
- ⚡ 非 MonoBehaviour 友好: 可从纯 C# 类、静态工具、服务层调用
- 📖 API 参考:
Runtime/IGameEvent.API.cs中的IGameEventManagerDatabaseAPI
🔑 API参考
监听器注册
| 方法 | 用例 | 清理方法 |
|---|---|---|
AddListener(method) | 标准绑定 | RemoveListener(method) |
AddPriorityListener(method, priority) | 执行顺序控制 | RemovePriorityListener(method) |
AddConditionalListener(method, predicate) | 基于谓词过滤 | RemoveConditionalListener(method) |
AddPersistentListener(method) | 跨场景存活 | RemovePersistentListener(method) |
事件触发
| 方法 | 用例 | 返回 |
|---|---|---|
Raise() | 立即执行 | void |
Raise(arg) | 带单个参数 | void |
Raise(sender, arg) | 带发送者上下文 | void |
RaiseDelayed(seconds) | 计划执行 | ScheduleHandle |
RaiseRepeating(interval, count) | 循环执行 | ScheduleHandle |
调度管理
| 方法 | 用例 |
|---|---|
CancelDelayed(handle) | 停止待执行的延迟事件 |
CancelRepeating(handle) | 停止活动循环 |
handle.OnStep | 循环迭代回调 |
handle.OnCompleted | 循环完成回调 |
handle.OnCancelled | 取消回调 |
流程图构建
| 方法 | 用例 | 返回 |
|---|---|---|
AddTriggerEvent(target, ...) | 并行分支 | TriggerHandle |
RemoveTriggerEvent(handle) | 移除分支 | void |
AddChainEvent(target, ...) | 顺序步骤 | ChainHandle |
RemoveChainEvent(handle) | 移除步骤 | void |
RemoveAllChainEvents() | 清除所有步骤 | void |
数据库管理(GameEventManager.Instance)
| 方法 | 用例 |
|---|---|
RegisterDatabase(db) | 运行时添加数据库及其事件 |
UnregisterDatabase(db) | 移除数据库并清理绑定 |
SetDatabaseActive(db, bool) | 不注销仅切换事件调用开关 |
运行时查询(GameEventManager.Instance)
| 方法 | 用例 |
|---|---|
HasGameEvent(guid) | 检查指定 GUID 的事件是否存在 |
GetGameEvent(guid)(+ <T> / <TSender, TArgs>) | 按 GUID 获取类型化事件 |
TryGetGameEvent(guid, out)(+ 三种泛型重载) | 带 out 参数的安全 GUID 查询 |
HasGameEvents(name, category = null)(+ 三种泛型重载) | 检查名称匹配是否存在(可选分类过滤) |
GetGameEvents(name, category = null)(+ 三种泛型重载) | 获取所有名称匹配,返回 List<T> |
GetFirstGameEventByName(name, category = null)(+ 三种泛型重载) | 按名称的首个匹配(重名时任选其一) |
TryGetFirstGameEventByName(name, out, category = null)(+ 三种泛型重载) | 安全的首个匹配查询 |
⚠️ 关键最佳实践
✅ 应该做
private void OnEnable()
{
myEvent.AddListener(OnReceived); // ← 注册
}
private void OnDisable()
{
myEvent.RemoveListener(OnReceived); // ← 总是清理
}
❌ 不应该做
private void Start()
{
myEvent.AddListener(OnReceived); // ← 在Start中注册...
}
// ❌ 无OnDisable清理 → 内存泄漏
句柄管理
private ScheduleHandle _handle;
public void StartLoop()
{
_handle = myEvent.RaiseRepeating(1f, -1);
}
public void StopLoop()
{
if (_handle != null) myEvent.CancelRepeating(_handle); // ← 使用存储的句柄
}
生命周期模式
| 生命周期方法 | 用于 |
|---|---|
Awake | 持久化监听器 + DontDestroyOnLoad |
OnEnable | 标准监听器、触发器、链 |
OnDisable | 移除标准监听器 |
OnDestroy | 移除持久化监听器 |
🎯 何时选择代码与可视化
选择可视化工作流当:
- ✅ 设计师需要直接控制
- ✅ 快速迭代是优先事项
- ✅ 逻辑相对静态
- ✅ 可视化调试有益
- ✅ 跨学科团队协作
选择代码工作流当:
- ✅ 逻辑高度动态(运行时图表构建)
- ✅ 条件需要复杂的C#代码
- ✅ 与现有代码系统集成
- ✅ 高级调度模式
- ✅ 编程监听器管理
- ✅ 逻辑的版本控制(代码差异比.asset差异更清晰)
混合方法:
- 🎨 可视化: 事件定义、简单绑定
- 💻 代码: 复杂条件、动态图表、运行时调度
- 示例: 可视化定义事件,但在代码中为程序化系统构建触发器/链式图表