设置扩展
This commit is contained in:
@@ -3,7 +3,6 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using AK.Wwise;
|
||||
using Ichni.Menu;
|
||||
using Ichni.Menu.UI;
|
||||
using Ichni.Story;
|
||||
using Ichni.Story.UI;
|
||||
using Ichni.UI;
|
||||
@@ -24,7 +23,6 @@ namespace Ichni
|
||||
public LoginPage loginPage;
|
||||
public ChapterSelectionUIPage chapterSelectionUIPage;
|
||||
public StoryUIPage storyUIPage;
|
||||
public SelectionBoxUIPage selectionBoxUIPage;
|
||||
// dialogUIPage 已随旧对话系统移除;阶段 3 将接入新的 VN 对话页面引用
|
||||
public SongSelectionUIPage songSelectionUIPage;
|
||||
public TransitionUIPage transitionUIPage;
|
||||
@@ -88,7 +86,7 @@ namespace Ichni
|
||||
// 返回目标是一次性运行态信息。无论是否能恢复页面,都不能让它在下次重进菜单时重复生效。
|
||||
information?.ClearMenuReturnDestination();
|
||||
|
||||
Application.targetFrameRate = SettingsManager.instance.gameSettings.targetFrame;
|
||||
Application.targetFrameRate = SettingsManager.instance.settingsSaveData.targetFrame;
|
||||
asyncOperation = SceneManager.LoadSceneAsync("GameScene");
|
||||
asyncOperation.allowSceneActivation = false;
|
||||
}
|
||||
|
||||
@@ -1,13 +1,15 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Menu.UI;
|
||||
using Ichni.UI;
|
||||
using Sirenix.OdinInspector;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于在剧情系统中管理并顺序展示 MessageBox 的独立 UI 页面。
|
||||
/// 支持动态实例化给定的 MessageBox Prefab,并在所有消息展示完毕后进行大页面的 FadeOut。
|
||||
/// 剧情与菜单共用的弹窗页面。
|
||||
/// MessageBox 与 SelectionBox 共用同一遮罩、容器和显示队列:两者不会重叠,
|
||||
/// 后续系统只需向本页提交“消息”或“选项”请求,无需再创建平行的 UIPage。
|
||||
/// </summary>
|
||||
public class MessageUIPage : UIPageBase
|
||||
{
|
||||
@@ -20,16 +22,28 @@ namespace Ichni.Story.UI
|
||||
[Tooltip("生成的 MessageBox 挂载在哪?如果不填,默认挂载在自己身上")]
|
||||
public Transform messageContainer;
|
||||
|
||||
// 用于排队的内部消息数据结构
|
||||
private struct PendingMessage
|
||||
[Tooltip("通用多选弹窗预制体。TutorialBlock、奖励选择等系统都通过本页动态生成它。")]
|
||||
public SelectionBox defaultSelectionBoxPrefab;
|
||||
|
||||
private enum PopupType
|
||||
{
|
||||
public string title;
|
||||
public string content;
|
||||
public MessageBox customPrefab; // 支持未来传入不同种类的 Prefab
|
||||
Message,
|
||||
Selection
|
||||
}
|
||||
|
||||
private Queue<PendingMessage> _messageQueue = new Queue<PendingMessage>();
|
||||
private MessageBox _currentActiveBox;
|
||||
// 用于统一排队的内部弹窗数据结构。
|
||||
private struct PendingPopup
|
||||
{
|
||||
public PopupType type;
|
||||
public string title;
|
||||
public string content;
|
||||
public MessageBox messagePrefab;
|
||||
public List<SelectionBoxOption> selectionOptions;
|
||||
}
|
||||
|
||||
private readonly Queue<PendingPopup> _popupQueue = new();
|
||||
private MessageBox _currentMessageBox;
|
||||
private SelectionBox _currentSelectionBox;
|
||||
private bool _isPageActive = false;
|
||||
|
||||
protected override void Awake()
|
||||
@@ -71,6 +85,83 @@ namespace Ichni.Story.UI
|
||||
EnqueueMessage(title, content, defaultMessageBoxPrefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspector 专用的运行时调试入口。
|
||||
/// 通过正常队列创建一条基础测试文本,因此可以同时验证新 Prefab 的布局、
|
||||
/// 入退场动画、输入拦截与队列收尾,而不会绕过正式流程或污染剧情数据。
|
||||
/// </summary>
|
||||
[Button("调试:创建空 MessageBox", ButtonSizes.Medium)]
|
||||
private void DebugCreateEmptyMessageBox()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Debug.LogWarning("[MessageUIPage] 空 MessageBox 调试按钮只能在 Play Mode 中使用。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowCustomMessage(
|
||||
"MessageBox Debug",
|
||||
"这是一条用于检查基础布局、文本换行与 Rail 动效的测试文本。\n\n" +
|
||||
"This is a basic MessageBox test message.");
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Inspector 专用的运行时调试入口。
|
||||
/// 通过正常队列创建一条基础测试文本,因此可以同时验证新 Prefab 的布局、
|
||||
/// 入退场动画、输入拦截与队列收尾,而不会绕过正式流程或污染剧情数据。
|
||||
/// </summary>
|
||||
[Button("调试:创建空 SelectionBox", ButtonSizes.Medium)]
|
||||
private void DebugCreateEmptySelectionBox()
|
||||
{
|
||||
if (!Application.isPlaying)
|
||||
{
|
||||
Debug.LogWarning("[MessageUIPage] 空 SelectionBox 调试按钮只能在 Play Mode 中使用。", this);
|
||||
return;
|
||||
}
|
||||
|
||||
ShowSelectionBox(
|
||||
"SelectionBox Debug",
|
||||
"这是一条用于检查基础布局、文本换行与 Rail 动效的测试文本。\n\n" +
|
||||
"This is a basic SelectionBox test message.",
|
||||
new List<SelectionBoxOption>
|
||||
{
|
||||
new SelectionBoxOption("Option 1", () => Debug.Log("Option 1 selected")),
|
||||
new SelectionBoxOption("Option 2", () => Debug.Log("Option 2 selected")),
|
||||
new SelectionBoxOption("Option 3", () => Debug.Log("Option 3 selected"))
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将一组运行时选项加入通用弹窗队列。
|
||||
/// 若 MessageBox 或其它 SelectionBox 正在显示,选项会按提交顺序等待,而不会叠在同一遮罩上。
|
||||
/// </summary>
|
||||
public bool ShowSelectionBox(string title, string content, IReadOnlyList<SelectionBoxOption> options)
|
||||
{
|
||||
if (defaultSelectionBoxPrefab == null)
|
||||
{
|
||||
Debug.LogError("[MessageUIPage] 未配置 defaultSelectionBoxPrefab,无法显示选项框。");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (options == null || options.Count == 0)
|
||||
{
|
||||
Debug.LogWarning("[MessageUIPage] 未提供任何 SelectionBoxOption,忽略空选项框。");
|
||||
return false;
|
||||
}
|
||||
|
||||
_popupQueue.Enqueue(new PendingPopup
|
||||
{
|
||||
type = PopupType.Selection,
|
||||
title = title,
|
||||
content = content,
|
||||
// 复制调用方传入的列表,防止调用方随后改动集合而改变已排队的玩家选择。
|
||||
selectionOptions = new List<SelectionBoxOption>(options)
|
||||
});
|
||||
|
||||
BeginQueueIfNeeded();
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定消息入队。如果是空闲状态,则唤醒大页面并开始处理队列。
|
||||
/// </summary>
|
||||
@@ -82,22 +173,30 @@ namespace Ichni.Story.UI
|
||||
return;
|
||||
}
|
||||
|
||||
_messageQueue.Enqueue(new PendingMessage
|
||||
_popupQueue.Enqueue(new PendingPopup
|
||||
{
|
||||
type = PopupType.Message,
|
||||
title = title,
|
||||
content = content,
|
||||
customPrefab = prefab
|
||||
messagePrefab = prefab
|
||||
});
|
||||
|
||||
// 如果当前页面没有在显示中,则开始整体流程
|
||||
if (!_isPageActive)
|
||||
{
|
||||
_isPageActive = true;
|
||||
|
||||
// 唤醒大页面(执行 UIPageBase 的渐入并拦截底层射线)
|
||||
this.FadeIn(0.5f, false, ShowNextInQueue);
|
||||
}
|
||||
|
||||
BeginQueueIfNeeded();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 若当前没有弹窗,激活共享遮罩后开始依次显示队列。遮罩从出现瞬间拦截输入,
|
||||
/// 避免 UIPageBase 默认淡入结束前的短暂底层点击窗口。
|
||||
/// </summary>
|
||||
private void BeginQueueIfNeeded()
|
||||
{
|
||||
if (_isPageActive) return;
|
||||
|
||||
_isPageActive = true;
|
||||
mainCanvasGroup.gameObject.SetActive(true);
|
||||
mainCanvasGroup.interactable = true;
|
||||
mainCanvasGroup.blocksRaycasts = true;
|
||||
FadeIn(0f, false, ShowNextInQueue);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -105,40 +204,85 @@ namespace Ichni.Story.UI
|
||||
/// </summary>
|
||||
private void ShowNextInQueue()
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
if (_popupQueue.Count == 0)
|
||||
{
|
||||
// 队列处理完毕,大页面整体退场
|
||||
_currentActiveBox = null;
|
||||
// 队列处理完毕,大页面整体退场。
|
||||
_currentMessageBox = null;
|
||||
_currentSelectionBox = null;
|
||||
_isPageActive = false;
|
||||
this.FadeOut();
|
||||
FadeOut(0f);
|
||||
return;
|
||||
}
|
||||
|
||||
PendingMessage msg = _messageQueue.Dequeue();
|
||||
|
||||
// 实例化预制体
|
||||
_currentActiveBox = Instantiate(msg.customPrefab, messageContainer);
|
||||
|
||||
// 订阅“当这个消息框彻底结束关闭时”的事件
|
||||
_currentActiveBox.onAllMessagesClosed.AddListener(() =>
|
||||
PendingPopup popup = _popupQueue.Dequeue();
|
||||
if (popup.type == PopupType.Selection)
|
||||
{
|
||||
// 销毁旧实例
|
||||
if (_currentActiveBox != null)
|
||||
{
|
||||
Destroy(_currentActiveBox.gameObject);
|
||||
}
|
||||
|
||||
// 检查并显示下一个
|
||||
ShowNextInQueue();
|
||||
});
|
||||
ShowSelectionPopup(popup);
|
||||
return;
|
||||
}
|
||||
|
||||
// 配置并启动 MessageBox
|
||||
_currentActiveBox.Clear();
|
||||
_currentActiveBox.AddInfo(msg.title, msg.content, null);
|
||||
|
||||
// MessageBox.SetUp() 内部会激活自己并执行自己的 FadeIn
|
||||
_currentActiveBox.gameObject.SetActive(true);
|
||||
_currentActiveBox.SetUp();
|
||||
ShowMessagePopup(popup);
|
||||
}
|
||||
|
||||
private void ShowMessagePopup(PendingPopup popup)
|
||||
{
|
||||
if (popup.messagePrefab == null)
|
||||
{
|
||||
Debug.LogError("[MessageUIPage] 队列中的 MessageBox Prefab 已丢失,跳过该消息。");
|
||||
ShowNextInQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
MessageBox messageBox = Instantiate(popup.messagePrefab, messageContainer);
|
||||
_currentMessageBox = messageBox;
|
||||
|
||||
// MessageBox 只显示一条消息;队列顺序始终由本页统一管理。
|
||||
messageBox.Closed += HandleMessageBoxClosed;
|
||||
messageBox.gameObject.SetActive(true);
|
||||
messageBox.SetUp(popup.title, popup.content);
|
||||
}
|
||||
|
||||
private void HandleMessageBoxClosed(MessageBox messageBox)
|
||||
{
|
||||
messageBox.Closed -= HandleMessageBoxClosed;
|
||||
Destroy(messageBox.gameObject);
|
||||
|
||||
if (_currentMessageBox == messageBox)
|
||||
_currentMessageBox = null;
|
||||
|
||||
ShowNextInQueue();
|
||||
}
|
||||
|
||||
private void ShowSelectionPopup(PendingPopup popup)
|
||||
{
|
||||
if (defaultSelectionBoxPrefab == null)
|
||||
{
|
||||
Debug.LogError("[MessageUIPage] 队列中的 SelectionBox Prefab 已丢失,跳过该选项框。");
|
||||
ShowNextInQueue();
|
||||
return;
|
||||
}
|
||||
|
||||
SelectionBox selectionBox = Instantiate(defaultSelectionBoxPrefab, messageContainer);
|
||||
_currentSelectionBox = selectionBox;
|
||||
selectionBox.Closed += HandleSelectionBoxClosed;
|
||||
|
||||
if (!selectionBox.SetUp(popup.title, popup.content, popup.selectionOptions))
|
||||
{
|
||||
selectionBox.Closed -= HandleSelectionBoxClosed;
|
||||
Destroy(selectionBox.gameObject);
|
||||
_currentSelectionBox = null;
|
||||
ShowNextInQueue();
|
||||
}
|
||||
}
|
||||
|
||||
private void HandleSelectionBoxClosed(SelectionBox selectionBox)
|
||||
{
|
||||
if (selectionBox != _currentSelectionBox) return;
|
||||
|
||||
selectionBox.Closed -= HandleSelectionBoxClosed;
|
||||
Destroy(selectionBox.gameObject);
|
||||
_currentSelectionBox = null;
|
||||
ShowNextInQueue();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user