using System; using System.Collections; using System.Collections.Generic; using System.Threading.Tasks; // 引入Task命名空间 using Configs; using Logging; using Managers; using TMPro; using UI; using UnityEngine; using UnityEngine.SceneManagement; // 提供List等通用集合类型 // 提供日志记录功能 // 提供管理器接口和实现 // TextMeshPro文本组件命名空间 // Unity引擎核心命名空间 // 场景管理命名空间 namespace Base { /// /// Launcher 类负责管理游戏的初始化加载流程,包括管理器加载、进度条显示和场景切换。 /// /// /// /// 请注意:虽然此处的 progressBar 变量类型被声明为 Gradient, /// 但其在代码中的使用方式(如访问 color1, color2 属性和调用 Refresh() 方法) /// 强烈暗示它应引用一个具有类似 API 的自定义进度条组件,例如 CustomProgressBar。 /// /// /// 如果您使用的是标准 UnityEngine.UI.Image 或其他组件来显示进度, /// 则 ProgressOpacity 属性的实现逻辑需要根据实际组件的 API 进行大幅修改。 /// /// public class Launcher : MonoBehaviour { /// /// 加载界面UI的根游戏对象。 /// public GameObject loadingUI; /// /// 用于显示加载进度的自定义进度条组件。 /// 请参阅类注释以了解其声明类型与预期API的差异。 /// public ColorBar progressBar; /// /// 用于显示当前加载步骤描述的文本组件。 /// public TMP_Text describeText; /// /// 进度条每个加载步骤的平滑过渡时间(秒)。 /// public float duration = 0.5f; /// /// 加载完成后UI元素渐隐的时间(秒)。 /// public float fadeDuration = 2f; private float _currentProgressValue; // 实际的当前进度值(在0到1之间) private Color _initialProgressBarColor1; // 进度条颜色1的原始值,用于渐变 private Color _initialProgressBarColor2; // 进度条颜色2的原始值,用于渐变 private Color _initialTextColor; // 描述文本的原始颜色,用于渐隐效果 /// /// 存储所有需要在启动时加载和在重载时清理的管理器实例列表。 /// private List _managersToLoad; /// /// 获取或设置加载进度值,范围为0到1。 /// 设置此属性将更新进度条的视觉显示和颜色。 /// public float Progress { set { // 确保进度值在0到1之间,提高系统健壮性 _currentProgressValue = Mathf.Clamp01(value); if (progressBar != null) // 对外部引用的组件进行空检查是必要的 { // 根据进度值平滑改变进度条的颜色 if (_currentProgressValue < 0.5f) { // 前半段:color2 从初始色渐变到白色 progressBar.color2 = Color.Lerp(_initialProgressBarColor2, Color.white, _currentProgressValue * 2); } else { progressBar.color2 = Color.white; // 后半段:color2 保持白色,color1 从初始色渐变到白色 progressBar.color1 = Color.Lerp(_initialProgressBarColor1, Color.white, (_currentProgressValue - 0.5f) * 2); } // 通知自定义进度条组件更新显示。 // 注意:UnityEngine.Gradient 作为数据结构没有 Refresh() 方法, // 此调用表明 progressBar 实际上预期是一个具有此方法的自定义组件。 progressBar.Refresh(); } } get => _currentProgressValue; } /// /// 获取或设置UI元素的整体不透明度,范围0(完全透明)到1(完全不透明)。 /// 设置此属性将更新进度条和描述文本的透明度。 /// public float Opacity { set { var alpha = Mathf.Clamp01(value); // 确保透明度值在0到1之间 if (progressBar != null) // 对外部引用的组件进行空检查是必要的 { // 更新进度条颜色的透明度 Color c1 = progressBar.color1; c1.a = alpha; progressBar.color1 = c1; Color c2 = progressBar.color2; c2.a = alpha; progressBar.color2 = c2; // 通知自定义进度条组件更新显示。 // 注意:UnityEngine.Gradient 作为数据结构没有 Refresh() 方法, // 此调用表明 progressBar 实际上预期是一个具有此方法的自定义组件。 progressBar.Refresh(); } if (describeText != null) // 对外部引用的组件进行空检查是必要的 { // 更新描述文本颜色的透明度 // 渐隐时,描述文本的透明度在 Opacity > 0.5f 时才开始从0渐变到其原始透明度 var textAlpha = alpha > 0.5f ? Mathf.Lerp(0f, _initialTextColor.a, (alpha - 0.5f) * 2) : 0f; describeText.color = new Color(_initialTextColor.r, _initialTextColor.g, _initialTextColor.b, textAlpha); } } } /// /// 当脚本实例被启用时调用一次。 /// 用于初始化管理器列表并缓存UI元素的初始颜色。 /// private void Awake() { // 在 Awake 中初始化管理器列表,确保在 Start 之前完成所有管理器实例的注册 _managersToLoad = new List { new LoggerManagerWrapper(), // Unity日志的包装器 ConfigManager.Instance, DefineManager.Instance, PackagesImageManager.Instance, TileManager.Instance, AffiliationManager.Instance, ItemResourceManager.Instance, EventManager.Instance, AudioManager.Instance, SkillTreeManager.Instance, KeyValueArchiveManager.Instance, SaveManager.Instance, // 存档管理器应在其他依赖管理器之后加载 HediffManager.Instance }; // 缓存UI的初始颜色,以便后续操作(如渐隐)或重置 // 对UnityEngine.Object的组件进行空检查是必要的 _initialTextColor = (describeText != null) ? describeText.color : Color.white; if (progressBar != null) // 对UnityEngine.Object的组件进行空检查是必要的 { _initialProgressBarColor1 = progressBar.color1; _initialProgressBarColor2 = progressBar.color2; } else { // 警告日志:自定义进度条组件未赋值给启动器。 Debug.LogWarning("自定义进度条组件未赋值给启动器,进度条可能无法正常显示。"); } } /// /// 在Awake方法之后、首次帧更新之前调用。 /// 用于判断是否需要执行完整的加载流程,并根据需要启动加载或隐藏加载UI。 /// private void Start() { // Program.Instance 假设是一个单例,不进行空检查 // Program.Instance.NeedLoad 为 false,表示游戏已加载或不需要重新加载,直接隐藏加载UI if (!Program.Instance.NeedLoad) { loadingUI.SetActive(false); return; } // 如果需要加载,则先清理所有管理器(用于重载或确保干净启动),再启动加载流程 ClearAllManagers(); // 初始化游戏设置 (Setting.Instance 假设是一个单例,不进行空检查) Setting.Instance.Init(); #if !DEBUG // 在非DEBUG模式下,从游戏设置中获取加载和渐隐的持续时间 (Setting.Instance 假设是一个单例,不进行空检查) duration = Setting.Instance.CurrentSettings.progressStepDuration; fadeDuration = Setting.Instance.CurrentSettings.exitAnimationDuration; #endif Load(); // 启动加载流程(内部会调用LoadAllManagers) Program.Instance.NeedLoad = false; // 加载完成后重置加载标志 } /// /// 启动游戏的加载流程。 /// 该方法会激活加载UI,重置进度与透明度,并启动所有管理器的异步加载协程。 /// public void Load() { loadingUI.SetActive(true); // 激活加载UI Progress = 0f; // 将进度重置为0 Opacity = 1f; // 将UI透明度重置为完全不透明 StartCoroutine(LoadAllManagers()); } /// /// 清理所有管理器,然后重新加载游戏数据。 /// 此方法适用于游戏重载、场景切换后需要重新初始化所有数据的情况。 /// public void Reload() { // 日志:正在执行所有管理器的完整重新加载... Debug.Log("正在执行所有管理器的完整重新加载..."); StopAllCoroutines(); // 停止当前所有正在运行的加载协程,避免冲突 ClearAllManagers(); // 清理所有管理器数据 Load(); // 重新加载游戏数据 } /// /// 遍历所有已注册的管理器,并安全地调用它们的 Clear 方法以释放资源或重置状态。 /// private void ClearAllManagers() { foreach (var manager in _managersToLoad) try { manager.Clear(); } catch (Exception ex) { // 错误日志:清理管理器时发生异常。 Debug.LogError( $"清理管理器 '{manager.StepDescription}' 时出错: {ex.Message}\n{ex.StackTrace}"); } } /// /// 协程:按顺序加载所有注册的管理器。 /// 该方法会在加载过程中更新描述文本、平滑过渡进度条,并安全初始化每个管理器。 /// /// 一个 ,用于协程。 private IEnumerator LoadAllManagers() { for (var i = 0; i < _managersToLoad.Count; i++) { var manager = _managersToLoad[i]; // 更新描述文本,显示当前正在加载的管理器步骤描述 // 对TMP_Text组件进行空检查是必要的 if (describeText) describeText.text = manager.StepDescription; // 计算当前阶段的目标进度值 var targetProgress = (float)(i + 1) / _managersToLoad.Count; // 平滑过渡进度条到下一个目标进度 yield return SmoothTransitionTo(targetProgress); // 初始化对应的管理器,并处理可能发生的异常 // InitializeManagerSafely现在内部会等待异步任务完成 yield return InitializeManagerSafely(manager); // yield return new WaitForSeconds(0.1f); // 此行代码可用于模拟每个管理器加载的耗时,默认不启用。 } // 所有管理器加载完成后的处理 if (describeText != null) // 对TMP_Text组件进行空检查是必要的 describeText.text = "加载完成!"; // 确保进度条最终达到100% Progress = 1f; // 启动加载UI的渐隐效果 yield return FadeOutProgressBar(); // 所有加载和动画结束后,可以考虑卸载加载UI,或跳转到主场景 // ToScene("MainGameScene"); // 如果有需要,可以在此处调用静态方法跳转到主游戏场景 } /// /// 尝试安全地初始化单个管理器实例,并捕获任何可能发生的异常。 /// 如果初始化失败,将记录错误日志并更新UI提示。 /// /// 要初始化的管理器实例。 /// 一个 ,用于协程。 private IEnumerator InitializeManagerSafely(ILaunchManager manager) { // 启动异步初始化任务,不再直接同步调用 var initTask = manager.Init(); // 持续等待,直到任务完成 (非阻塞式等待) while (!initTask.IsCompleted) { yield return null; // 等待下一帧 } // 检查任务是否因异常而失败 if (initTask.IsFaulted) { // 提取任务链中的实际异常信息,AggregateException可能包含多个内部异常 // 使用 ?? 运算符确保即使 InnerException 为 null 也能获取到异常信息 var initException = initTask.Exception.InnerException ?? initTask.Exception; Debug.LogError( $"初始化管理器 '{manager.StepDescription}' 时出错: {initException.Message}\n{initException.StackTrace}"); if (describeText) // 对UnityEngine.Object的组件进行空检查是必要的 describeText.text = $"{manager.StepDescription} (初始化失败)"; } else if (initTask.IsCanceled) // 额外增加对取消状态的处理 { Debug.LogWarning($"初始化管理器 '{manager.StepDescription}' 被取消。"); if (describeText) // 对UnityEngine.Object的组件进行空检查是必要的 describeText.text = $"{manager.StepDescription} (已取消)"; } // 协程会自然结束,不再需要额外的 yield return null } /// /// 协程:平滑地过渡进度条的当前值到指定的目标进度。 /// 使用 Mathf.SmoothStep 实现自然加速和减速的过渡效果。 /// /// 目标进度值 (0到1之间)。 /// 一个 ,用于协程。 private IEnumerator SmoothTransitionTo(float targetProgress) { var startProgress = _currentProgressValue; // 获取当前进度作为过渡的起始点 var elapsedTime = 0f; while (elapsedTime < duration) { elapsedTime += Time.deltaTime; // 使用 SmoothStep 实现更自然的加速和减速过渡效果 var t = Mathf.SmoothStep(0f, 1f, elapsedTime / duration); Progress = Mathf.Lerp(startProgress, targetProgress, t); // 在起始和目标进度之间插值 yield return null; } Progress = targetProgress; // 确保最终进度精确达到目标值 } /// /// 协程:平滑渐隐加载UI的所有元素,使其透明度从完全不透明过渡到完全透明。 /// /// 一个 ,用于协程。 private IEnumerator FadeOutProgressBar() { var elapsedTime = 0f; while (elapsedTime < fadeDuration) { elapsedTime += Time.deltaTime; // 使用 SmoothStep 实现平滑渐隐 var t = Mathf.SmoothStep(0f, 1f, elapsedTime / fadeDuration); Opacity = 1f - t; // 透明度从 1 线性渐变到 0 yield return null; } Opacity = 0f; // 确保最终不透明度为 0 loadingUI.SetActive(false); // 完全隐藏加载UI } /// /// 静态方法:加载指定名称的Unity场景。 /// /// 要加载的场景名称。 public static void ToScene(string scene) { SceneManager.LoadScene(scene); } } }