多轮审查和修复
This commit is contained in:
18
Assets/Scripts/EventChain/BaseGames.EventChain.asmdef
Normal file
18
Assets/Scripts/EventChain/BaseGames.EventChain.asmdef
Normal file
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"excludePlatforms": [],
|
||||
"allowUnsafeCode": false,
|
||||
"precompiledReferences": [],
|
||||
"name": "BaseGames.EventChain",
|
||||
"defineConstraints": [],
|
||||
"noEngineReferences": false,
|
||||
"versionDefines": [],
|
||||
"rootNamespace": "BaseGames.EventChain",
|
||||
"references": [
|
||||
"BaseGames.Core",
|
||||
"BaseGames.Core.Events",
|
||||
"BaseGames.Core.Save"
|
||||
],
|
||||
"autoReferenced": true,
|
||||
"overrideReferences": false,
|
||||
"includePlatforms": []
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 11ab4ef74083e0c4eb26342cc1d6e4e4
|
||||
AssemblyDefinitionImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
139
Assets/Scripts/EventChain/EventChainManager.cs
Normal file
139
Assets/Scripts/EventChain/EventChainManager.cs
Normal file
@@ -0,0 +1,139 @@
|
||||
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_EnemyDied(bossId)
|
||||
[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();
|
||||
/// <summary>当帧内有任意事件触发时置 true,Update 中合并为单次评估,避免同帧多事件重复遍历。</summary>
|
||||
private bool _evaluatePending;
|
||||
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);
|
||||
}
|
||||
|
||||
// ── 评估逻辑 ──────────────────────────────────────────────────────
|
||||
private void EvaluateAll() => _evaluatePending = true;
|
||||
private void Update()
|
||||
{
|
||||
if (!_evaluatePending) return;
|
||||
_evaluatePending = false;
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/EventChain/EventChainManager.cs.meta
Normal file
11
Assets/Scripts/EventChain/EventChainManager.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 36df7401ba1f4084ebd28f4d162f6c9f
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
288
Assets/Scripts/EventChain/EventChainSO.cs
Normal file
288
Assets/Scripts/EventChain/EventChainSO.cs
Normal file
@@ -0,0 +1,288 @@
|
||||
using System;
|
||||
using System.Collections;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.EventChain
|
||||
{
|
||||
// =====================================================================
|
||||
// EventChainSO —— 世界事件链数据(架构 14_NarrativeModule §9)
|
||||
// =====================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 世界事件链:描述"当全部 conditions 满足时,依序执行 actions"。
|
||||
/// 策划纯数据配置,无需程序员介入。
|
||||
/// 资产路径:Assets/ScriptableObjects/EventChains/Chain_{Context}.asset
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/EventChain")]
|
||||
public class EventChainSO : ScriptableObject
|
||||
{
|
||||
[Header("基础")]
|
||||
public string chainId; // 全局唯一,如 "Chain_BossForest_Defeated"
|
||||
public bool repeatable; // false = 只触发一次(触发后存档记录)
|
||||
[Min(0f)]
|
||||
public float actionDelay = 0f; // 各 action 之间的延迟(秒),0 = 紧接着执行
|
||||
|
||||
[Header("触发条件(全部满足才触发)")]
|
||||
public ChainCondition[] conditions;
|
||||
|
||||
[Header("执行动作(顺序执行)")]
|
||||
public ChainAction[] actions;
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// ChainCondition 抽象基类 + 内置实现
|
||||
// =====================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 事件链触发条件抽象基类(Strategy + Observer 混合)。
|
||||
/// Register/Unregister 向 EventChainManager 的中继 C# 事件挂钩,
|
||||
/// IsMet() 被 EvaluateAll() 调用以检验是否满足触发条件。
|
||||
/// </summary>
|
||||
public abstract class ChainCondition : ScriptableObject
|
||||
{
|
||||
public abstract void Register(EventChainManager manager);
|
||||
public abstract void Unregister(EventChainManager manager);
|
||||
public abstract bool IsMet();
|
||||
/// <summary>
|
||||
/// 重置运行时瞬态状态(每次 EventChainManager.OnEnable 时调用)。
|
||||
/// ScriptableObject 是资产,_met 等字段会跨 PlayMode 会话残留;
|
||||
/// 显式重置确保每次进入游戏/切换场景时条件均从初始状态开始评估。
|
||||
/// </summary>
|
||||
public virtual void ResetState() { }
|
||||
}
|
||||
|
||||
// ─── 内置条件 ──────────────────────────────────────────────────────────
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/BossDefeated")]
|
||||
public class BossDefeatedCondition : ChainCondition
|
||||
{
|
||||
public string bossId;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnBossDefeated += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnBossDefeated -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == bossId) _met = true; }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/FlagSet")]
|
||||
public class FlagSetCondition : ChainCondition
|
||||
{
|
||||
public string flagId;
|
||||
public override void Register(EventChainManager m) { } // 持续轮询,无需订阅
|
||||
public override void Unregister(EventChainManager m) { }
|
||||
public override bool IsMet() { var sm = ServiceLocator.GetOrDefault<ISaveService>(); return sm != null && sm.GetFlag(flagId); }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/AbilityUnlocked")]
|
||||
public class AbilityUnlockedCondition : ChainCondition
|
||||
{
|
||||
public string abilityId;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnAbilityUnlocked += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnAbilityUnlocked -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == abilityId) _met = true; }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/CollectibleCollected")]
|
||||
public class CollectibleCollectedCondition : ChainCondition
|
||||
{
|
||||
public string itemId;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnCollectiblePickedUp += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnCollectiblePickedUp -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == itemId) _met = true; }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/RoomEntered")]
|
||||
public class RoomEnteredCondition : ChainCondition
|
||||
{
|
||||
public string sceneName;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnRoomEntered += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnRoomEntered -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == sceneName) _met = true; }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/DialogueCompleted")]
|
||||
public class DialogueCompletedCondition : ChainCondition
|
||||
{
|
||||
public string npcId;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnDialogueCompleted += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnDialogueCompleted -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == npcId) _met = true; }
|
||||
}
|
||||
|
||||
[CreateAssetMenu(menuName = "EventChain/Condition/ChainCompleted")]
|
||||
public class ChainCompletedCondition : ChainCondition
|
||||
{
|
||||
public string chainId;
|
||||
private bool _met;
|
||||
public override void Register(EventChainManager m) => m.OnChainCompleted += Check;
|
||||
public override void Unregister(EventChainManager m) => m.OnChainCompleted -= Check;
|
||||
public override bool IsMet() => _met;
|
||||
public override void ResetState() => _met = false;
|
||||
private void Check(string id) { if (id == chainId) _met = true; }
|
||||
}
|
||||
|
||||
// =====================================================================
|
||||
// ChainAction 抽象基类 + 内置实现
|
||||
// =====================================================================
|
||||
|
||||
/// <summary>
|
||||
/// 事件链执行动作抽象基类。ExecuteAsync 可即时返回或协程等待。
|
||||
/// </summary>
|
||||
public abstract class ChainAction : ScriptableObject
|
||||
{
|
||||
public abstract IEnumerator ExecuteAsync(MonoBehaviour runner);
|
||||
}
|
||||
|
||||
// ─── 内置动作 ──────────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>打开门(发布 EVT_DoorOpened 事件)。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/OpenDoor")]
|
||||
public class OpenDoorAction : ChainAction
|
||||
{
|
||||
public string doorId;
|
||||
[SerializeField] private StringEventChannelSO _onDoorOpened; // EVT_DoorOpened
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_onDoorOpened?.Raise(doorId);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>设置/清除存档标志。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/SetFlag")]
|
||||
public class SetFlagAction : ChainAction
|
||||
{
|
||||
public string flagId;
|
||||
public bool value = true;
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
ServiceLocator.GetOrDefault<ISaveService>()?.SetFlag(flagId, value);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>通知地图显示/标记区域(通过事件频道解耦,无直接 MapManager 依赖)。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/UpdateMap")]
|
||||
public class UpdateMapAction : ChainAction
|
||||
{
|
||||
public string regionId;
|
||||
[SerializeField] private StringEventChannelSO _onRevealRegion; // EVT_RevealRegion → MapManager 订阅
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_onRevealRegion?.Raise(regionId);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>播放过场动画,等待其结束再继续链。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/PlayCutscene")]
|
||||
public class PlayCutsceneAction : ChainAction
|
||||
{
|
||||
public string cutsceneId;
|
||||
[SerializeField] private StringEventChannelSO _onPlayCutscene; // → CutsceneManager.PlayById
|
||||
[SerializeField] private VoidEventChannelSO _onCutsceneEnded; // ← CutsceneManager 播完后 Raise
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
bool done = false;
|
||||
var sub = _onCutsceneEnded != null
|
||||
? _onCutsceneEnded.Subscribe(() => done = true)
|
||||
: default(EventSubscription);
|
||||
_onPlayCutscene?.Raise(cutsceneId);
|
||||
yield return new WaitUntil(() => done);
|
||||
sub.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>切换 NPC 对话(通过 EVT_NPCDialogueChange 广播,NPC 自行响应)。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/ChangeNPCDialogue")]
|
||||
public class ChangeNPCDialogueAction : ChainAction
|
||||
{
|
||||
public string npcId;
|
||||
public string newSequenceId;
|
||||
[SerializeField] private StringEventChannelSO _onNPCDialogueChange; // EVT_NPCDialogueChange → NPC 订阅
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_onNPCDialogueChange?.Raise($"{npcId}:{newSequenceId}");
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>在场景中生成一个预制体。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/SpawnObject")]
|
||||
public class SpawnObjectAction : ChainAction
|
||||
{
|
||||
public GameObject prefab;
|
||||
public Vector3 position;
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
if (prefab != null)
|
||||
UnityEngine.Object.Instantiate(prefab, position, Quaternion.identity);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>等待指定秒数。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/Wait")]
|
||||
public class WaitAction : ChainAction
|
||||
{
|
||||
[Min(0f)]
|
||||
public float seconds;
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
yield return new WaitForSeconds(seconds);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>发布一个无负载 Void 事件频道。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/RaiseEvent")]
|
||||
public class RaiseEventAction : ChainAction
|
||||
{
|
||||
[SerializeField] private VoidEventChannelSO _eventChannel;
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_eventChannel?.Raise();
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>解锁能力(通过 EVT_AbilityUnlocked 事件广播,PlayerStats 响应)。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/UnlockAbility")]
|
||||
public class UnlockAbilityAction : ChainAction
|
||||
{
|
||||
public string abilityId;
|
||||
[SerializeField] private StringEventChannelSO _onAbilityUnlock; // EVT_AbilityUnlocked
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_onAbilityUnlock?.Raise(abilityId);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>切换背景音乐(通过 EVT_PlayBGM 事件广播)。</summary>
|
||||
[CreateAssetMenu(menuName = "EventChain/Action/PlayAudio")]
|
||||
public class PlayAudioAction : ChainAction
|
||||
{
|
||||
public string bgmKey;
|
||||
[SerializeField] private StringEventChannelSO _onPlayBGM; // EVT_PlayBGM
|
||||
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
|
||||
{
|
||||
_onPlayBGM?.Raise(bgmKey);
|
||||
yield break;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/Scripts/EventChain/EventChainSO.cs.meta
Normal file
11
Assets/Scripts/EventChain/EventChainSO.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5747d1e1a1513194ba1f2b5f1cd5c8ba
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user