Files

494 lines
21 KiB
C#
Raw Permalink Normal View History

using System;
2025-08-28 16:20:24 +08:00
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks; // For Task
using Base;
using Data;
using EventWorkClass;
using UnityEngine;
using UnityEngine.SceneManagement;
using Utils;
namespace Managers
{
/// <summary>
/// 事件管理器,负责事件的加载、注册和执行。
/// 遵循单例模式,并在启动流程中扮演一个管理器角色。
/// </summary>
internal class EventManager : Singleton<EventManager>, ILaunchManager, ITick // 实现 ITick 接口
{
/// <summary>
/// 存储当前正在播放的所有故事实例。
/// </summary>
private readonly List<StoryPlayer> _activeStoryPlayers = new();
/// <summary>
/// 存储所有已加载的事件定义,键为事件名称,值为对应的事件工作类实例。
/// </summary>
public Dictionary<string, EventWorkClassBase> EventDefs { get; private set; }
/// <summary>
/// 存储所有已加载的故事定义,键为故事名称,值为对应的故事定义实例。
/// </summary>
public Dictionary<string, StoryDef> StoryDefs { get; private set; }
public bool HasStoryDisplay => _activeStoryPlayers.Any();
/// <summary>
/// 指示管理器是否已完成初始化。
/// </summary>
public bool Completed { get; set; }
/// <summary>
/// 获取当前加载步骤的描述,用于启动流程的进度显示。
/// </summary>
public string StepDescription => "正在载入事件和故事";
/// <summary>
/// 初始化事件管理器,从定义管理器中加载所有事件定义和故事定义。
/// </summary>
public async Task Init() // 接口变更:返回 Task
{
// 如果事件和故事定义已经加载,则直接返回,避免重复初始化。
// 使用 EventDefs != null 作为内部数据是否已加载的判据。
if (EventDefs != null && StoryDefs != null)
return;
// 从定义管理器查询并加载所有事件定义。(不进行单例空检查)
var eventDefs = DefineManager.Instance.QueryDefinesByType<EventDef>();
EventDefs = new Dictionary<string, EventWorkClassBase>();
if (eventDefs != null) // 检查 eventDefs 是否为 null以避免 foreach 遍历 null 集合
{
foreach (var def in eventDefs)
{
if (EventDefs.ContainsKey(def.defName))
{
Debug.LogWarning($"警告:事件名称重复,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
try
{
var eventWorker = StringUtils.GetAndInstantiateEventWorker(def.workClass);
if (eventWorker == null)
{
Debug.LogWarning(
$"警告:未能找到或实例化名称为 '{def.workClass}' 的事件工作类(可能是类型不存在或构造函数问题),已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
eventWorker.Init(def.parameter);
EventDefs.Add(def.defName, eventWorker);
}
catch (Exception ex)
{
Debug.LogError(
$"错误:加载或实例化事件工作类 '{def.workClass}' (事件 '{def.defName}') 时发生异常:{ex.Message}\n{ex.StackTrace}");
}
}
}
// 从定义管理器查询并加载所有故事定义。(不进行单例空检查)
var storyDefs = DefineManager.Instance.QueryDefinesByType<StoryDef>();
StoryDefs = new Dictionary<string, StoryDef>();
if (storyDefs != null) // 检查 storyDefs 是否为 null
{
foreach (var storyDef in storyDefs)
{
if (StoryDefs.ContainsKey(storyDef.defName))
{
Debug.LogWarning($"警告:故事名称重复,已跳过加载名称为 '{storyDef.defName}' 的故事定义。");
continue;
}
StoryDefs.Add(storyDef.defName, storyDef);
}
}
// 将自身注册到时钟系统,以便每帧更新故事播放。(不进行单例空检查)
Clock.AddTick(this);
SceneManager.sceneLoaded += OnSceneLoaded;
Completed = true; // 接口变更:设置 Completed 为 true
}
/// <summary>
/// 清理事件管理器,释放所有已加载的事件和故事定义。
/// </summary>
public void Clear()
{
// 从时钟系统移除自身停止接收Tick更新。不进行单例空检查
Clock.RemoveTick(this);
// 清理所有正在播放的故事实例。
_activeStoryPlayers.Clear();
// 释放事件定义字典。
EventDefs = null;
// 释放故事定义字典。
StoryDefs = null;
SceneManager.sceneLoaded -= OnSceneLoaded;
Completed = false; // 清理后应将 Completed 置为 false
}
/// <summary>
/// 每帧更新,用于驱动所有活跃故事的播放逻辑。
/// 作为 <see cref="ITick" /> 接口的实现,由时钟系统调用。
/// </summary>
public void Tick()
{
if (_activeStoryPlayers.Count == 0) return;
// 获取自上一帧以来的时间增量。
var deltaTime = Time.deltaTime;
// 遍历所有正在播放的故事实例,并更新它们的状态。
// 为了避免在迭代过程中修改列表,先收集要移除的,再统一移除。
var playersToRemove = new List<StoryPlayer>();
foreach (var player in _activeStoryPlayers)
{
player.Update(deltaTime);
if (player.IsFinished) playersToRemove.Add(player);
}
// 移除所有已完成的故事实例。
foreach (var player in playersToRemove) _activeStoryPlayers.Remove(player);
}
private void OnSceneLoaded(Scene scene, LoadSceneMode mode)
{
// 如果 Clock.AddTick 内部没有去重逻辑,这里可能会重复添加。
// 但根据要求,不修改原有逻辑,保持原样。
Clock.AddTick(this); // (不进行单例空检查)
}
/// <summary>
/// 执行指定名称的事件。
/// </summary>
/// <param name="eventName">要执行的事件的名称。</param>
/// <param name="dimensionID">事件执行的维度ID如果为null将使用当前焦点的维度ID。</param>
public void Action(string eventName, string dimensionID = null)
{
// 依赖于 EventDefs 集合是否已初始化,而不是 Completed 状态,
// 因为 Completed 仅表示 ILaunchManager 的初始化完成,
// 可能会有异步操作导致 EventDefs 尚未完全填充。
if (EventDefs == null)
{
Debug.LogError($"错误:事件管理器尚未初始化或已被清理。无法执行事件 '{eventName}'。");
return;
}
if (!EventDefs.TryGetValue(eventName, out var eventWorker))
{
Debug.LogWarning($"警告:未能找到名称为 '{eventName}' 的事件定义,已跳过执行该事件。");
return;
}
// 假设 Program.Instance 存在且可访问。(不进行单例空检查)
// 如果 dimensionID 为 null则使用当前焦点维度ID。
dimensionID ??= Program.Instance.FocusedDimensionId;
eventWorker.Run(dimensionID);
}
public void Action(string eventName, Entity.Entity initiator)
{
if (EventDefs == null)
{
Debug.LogError($"错误:事件管理器尚未初始化或已被清理。无法执行事件 '{eventName}'。");
return;
}
if (!EventDefs.TryGetValue(eventName, out var eventWorker))
{
Debug.LogWarning($"警告:未能找到名称为 '{eventName}' 的事件定义,已跳过执行该事件。");
return;
}
eventWorker.Run(initiator.currentDimensionId, initiator);
}
public void Action(EventDef eventDef, Entity.Entity initiator)
{
Action(eventDef.defName, initiator);
}
/// <summary>
/// 播放指定名称的故事。
/// </summary>
/// <param name="storyName">要播放的故事的名称。</param>
/// <param name="dimensionID">故事执行的维度ID如果为null将使用当前焦点的维度ID。</param>
public void PlayStory(string storyName, string dimensionID = null)
{
// 依赖于 StoryDefs 集合是否已初始化。
if (StoryDefs == null || StoryDefs.Count == 0)
{
Debug.LogError($"错误:故事定义尚未加载或已被清理。无法播放故事 '{storyName}'。");
return;
}
if (!StoryDefs.TryGetValue(storyName, out var storyDef))
{
Debug.LogWarning($"警告:未能找到名称为 '{storyName}' 的故事定义,已跳过播放该故事。");
return;
}
if (storyDef.storyStage == null || storyDef.storyStage.Length == 0)
{
Debug.LogWarning($"警告:故事 '{storyDef.defName}' 没有定义任何阶段,已跳过播放。");
return;
}
// 确保 dimensionID 有效,如果为 null 则使用当前焦点维度ID。不进行单例空检查
dimensionID ??= Program.Instance.FocusedDimensionId;
// 创建一个新的 StoryPlayer 实例并添加到活跃列表中。
var player = new StoryPlayer(storyDef, dimensionID, this);
_activeStoryPlayers.Add(player);
}
/// <summary>
/// 停止所有指定名称的活跃故事。
/// </summary>
/// <param name="storyName">要停止的故事的名称。</param>
/// <returns>停止的故事数量。</returns>
public int StopStory(string storyName)
{
if (string.IsNullOrEmpty(storyName))
{
Debug.LogWarning("停止故事提供的故事名称为空或null。");
return 0;
}
// 使用 RemoveAll 高效移除所有匹配的故事实例
var stoppedCount = _activeStoryPlayers.RemoveAll(player => player.StoryDef.defName == storyName);
if (stoppedCount > 0)
{
Debug.Log($"停止了 {stoppedCount} 个名称为 '{storyName}' 的故事。");
}
return stoppedCount;
}
/// <summary>
/// 停止指定名称和维度ID的活跃故事。
/// </summary>
/// <param name="storyName">要停止的故事的名称。</param>
/// <param name="dimensionID">故事所处的维度ID。</param>
/// <returns>停止的故事数量。</returns>
public int StopStory(string storyName, string dimensionID)
{
if (string.IsNullOrEmpty(storyName) || string.IsNullOrEmpty(dimensionID))
{
Debug.LogWarning("停止故事提供的故事名称或维度ID为空或null。");
return 0;
}
// 使用 RemoveAll 高效移除所有名称和维度ID都匹配的故事实例
var stoppedCount = _activeStoryPlayers.RemoveAll(player =>
player.StoryDef.defName == storyName && player.DimensionID == dimensionID);
if (stoppedCount > 0)
{
Debug.Log($"停止了 {stoppedCount} 个名称为 '{storyName}' 且维度ID为 '{dimensionID}' 的故事。");
}
return stoppedCount;
}
/// <summary>
/// 停止所有当前活跃的故事。
/// </summary>
/// <returns>停止的故事数量。</returns>
public int StopAllStories()
{
var count = _activeStoryPlayers.Count;
if (count > 0)
{
_activeStoryPlayers.Clear(); // 清空列表
Debug.Log($"停止了所有 {count} 个活跃故事。");
}
return count;
}
/// <summary>
/// 将字符串颜色值解析为 <see cref="Color" /> 对象。
/// 支持十六进制颜色(如 "#RRGGBB" 或 "#AARRGGBB")或标准颜色名称。
/// </summary>
/// <param name="colorString">颜色字符串。</param>
/// <returns>解析后的 <see cref="Color" /> 对象。如果解析失败,返回白色。</returns>
private Color ParseColor(string colorString)
{
if (ColorUtility.TryParseHtmlString(colorString, out var color)) return color;
Debug.LogWarning($"警告:无法解析颜色字符串 '{colorString}'。使用默认白色。");
return Color.white; // 默认返回白色
}
/// <summary>
/// 表示一个正在播放的故事实例的状态机。
/// </summary>
private class StoryPlayer
{
// 事件管理器实例,用于调用 Action 和 ParseColor 方法。
private readonly EventManager _eventManager;
// 当前故事阶段的索引。
private int _currentStageIndex;
// 当前故事播放器所处的状态。
private State _currentState;
// 当前阶段的等待计时器。
private float _currentWaitTimer;
/// <summary>
/// 初始化 <see cref="StoryPlayer" /> 类的新实例。
/// </summary>
/// <param name="storyDef">要播放的故事定义。</param>
/// <param name="dimensionId">故事播放所在的维度ID。</param>
/// <param name="eventManager">事件管理器实例,用于执行事件和解析颜色。</param>
public StoryPlayer(StoryDef storyDef, string dimensionId, EventManager eventManager)
{
StoryDef = storyDef;
DimensionID = dimensionId;
_eventManager = eventManager;
_currentStageIndex = 0;
_currentWaitTimer = 0f;
_currentState = State.Initializing;
}
/// <summary> 获取当前正在播放的故事定义。 </summary>
public StoryDef StoryDef { get; }
/// <summary> 获取故事播放所在的维度ID。 </summary>
public string DimensionID { get; }
/// <summary> 获取一个值,表示故事是否已播放完毕。 </summary>
public bool IsFinished { get; private set; }
/// <summary>
/// 每帧更新故事播放器的状态。
/// </summary>
/// <param name="deltaTime">自上一帧以来的时间增量。</param>
public void Update(float deltaTime)
{
if (IsFinished) return;
// 如果故事阶段定义为null或为空则立即标记故事完成。
if (StoryDef.storyStage == null || StoryDef.storyStage.Length == 0)
{
Debug.LogWarning($"警告:故事 '{StoryDef.defName}' 没有定义任何阶段StoryPlayer 将立即完成。");
IsFinished = true;
return;
}
var currentStage = StoryDef.storyStage[_currentStageIndex];
switch (_currentState)
{
case State.Initializing:
// 从当前阶段的 lastWaitTime 开始等待,如果 lastWaitTime 小于等于 0 则直接跳过等待执行阶段。
if (currentStage.lastWaitTime > 0)
{
_currentState = State.WaitingLastTime;
_currentWaitTimer = 0f;
}
else
{
// 没有 lastWaitTime直接执行阶段。
_currentState = State.ExecutingStage;
}
break;
case State.WaitingLastTime:
_currentWaitTimer += deltaTime;
if (_currentWaitTimer >= currentStage.lastWaitTime)
{
// 等待时间已到,切换到执行阶段。
_currentWaitTimer = 0f;
_currentState = State.ExecutingStage;
}
break;
case State.ExecutingStage:
// 处理事件
if (currentStage.eventDef != null)
{
if (!string.IsNullOrEmpty(currentStage.eventDef.defName))
_eventManager.Action(currentStage.eventDef.defName, DimensionID);
else
Debug.LogWarning(
$"警告:故事 '{StoryDef.defName}' 阶段 {_currentStageIndex} 包含一个没有定义名称的事件,已跳过。");
}
// 处理消息
if (currentStage.messageDef != null)
{
// MessageManager.Instance 不进行空检查,假定单例始终可用
var messageColor = _eventManager.ParseColor(currentStage.messageDef.color);
MessageManager.Instance.DisplayMessage(currentStage.messageDef.text,
currentStage.messageDef.type, messageColor);
}
// 决定下一个状态
if (currentStage.nextWaitTime > 0)
{
_currentState = State.WaitingNextTime;
_currentWaitTimer = 0f;
}
else
{
// 没有 nextWaitTime直接推进到下一个阶段。
AdvanceToNextStage();
}
break;
case State.WaitingNextTime:
_currentWaitTimer += deltaTime;
if (_currentWaitTimer >= currentStage.nextWaitTime)
{
// 等待时间已到,推进到下一个阶段。
_currentWaitTimer = 0f;
AdvanceToNextStage();
}
break;
case State.Finished:
// 故事已经完成,不再更新。
break;
}
}
/// <summary>
/// 推进到下一个故事阶段,或标记故事完成。
/// </summary>
private void AdvanceToNextStage()
{
_currentStageIndex++;
if (_currentStageIndex < StoryDef.storyStage.Length)
{
// 还有后续阶段,重置状态以处理下一个阶段的 lastWaitTime。
_currentState = State.Initializing;
}
else
{
// 所有阶段播放完毕,标记故事完成。
_currentState = State.Finished;
IsFinished = true;
}
}
/// <summary> 故事播放阶段的状态枚举。 </summary>
private enum State
{
Initializing, // 刚开始,准备处理第一个阶段的 lastWaitTime
WaitingLastTime, // 等待当前阶段的 lastWaitTime
ExecutingStage, // 执行当前阶段的事件/消息
WaitingNextTime, // 等待当前阶段的 nextWaitTime
Finished // 故事播放完毕
}
}
}
}