Files
zeling_v2/Assets/_Game/Scripts/EventChain/EventChainManager.cs

153 lines
6.9 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 BaseGames.Core.Save;
using UnityEngine;
namespace BaseGames.EventChain
{
/// <summary>
/// 世界事件链管理器(架构 14_NarrativeModule §10
/// 订阅游戏各系统的 SO 事件频道,转发为中继 C# 事件供 ChainCondition.Register() 绑定。
/// 每当收到新事件时,检查所有链的触发条件。
/// </summary>
public class EventChainManager : MonoBehaviour, ISaveable
{
[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()
{
// 不在此处读取存档数据——LoadAsync 尚未完成_current 为 null。
// 已完成链的恢复由 ISaveable.OnLoad 在 LoadAsync 完成后处理。
}
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);
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
// 向每个 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();
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
if (_chains == null) return;
foreach (var chain in _chains)
if (chain?.conditions != null)
foreach (var cond in chain.conditions)
cond?.Unregister(this);
}
// ── ISaveable ─────────────────────────────────────────────────────────
/// <summary>
/// SetChainCompleted 通过 ISaveService 直接写入 SaveDataOnSave 无需操作。
/// </summary>
public void OnSave(SaveData data) { }
/// <summary>
/// LoadAsync 完成后由 GameSaveManager.Register 补发,恢复本会话前已完成的链。
/// 仅恢复状态为 "Completed" 的条目;其他状态(如未来扩展的 "Failed")不影响重入检查。
/// </summary>
public void OnLoad(SaveData data)
{
_completedChains.Clear();
if (data?.EventChains?.ChainStates == null) return;
foreach (var kv in data.EventChains.ChainStates)
if (kv.Value == "Completed")
_completedChains.Add(kv.Key);
}
// ── 评估逻辑 ──────────────────────────────────────────────────────
/// <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
}
}
}