StorySystem
This commit is contained in:
71
Assets/Scripts/NewStorySystem/UI/DialogUIPage.cs
Normal file
71
Assets/Scripts/NewStorySystem/UI/DialogUIPage.cs
Normal file
@@ -0,0 +1,71 @@
|
||||
using Ichni.Story.Dialogue;
|
||||
using Ichni.UI;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Events;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话系统专用的 UI 页面容器。
|
||||
/// 接管整个对话面板的淡入淡出及交互状态控制,
|
||||
/// 将显示生命周期与对话流逻辑 (VNDialoguePresenter) 彻底解耦。
|
||||
/// </summary>
|
||||
public class DialogUIPage : UIPageBase
|
||||
{
|
||||
public static DialogUIPage instance;
|
||||
|
||||
[Header("立绘舞台")]
|
||||
public VNPortraitStage portraitStage;
|
||||
|
||||
[Header("台词区")]
|
||||
public TMPro.TextMeshProUGUI dialogueText;
|
||||
public GameObject speakerContainer;
|
||||
public TMPro.TextMeshProUGUI speakerText;
|
||||
|
||||
[Header("推进按钮")]
|
||||
public UnityEngine.UI.Button advanceButton;
|
||||
[Tooltip("点击后自动快进,直到遇到选项")]
|
||||
public UnityEngine.UI.Button fastForwardButton;
|
||||
|
||||
[Header("选项区")]
|
||||
public GameObject choiceFrame;
|
||||
public ChoiceButton[] choiceButtons = new ChoiceButton[4];
|
||||
|
||||
[Header("动态组件")]
|
||||
public DialogWheel dialogWheel;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
// 默认启动时强制隐藏
|
||||
if (mainCanvasGroup != null)
|
||||
{
|
||||
mainCanvasGroup.alpha = 0f;
|
||||
mainCanvasGroup.interactable = false;
|
||||
mainCanvasGroup.blocksRaycasts = false;
|
||||
mainCanvasGroup.gameObject.SetActive(false);
|
||||
}
|
||||
}
|
||||
|
||||
public void PlayFadeIn(UnityAction onComplete = null)
|
||||
{
|
||||
FadeIn(0.2f, false, onComplete);
|
||||
}
|
||||
|
||||
public void PlayFadeOut(UnityAction onComplete = null)
|
||||
{
|
||||
FadeOut(0.2f, false, onComplete);
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/NewStorySystem/UI/DialogUIPage.cs.meta
Normal file
2
Assets/Scripts/NewStorySystem/UI/DialogUIPage.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 0e1e4a2c0a479784e839bdf448215e19
|
||||
72
Assets/Scripts/NewStorySystem/UI/DialogWheel.cs
Normal file
72
Assets/Scripts/NewStorySystem/UI/DialogWheel.cs
Normal file
@@ -0,0 +1,72 @@
|
||||
using DG.Tweening;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话时常驻转动的齿轮/轮盘,以及伴随浮动的指针。
|
||||
/// </summary>
|
||||
public class DialogWheel : MonoBehaviour
|
||||
{
|
||||
[Header("轮盘旋转")]
|
||||
[Tooltip("要旋转的 RectTransform。如果不填,默认获取自身。")]
|
||||
public RectTransform wheelRect;
|
||||
|
||||
[Tooltip("顺时针旋转一整圈(360度)所需的时间(秒)")]
|
||||
public float rotationCycleDuration = 5f;
|
||||
|
||||
[Header("指针浮动")]
|
||||
[Tooltip("常驻上下浮动的指针对象 (RectTransform)")]
|
||||
public RectTransform pointerRect;
|
||||
|
||||
[Tooltip("指针单向浮动的距离(像素)")]
|
||||
public float pointerFloatDistance = 5f;
|
||||
|
||||
[Tooltip("指针完成单向浮动所需的时间")]
|
||||
public float pointerFloatDuration = 0.3f;
|
||||
|
||||
private Tween _rotateTween;
|
||||
private Tween _pointerTween;
|
||||
private float _pointerStartY;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (wheelRect == null) wheelRect = GetComponent<RectTransform>();
|
||||
|
||||
if (pointerRect != null)
|
||||
{
|
||||
_pointerStartY = pointerRect.anchoredPosition.y;
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
// 轮盘常驻匀速旋转
|
||||
if (wheelRect != null && rotationCycleDuration > 0f)
|
||||
{
|
||||
// SetRelative(true) 保证无论当前角度是多少,都在其基础上转 -360 度
|
||||
// LoopType.Restart 配合 Linear 缓动,实现无限丝滑旋转
|
||||
_rotateTween = wheelRect.DORotate(new Vector3(0, 0, -360f), rotationCycleDuration, RotateMode.FastBeyond360)
|
||||
.SetRelative(true)
|
||||
.SetLoops(-1, LoopType.Restart)
|
||||
.SetEase(Ease.Linear);
|
||||
_rotateTween.Play();
|
||||
}
|
||||
|
||||
// 指针常驻浮动,启动后永不停止
|
||||
if (pointerRect != null)
|
||||
{
|
||||
_pointerTween = pointerRect.DOAnchorPosY(_pointerStartY + pointerFloatDistance, pointerFloatDuration)
|
||||
.SetLoops(-1, LoopType.Yoyo)
|
||||
.SetEase(Ease.InOutSine);
|
||||
_pointerTween.Play();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
_rotateTween?.Kill();
|
||||
_pointerTween?.Kill();
|
||||
}
|
||||
}
|
||||
}
|
||||
2
Assets/Scripts/NewStorySystem/UI/DialogWheel.cs.meta
Normal file
2
Assets/Scripts/NewStorySystem/UI/DialogWheel.cs.meta
Normal file
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 027bcd2c2d137f5458f8b12002234663
|
||||
143
Assets/Scripts/NewStorySystem/UI/StoryMessageBoxUIPage.cs
Normal file
143
Assets/Scripts/NewStorySystem/UI/StoryMessageBoxUIPage.cs
Normal file
@@ -0,0 +1,143 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Menu.UI;
|
||||
using Ichni.UI;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于在剧情系统中管理并顺序展示 MessageBox 的独立 UI 页面。
|
||||
/// 支持动态实例化给定的 MessageBox Prefab,并在所有消息展示完毕后进行大页面的 FadeOut。
|
||||
/// </summary>
|
||||
public class StoryMessageBoxUIPage : UIPageBase
|
||||
{
|
||||
public static StoryMessageBoxUIPage instance;
|
||||
|
||||
[Header("Prefabs & Containers")]
|
||||
[Tooltip("默认使用的 MessageBox 预制体(请从 Project 中拖拽,而非场景中的实例)")]
|
||||
public MessageBox defaultMessageBoxPrefab;
|
||||
|
||||
[Tooltip("生成的 MessageBox 挂载在哪?如果不填,默认挂载在自己身上")]
|
||||
public Transform messageContainer;
|
||||
|
||||
// 用于排队的内部消息数据结构
|
||||
private struct PendingMessage
|
||||
{
|
||||
public string title;
|
||||
public string content;
|
||||
public MessageBox customPrefab; // 支持未来传入不同种类的 Prefab
|
||||
}
|
||||
|
||||
private Queue<PendingMessage> _messageQueue = new Queue<PendingMessage>();
|
||||
private MessageBox _currentActiveBox;
|
||||
private bool _isPageActive = false;
|
||||
|
||||
protected override void Awake()
|
||||
{
|
||||
base.Awake();
|
||||
|
||||
if (instance == null)
|
||||
{
|
||||
instance = this;
|
||||
}
|
||||
else
|
||||
{
|
||||
Debug.LogWarning("[StoryMessageBoxUIPage] 场景中存在多个实例,正在销毁多余的实例。");
|
||||
Destroy(gameObject);
|
||||
return;
|
||||
}
|
||||
|
||||
if (messageContainer == null)
|
||||
{
|
||||
messageContainer = this.transform;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将歌曲解锁提示加入弹窗队列。
|
||||
/// </summary>
|
||||
public void ShowUnlockMessage(string songUnlockKey)
|
||||
{
|
||||
string title = "New Song Unlocked!";
|
||||
string content = $"You have unlocked the song: {songUnlockKey}!";
|
||||
EnqueueMessage(title, content, defaultMessageBoxPrefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将自定义文本提示加入弹窗队列。
|
||||
/// </summary>
|
||||
public void ShowCustomMessage(string title, string content)
|
||||
{
|
||||
EnqueueMessage(title, content, defaultMessageBoxPrefab);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将指定消息入队。如果是空闲状态,则唤醒大页面并开始处理队列。
|
||||
/// </summary>
|
||||
private void EnqueueMessage(string title, string content, MessageBox prefab)
|
||||
{
|
||||
if (prefab == null)
|
||||
{
|
||||
Debug.LogError("[StoryMessageBoxUIPage] 没有可用的 MessageBox 预制体!");
|
||||
return;
|
||||
}
|
||||
|
||||
_messageQueue.Enqueue(new PendingMessage
|
||||
{
|
||||
title = title,
|
||||
content = content,
|
||||
customPrefab = prefab
|
||||
});
|
||||
|
||||
// 如果当前页面没有在显示中,则开始整体流程
|
||||
if (!_isPageActive)
|
||||
{
|
||||
_isPageActive = true;
|
||||
|
||||
// 唤醒大页面(执行 UIPageBase 的渐入并拦截底层射线)
|
||||
this.FadeIn(0.5f, false, ShowNextInQueue);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 从队列中取出一个并实例化显示。
|
||||
/// </summary>
|
||||
private void ShowNextInQueue()
|
||||
{
|
||||
if (_messageQueue.Count == 0)
|
||||
{
|
||||
// 队列处理完毕,大页面整体退场
|
||||
_currentActiveBox = null;
|
||||
_isPageActive = false;
|
||||
this.FadeOut();
|
||||
return;
|
||||
}
|
||||
|
||||
PendingMessage msg = _messageQueue.Dequeue();
|
||||
|
||||
// 实例化预制体
|
||||
_currentActiveBox = Instantiate(msg.customPrefab, messageContainer);
|
||||
|
||||
// 订阅“当这个消息框彻底结束关闭时”的事件
|
||||
_currentActiveBox.onAllMessagesClosed.AddListener(() =>
|
||||
{
|
||||
// 销毁旧实例
|
||||
if (_currentActiveBox != null)
|
||||
{
|
||||
Destroy(_currentActiveBox.gameObject);
|
||||
}
|
||||
|
||||
// 检查并显示下一个
|
||||
ShowNextInQueue();
|
||||
});
|
||||
|
||||
// 配置并启动 MessageBox
|
||||
_currentActiveBox.Clear();
|
||||
_currentActiveBox.AddInfo(msg.title, msg.content, null);
|
||||
|
||||
// MessageBox.SetUp() 内部会激活自己并执行自己的 FadeIn
|
||||
_currentActiveBox.gameObject.SetActive(true);
|
||||
_currentActiveBox.SetUp();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 07f2a7ffd4b513d4e90d60a6587914cc
|
||||
Reference in New Issue
Block a user