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

395 lines
16 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.Reflection;
using Data;
using EventWorkClass;
using Utils;
using UnityEngine;
using Base;
namespace Managers
{
/// <summary>
/// 事件管理器,负责事件的加载、注册和执行。
/// 遵循单例模式,并在启动流程中扮演一个管理器角色。
/// </summary>
class EventManager : Singleton<EventManager>, ILaunchManager, ITick // 实现 ITick 接口
{
/// <summary>
/// 存储所有已加载的事件定义,键为事件名称,值为对应的事件工作类实例。
/// </summary>
public Dictionary<string, EventWorkClassBase> EventDefs { get; private set; } = null;
/// <summary>
/// 存储所有已加载的故事定义,键为故事名称,值为对应的故事定义实例。
/// </summary>
public Dictionary<string, StoryDef> StoryDefs { get; private set; } = null;
/// <summary>
/// 存储当前正在播放的所有故事实例。
/// </summary>
private readonly List<StoryPlayer> _activeStoryPlayers = new List<StoryPlayer>();
/// <summary>
/// 获取当前加载步骤的描述,用于启动流程的进度显示。
/// </summary>
public string StepDescription => "正在载入事件和故事";
/// <summary>
/// 初始化事件管理器,从定义管理器中加载所有事件定义和故事定义。
/// </summary>
public void Init()
{
// 如果事件和故事定义已经加载,则直接返回,避免重复初始化。
if (EventDefs != null && StoryDefs != null)
return;
// 从定义管理器查询并加载所有事件定义。
var eventDefs = DefineManager.Instance.QueryDefinesByType<EventDef>();
EventDefs = new Dictionary<string, EventWorkClassBase>();
if (eventDefs == null)
return;
foreach (var def in eventDefs)
{
if (EventDefs.ContainsKey(def.defName))
{
Debug.LogWarning($"警告:事件名称重复,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
var eventWorker = Utils.StringUtils.GetAndInstantiateEventWorker(def.workClass);
if (eventWorker == null)
{
Debug.LogWarning($"警告:未能找到或实例化名称为 '{def.workClass}' 的事件工作类,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
eventWorker.Init(def.parameter);
EventDefs.Add(def.defName, eventWorker);
}
// 从定义管理器查询并加载所有故事定义。
var storyDefs = DefineManager.Instance.QueryDefinesByType<StoryDef>();
StoryDefs = new Dictionary<string, StoryDef>();
if (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);
}
/// <summary>
/// 清理事件管理器,释放所有已加载的事件和故事定义。
/// </summary>
public void Clear()
{
// 从时钟系统移除自身停止接收Tick更新。
Clock.RemoveTick(this);
// 清理所有正在播放的故事实例。
_activeStoryPlayers.Clear();
// 释放事件定义字典。
EventDefs = null;
// 释放故事定义字典。
StoryDefs = null;
}
/// <summary>
/// 执行指定名称的事件。
/// </summary>
/// <param name="eventName">要执行的事件的名称。</param>
/// <param name="dimensionID">事件执行的维度ID如果为null将使用当前焦点的维度ID。</param>
public void Action(string eventName, string dimensionID = null)
{
if (EventDefs == null)
{
Debug.LogError($"错误:事件管理器尚未初始化或已被清理。无法执行事件 '{eventName}'。");
return;
}
if (!EventDefs.TryGetValue(eventName, out var eventWorker))
{
Debug.LogWarning($"警告:未能找到名称为 '{eventName}' 的事件定义,已跳过执行该事件。");
return;
}
// 假设 Program.Instance 和 FocusedDimensionId 存在且可访问。
// 如果 dimensionID 为 null则使用当前焦点维度ID。
dimensionID ??= Program.Instance.FocusedDimensionId;
eventWorker.Run(dimensionID);
}
public void Action(string eventName, Entity.Entity initiator)
{
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)
{
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>
/// 每帧更新,用于驱动所有活跃故事的播放逻辑。
/// 作为 <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);
}
}
/// <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
{
/// <summary> 故事播放阶段的状态枚举。 </summary>
private enum State
{
Initializing, // 刚开始,准备处理第一个阶段的 lastWaitTime
WaitingLastTime, // 等待当前阶段的 lastWaitTime
ExecutingStage, // 执行当前阶段的事件/消息
WaitingNextTime, // 等待当前阶段的 nextWaitTime
Finished // 故事播放完毕
}
/// <summary> 获取当前正在播放的故事定义。 </summary>
public StoryDef StoryDef { get; private set; }
/// <summary> 获取故事播放所在的维度ID。 </summary>
public string DimensionID { get; private set; }
/// <summary> 获取一个值,表示故事是否已播放完毕。 </summary>
public bool IsFinished { get; private set; } = false;
// 事件管理器实例,用于调用 Action 和 ParseColor 方法。
private readonly EventManager _eventManager;
// 当前故事阶段的索引。
private int _currentStageIndex;
// 当前阶段的等待计时器。
private float _currentWaitTimer;
// 当前故事播放器所处的状态。
private State _currentState;
/// <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>
/// <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)
{
if (MessageManager.Instance == null)
{
Debug.LogError($"错误MessageManager 实例不存在,无法显示故事 '{StoryDef.defName}' 的消息。");
}
else
{
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;
}
}
}
}
}