Files
zeling_v2/Assets/Scripts/EventChain/EventChainManager.cs
2026-05-13 09:19:54 +08:00

133 lines
5.8 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using BaseGames.Core;
using BaseGames.Core.Events;
using UnityEngine;
namespace BaseGames.EventChain
{
/// <summary>
/// 世界事件链管理器(架构 14_NarrativeModule §10
/// 订阅游戏各系统的 SO 事件频道,转发为中继 C# 事件供 ChainCondition.Register() 绑定。
/// 每当收到新事件时,检查所有链的触发条件。
/// </summary>
public class EventChainManager : MonoBehaviour
{
[Header("所有事件链")]
[SerializeField] private EventChainSO[] _chains;
[Header("事件频道(中继)")]
[SerializeField] private StringEventChannelSO _onBossDefeated; // EVT_EnemyDiedbossId
[SerializeField] private StringEventChannelSO _onCollectiblePickedUp; // EVT_CollectiblePickup
[SerializeField] private StringEventChannelSO _onAbilityUnlocked; // EVT_AbilityUnlocked
[SerializeField] private StringEventChannelSO _onRoomEntered; // EVT_SceneLoaded
[SerializeField] private StringEventChannelSO _onDialogueCompleted; // EVT_NpcDialogueCompleted
// ── 中继 C# 事件,供 ChainCondition.Register() 订阅 ──────────────
public event Action<string> OnBossDefeated;
public event Action<string> OnCollectiblePickedUp;
public event Action<string> OnAbilityUnlocked;
public event Action<string> OnRoomEntered;
public event Action<string> OnDialogueCompleted;
/// <summary>链执行完成时广播 chainId供 ChainCompletedCondition。</summary>
public event Action<string> OnChainCompleted;
#if UNITY_EDITOR
/// <summary>
/// Editor 专用静态事件:链执行完成时向编辑器窗口推送日志。
/// 仅在编辑器构建中存在,不产生运行时开销。
/// EventChainEditorWindow 在 OnEnable 中订阅此事件。
/// </summary>
public static event Action<string, string> OnChainExecutedInEditor;
#endif
private readonly HashSet<string> _completedChains = new();
private readonly CompositeDisposable _subs = new();
// ── 生命周期 ──────────────────────────────────────────────────────
private void Awake()
{
// 从存档恢复已完成链
var sm = ServiceLocator.GetOrDefault<ISaveService>();
if (sm != null)
foreach (var id in sm.GetCompletedChains())
_completedChains.Add(id);
}
private void OnEnable()
{
_onBossDefeated?.Subscribe(id => { OnBossDefeated?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
_onCollectiblePickedUp?.Subscribe(id => { OnCollectiblePickedUp?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
_onAbilityUnlocked?.Subscribe(id => { OnAbilityUnlocked?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
_onRoomEntered?.Subscribe(id => { OnRoomEntered?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
_onDialogueCompleted?.Subscribe(id => { OnDialogueCompleted?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
// 向每个 Condition 注册中继事件
// 先重置 SO 资产上的运行时状态,防止跨 PlayMode 会话或多场景加载时状态残留
if (_chains == null) return;
foreach (var chain in _chains)
if (chain?.conditions != null)
foreach (var cond in chain.conditions)
{
cond?.ResetState();
cond?.Register(this);
}
}
private void OnDisable()
{
_subs.Clear();
if (_chains == null) return;
foreach (var chain in _chains)
if (chain?.conditions != null)
foreach (var cond in chain.conditions)
cond?.Unregister(this);
}
// ── 评估逻辑 ──────────────────────────────────────────────────────
/// <summary>收到新事件时立即评估所有链条件(无帧延迟)。</summary>
private void EvaluateAll() => DoEvaluateAll();
private void DoEvaluateAll()
{
if (_chains == null) return;
foreach (var chain in _chains)
{
if (chain == null) continue;
if (!chain.repeatable && _completedChains.Contains(chain.chainId)) continue;
bool allMet = true;
if (chain.conditions != null)
foreach (var cond in chain.conditions)
if (cond != null && !cond.IsMet()) { allMet = false; break; }
if (allMet) StartCoroutine(ExecuteChain(chain));
}
}
private IEnumerator ExecuteChain(EventChainSO chain)
{
// 防重入:一次性链立即标记为已完成
if (!chain.repeatable) _completedChains.Add(chain.chainId);
if (chain.actions != null)
foreach (var action in chain.actions)
{
if (action == null) continue;
yield return action.ExecuteAsync(this);
if (chain.actionDelay > 0f)
yield return new WaitForSeconds(chain.actionDelay);
}
ServiceLocator.GetOrDefault<ISaveService>()?.SetChainCompleted(chain.chainId);
OnChainCompleted?.Invoke(chain.chainId);
#if UNITY_EDITOR
OnChainExecutedInEditor?.Invoke(chain.chainId, "执行完成");
#endif
}
}
}