Files

442 lines
19 KiB
C#
Raw Permalink 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>();
var loadedEventCount = 0;
foreach (var def in eventDefs)
{
if (EventDefs.ContainsKey(def.defName))
{
Debug.LogWarning($"警告:事件名称重复,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
var eventWorker = GetAndInstantiateEventWorker(def.workClass);
if (eventWorker == null)
{
Debug.LogWarning($"警告:未能找到或实例化名称为 '{def.workClass}' 的事件工作类,已跳过加载名称为 '{def.defName}' 的事件定义。");
continue;
}
eventWorker.Init(def.parameter);
EventDefs.Add(def.defName, eventWorker);
loadedEventCount++;
}
Debug.Log($"事件管理器初始化完成,共载入 {loadedEventCount} 个事件。");
// 从定义管理器查询并加载所有故事定义。
var storyDefs = DefineManager.Instance.QueryDefinesByType<StoryDef>();
StoryDefs = new Dictionary<string, StoryDef>();
var loadedStoryCount = 0;
foreach (var storyDef in storyDefs)
{
if (StoryDefs.ContainsKey(storyDef.defName))
{
Debug.LogWarning($"警告:故事名称重复,已跳过加载名称为 '{storyDef.defName}' 的故事定义。");
continue;
}
StoryDefs.Add(storyDef.defName, storyDef);
loadedStoryCount++;
}
Debug.Log($"事件管理器初始化完成,共载入 {loadedStoryCount} 个故事。");
// 将自身注册到时钟系统,以便每帧更新故事播放。
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);
}
/// <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>
/// 根据类名从指定命名空间和程序集下获取并实例化一个 <see cref="EventWorkClassBase"/> 的子类。
/// </summary>
/// <param name="className">要实例化的类的短名称(不包含命名空间)。</param>
/// <param name="targetNamespace">目标类所在的完整命名空间。</param>
/// <param name="assemblyToSearch">要搜索的程序集。如果为 null将搜索 <see cref="EventWorkClassBase"/> 所在的程序集。</param>
/// <returns>实例化后的 <see cref="EventWorkClassBase"/> 对象,如果找不到或不符合条件则返回 null。</returns>
public static EventWorkClassBase GetAndInstantiateEventWorker(
string className,
string targetNamespace = "EventWorkClass", // 默认命名空间
Assembly assemblyToSearch = null) // 默认程序集
{
// 1. 确定要搜索的程序集。
if (assemblyToSearch == null)
{
// 默认从 EventWorkClassBase 所在的程序集查找,通常其实现类也位于此程序集。
assemblyToSearch = typeof(EventWorkClassBase).Assembly;
}
// 2. 构造完整的类型名称。
var fullTypeName = $"{targetNamespace}.{className}";
Type targetType = null;
// 3. 尝试直接从程序集获取类型。
targetType = assemblyToSearch.GetType(fullTypeName);
// 4. 进行类型检查。
if (targetType == null)
{
Debug.LogError($"错误:在程序集 '{assemblyToSearch.FullName}' 的命名空间 '{targetNamespace}' 中未找到类 '{className}'。");
return null;
}
// 检查是否是 EventWorkClassBase 的子类。
if (!typeof(EventWorkClassBase).IsAssignableFrom(targetType))
{
Debug.LogError($"错误:类 '{fullTypeName}' 不是 '{typeof(EventWorkClassBase).FullName}' 的子类。");
return null;
}
// 检查是否可以实例化(非抽象类,非接口)。
if (targetType.IsAbstract || targetType.IsInterface)
{
Debug.LogError($"错误:类 '{fullTypeName}' 是抽象类或接口,不能直接实例化。");
return null;
}
// 5. 实例化对象。
try
{
// 使用 Activator.CreateInstance 实例化对象。它默认调用无参公共构造函数。
var instance = Activator.CreateInstance(targetType);
return instance as EventWorkClassBase;
}
catch (MissingMethodException ex)
{
Debug.LogError($"错误:类 '{fullTypeName}' 没有公共的无参构造函数。详细信息: {ex.Message}");
return null;
}
catch (Exception ex)
{
Debug.LogError($"实例化类 '{fullTypeName}' 时发生未知错误。详细信息: {ex.Message}");
return null;
}
}
/// <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;
}
}
}
}
}