剧情+对话完善
This commit is contained in:
@@ -24,9 +24,19 @@ namespace Ichni.Story.UI
|
||||
|
||||
[Header("推进按钮")]
|
||||
public UnityEngine.UI.Button advanceButton;
|
||||
[Tooltip("点击后自动快进,直到遇到选项")]
|
||||
[Tooltip("点击后自动快进;已记忆选项会自动跨过,遇到需要玩家选择的新选项时停止。")]
|
||||
public UnityEngine.UI.Button fastForwardButton;
|
||||
|
||||
[Header("对话辅助功能")]
|
||||
[Tooltip("中途退出当前 TextBlock;本次尚未完成的选项、变量和进度会恢复到进入前状态。")]
|
||||
[SerializeField] private UnityEngine.UI.Button backButton;
|
||||
|
||||
[Tooltip("打开当前一次 TextBlock 的临时对话记录。")]
|
||||
[SerializeField] private UnityEngine.UI.Button showHistoryButton;
|
||||
|
||||
[Tooltip("覆盖在对话上方的记录面板;它不是独立 UIPage。")]
|
||||
[SerializeField] private DialogueHistoryPage dialogueHistoryPage;
|
||||
|
||||
[Header("选项区")]
|
||||
public GameObject choiceFrame;
|
||||
public ChoiceButton[] choiceButtons = new ChoiceButton[4];
|
||||
@@ -58,6 +68,27 @@ namespace Ichni.Story.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (backButton != null)
|
||||
backButton.onClick.AddListener(OnBackButtonClicked);
|
||||
|
||||
if (showHistoryButton != null)
|
||||
showHistoryButton.onClick.AddListener(OnShowHistoryButtonClicked);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (backButton != null)
|
||||
backButton.onClick.RemoveListener(OnBackButtonClicked);
|
||||
|
||||
if (showHistoryButton != null)
|
||||
showHistoryButton.onClick.RemoveListener(OnShowHistoryButtonClicked);
|
||||
|
||||
if (instance == this)
|
||||
instance = null;
|
||||
}
|
||||
|
||||
public void PlayFadeIn(UnityAction onComplete = null)
|
||||
{
|
||||
FadeIn(0.2f, false, onComplete);
|
||||
@@ -67,5 +98,26 @@ namespace Ichni.Story.UI
|
||||
{
|
||||
FadeOut(0.2f, false, onComplete);
|
||||
}
|
||||
|
||||
private void OnBackButtonClicked()
|
||||
{
|
||||
// History 覆盖层本身会拦截输入;此处额外保护,避免错误配置导致 Back 穿透到底层对话。
|
||||
if (dialogueHistoryPage != null && dialogueHistoryPage.IsOpen)
|
||||
return;
|
||||
|
||||
StoryDialogueController.instance?.CancelActiveDialogue();
|
||||
}
|
||||
|
||||
private void OnShowHistoryButtonClicked()
|
||||
{
|
||||
if (dialogueHistoryPage == null || dialogueHistoryPage.IsOpen)
|
||||
return;
|
||||
|
||||
// 打开记录不会推进 Yarn;仅取消快进并补全正在显示的一句,避免记录与画面不同步。
|
||||
StoryDialogueController.instance?.dialogueRunner
|
||||
?.GetComponent<VNDialoguePresenter>()
|
||||
?.PrepareForHistory();
|
||||
dialogueHistoryPage.Open(DialogueHistory.Entries);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
36
Assets/Scripts/NewStorySystem/UI/DialogueHistoryEntryView.cs
Normal file
36
Assets/Scripts/NewStorySystem/UI/DialogueHistoryEntryView.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using TMPro;
|
||||
using Ichni.Story.Dialogue;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>DialogHistoryPage 中单条记录的 Prefab 视图。</summary>
|
||||
public class DialogueHistoryEntryView : MonoBehaviour
|
||||
{
|
||||
[Tooltip("普通台词显示的说话者文本;选项记录会自动隐藏。")]
|
||||
[SerializeField] private TMP_Text speakerText;
|
||||
|
||||
[Tooltip("台词或玩家已采用选项的正文文本。")]
|
||||
[SerializeField] private TMP_Text contentText;
|
||||
|
||||
[Tooltip("可选:仅在玩家选项记录时显示的视觉标识。")]
|
||||
[SerializeField] private GameObject choiceIndicator;
|
||||
|
||||
/// <summary>将一条临时对话记录映射到该视图。</summary>
|
||||
public void SetUp(DialogueHistoryEntry entry)
|
||||
{
|
||||
bool isChoice = entry != null && entry.isChoice;
|
||||
if (speakerText != null)
|
||||
{
|
||||
speakerText.gameObject.SetActive(!isChoice && !string.IsNullOrEmpty(entry?.speakerName));
|
||||
speakerText.text = entry?.speakerName ?? string.Empty;
|
||||
}
|
||||
|
||||
if (contentText != null)
|
||||
contentText.text = entry?.content ?? string.Empty;
|
||||
|
||||
if (choiceIndicator != null)
|
||||
choiceIndicator.SetActive(isChoice);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: eaf151163c994d31bfabde12ede0fa98
|
||||
87
Assets/Scripts/NewStorySystem/UI/DialogueHistoryPage.cs
Normal file
87
Assets/Scripts/NewStorySystem/UI/DialogueHistoryPage.cs
Normal file
@@ -0,0 +1,87 @@
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Story.Dialogue;
|
||||
using UnityEngine;
|
||||
using UnityEngine.UI;
|
||||
|
||||
namespace Ichni.Story.UI
|
||||
{
|
||||
/// <summary>
|
||||
/// 对话记录覆盖面板。
|
||||
/// <para>它不是独立 UIPage:打开时覆盖在 DialogUIPage 上方,并由全屏 Raycast Target 阻断底层输入;
|
||||
/// 关闭后仅恢复对话画面,不推进 Yarn 对话。</para>
|
||||
/// </summary>
|
||||
public class DialogueHistoryPage : MonoBehaviour
|
||||
{
|
||||
[Header("References")]
|
||||
[Tooltip("覆盖全屏的 CanvasGroup。根物体应位于 DialogCanvas 顶层,并包含可接收 Raycast 的全屏透明 Image。")]
|
||||
[SerializeField] private CanvasGroup canvasGroup;
|
||||
[SerializeField] private ScrollRect scrollRect;
|
||||
[SerializeField] private RectTransform entryContainer;
|
||||
[SerializeField] private DialogueHistoryEntryView entryPrefab;
|
||||
[SerializeField] private Button closeButton;
|
||||
|
||||
/// <summary>当前覆盖层是否正在显示并拦截 DialogUIPage 输入。</summary>
|
||||
public bool IsOpen => _isOpen;
|
||||
|
||||
private readonly List<DialogueHistoryEntryView> _spawnedEntries = new();
|
||||
private bool _isOpen;
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (closeButton != null)
|
||||
closeButton.onClick.AddListener(Close);
|
||||
|
||||
SetVisible(false);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
if (closeButton != null)
|
||||
closeButton.onClick.RemoveListener(Close);
|
||||
}
|
||||
|
||||
/// <summary>以当前一次 TextBlock 对话的内存记录刷新并打开覆盖面板。</summary>
|
||||
public void Open(IReadOnlyList<DialogueHistoryEntry> entries)
|
||||
{
|
||||
RebuildEntries(entries);
|
||||
SetVisible(true);
|
||||
Canvas.ForceUpdateCanvases();
|
||||
|
||||
// 记录按时间从上到下排列;打开后停在最早一条,玩家可向下浏览最新内容。
|
||||
if (scrollRect != null)
|
||||
scrollRect.verticalNormalizedPosition = 1f;
|
||||
}
|
||||
|
||||
/// <summary>关闭覆盖面板,不修改当前 Yarn 进度或选项状态。</summary>
|
||||
public void Close() => SetVisible(false);
|
||||
|
||||
private void RebuildEntries(IReadOnlyList<DialogueHistoryEntry> entries)
|
||||
{
|
||||
foreach (DialogueHistoryEntryView view in _spawnedEntries)
|
||||
if (view != null)
|
||||
Destroy(view.gameObject);
|
||||
_spawnedEntries.Clear();
|
||||
|
||||
if (entries == null || entryPrefab == null || entryContainer == null)
|
||||
return;
|
||||
|
||||
foreach (DialogueHistoryEntry entry in entries)
|
||||
{
|
||||
DialogueHistoryEntryView view = Instantiate(entryPrefab, entryContainer);
|
||||
view.SetUp(entry);
|
||||
_spawnedEntries.Add(view);
|
||||
}
|
||||
}
|
||||
|
||||
private void SetVisible(bool visible)
|
||||
{
|
||||
_isOpen = visible;
|
||||
if (canvasGroup == null)
|
||||
return;
|
||||
|
||||
canvasGroup.alpha = visible ? 1f : 0f;
|
||||
canvasGroup.interactable = visible;
|
||||
canvasGroup.blocksRaycasts = visible;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: b01a0f4f2bd84076a5fa1e6c43a21d47
|
||||
Reference in New Issue
Block a user