跳到主要内容

13 运行时API:代码工作流

🔈 Hover for sound

📋 概述

之前的示例(01-11)演示了可视化工作流——在检查器中绑定监听器、在行为窗口中配置条件以及可视化构建流程图。这种方法非常适合设计师和快速原型开发。然而,程序员通常更喜欢完全的代码控制,用于复杂系统、动态行为或可视化工具变得受限时。

Demo 13证明了一个关键的架构原则: 您在可视化工作流中看到的每个功能都有一个完整的、类型安全的C# API。本示例重新访问所有11个先前的场景,移除所有检查器绑定和图表配置,用运行时代码替换它们。

💡 您将学到
  • 如何以编程方式注册/移除监听器(AddListenerRemoveListener
  • 动态优先级控制(AddPriorityListener
  • 运行时条件注册(AddConditionalListener
  • 调度API(RaiseDelayedRaiseRepeatingCancel
  • 在代码中构建流程图(AddTriggerEventAddChainEvent
  • 持久化监听器管理(AddPersistentListener
  • 生命周期管理(OnEnableOnDisable、清理模式)

🎬 示例结构

📁 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)
  • 签名: 方法必须匹配事件类型(GameEventvoid

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) = 无限直到取消
  • 📡 回调: OnStepOnCompletedOnCancelled 事件
  • 🛑 手动停止: 无限循环使用 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差异更清晰)

混合方法:

  • 🎨 可视化: 事件定义、简单绑定
  • 💻 代码: 复杂条件、动态图表、运行时调度
  • 示例: 可视化定义事件,但在代码中为程序化系统构建触发器/链式图表

📚 相关文档