Files
Gen_Hack-and-Slash-Roguelite/Client/Assets/Scripts/Managers/EventManager.cs

494 lines
21 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.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 // 故事播放完毕
}
}
}
}