Files
zeling_v2/Assets/_Game/Scripts/EventChain/EventChainSO.cs
Joywayer 446fd5dcd0 feat: Add WorldStateFlagAttribute and custom property drawer for enhanced dialogue management
- Implemented WorldStateFlagAttribute to mark string fields as world state flags.
- Created NarrativeNPCEditor for custom inspector to visualize dialogue version activation states.
- Developed WorldStateFlagDrawer to provide dropdown menu for known flags in the inspector.
- Introduced ActorModule for managing DialogueActorSO assets, including viewing, creating, and deleting actors.
- Added DialogueModule for managing DialogueSequenceSO assets with detailed previews and action bars.
- Established QuestModule for managing QuestSO assets, including objectives and branches.
- Implemented QuestManagerPostprocessor to automatically refresh QuestManager's quest list on asset changes.
2026-05-24 00:36:11 +08:00

297 lines
13 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 BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Dialogue;
using UnityEngine;
namespace BaseGames.EventChain
{
// =====================================================================
// EventChainSO —— 世界事件链数据(架构 14_NarrativeModule §9
// =====================================================================
/// <summary>
/// 世界事件链:描述"当全部 conditions 满足时,依序执行 actions"。
/// 策划纯数据配置,无需程序员介入。
/// 资产路径Assets/ScriptableObjects/EventChains/Chain_{Context}.asset
/// </summary>
[CreateAssetMenu(menuName = "BaseGames/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 = "BaseGames/EventChain/Condition/BossDefeated")]
public class BossDefeatedCondition : ChainCondition
{
public string bossId;
[System.NonSerialized] 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 = "BaseGames/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 = "BaseGames/EventChain/Condition/AbilityUnlocked")]
public class AbilityUnlockedCondition : ChainCondition
{
public string abilityId;
[System.NonSerialized] 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 = "BaseGames/EventChain/Condition/CollectibleCollected")]
public class CollectibleCollectedCondition : ChainCondition
{
public string itemId;
[System.NonSerialized] 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 = "BaseGames/EventChain/Condition/RoomEntered")]
public class RoomEnteredCondition : ChainCondition
{
public string sceneName;
[System.NonSerialized] 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 = "BaseGames/EventChain/Condition/DialogueCompleted")]
public class DialogueCompletedCondition : ChainCondition
{
public string npcId;
[System.NonSerialized] 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 = "BaseGames/EventChain/Condition/ChainCompleted")]
public class ChainCompletedCondition : ChainCondition
{
public string chainId;
[System.NonSerialized] 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 = "BaseGames/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 = "BaseGames/EventChain/Action/SetFlag")]
public class SetFlagAction : ChainAction
{
[WorldStateFlag]
public string flagId;
public bool value = true;
public override IEnumerator ExecuteAsync(MonoBehaviour runner)
{
var saveService = ServiceLocator.GetOrDefault<ISaveService>();
if (saveService == null)
{
Debug.LogError($"[SetFlagAction] ISaveService 未注册,标志 '{flagId}' 无法持久化。", this);
yield break;
}
saveService.SetFlag(flagId, value);
yield break;
}
}
/// <summary>通知地图显示/标记区域(通过事件频道解耦,无直接 MapManager 依赖)。</summary>
[CreateAssetMenu(menuName = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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 = "BaseGames/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;
}
}
}