剧情+对话完善
This commit is contained in:
@@ -1,5 +1,6 @@
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using Ichni.Menu.UI;
|
||||
using TMPro;
|
||||
using UnityEngine;
|
||||
using UnityEngine.Localization;
|
||||
@@ -44,14 +45,18 @@ namespace Ichni.Story.UI
|
||||
private sealed class RuntimeMarker
|
||||
{
|
||||
public StoryTimelineMarkerDefinition definition;
|
||||
public StoryBlockDefinition anchorDefinition;
|
||||
public StoryBlockView anchorView;
|
||||
public StoryTimelineMarkerView view;
|
||||
}
|
||||
|
||||
private readonly List<RuntimeMarker> _markers = new List<RuntimeMarker>();
|
||||
private StoryTreeController _treeController;
|
||||
private ScrollRect _subscribedScrollRect;
|
||||
|
||||
// StoryPage 在进入对话时会被 FadeOut 并暂时 SetActive(false)。
|
||||
// 因此需要将“应监听的 ScrollRect”与“当前是否已挂接监听”分开保存:
|
||||
// OnDisable 只解除事件,OnEnable 再恢复同一个目标的事件,不能丢失该引用。
|
||||
private ScrollRect _storyScrollRect;
|
||||
private bool _isScrollRectSubscribed;
|
||||
|
||||
// 重复刻度的初始视觉状态。它们只在当前章节 Timeline 存在期间有效,
|
||||
// Clear 时会恢复,避免重复进入章节后不断累积额外宽度。
|
||||
@@ -64,12 +69,17 @@ namespace Ichni.Story.UI
|
||||
private void OnEnable()
|
||||
{
|
||||
LocalizationSettings.SelectedLocaleChanged += OnSelectedLocaleChanged;
|
||||
SubscribeToStoryScrollRect();
|
||||
|
||||
// StoryPage 从对话界面返回后,Canvas 与 StoryTree 会在同一帧重新启用。
|
||||
// 立即按锚定 Block 的当前实际位置刷新一次,避免首帧沿用淡出前的坐标。
|
||||
RefreshMarkerPositions();
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
LocalizationSettings.SelectedLocaleChanged -= OnSelectedLocaleChanged;
|
||||
SubscribeScrollRect(null);
|
||||
UnsubscribeFromStoryScrollRect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -80,7 +90,7 @@ namespace Ichni.Story.UI
|
||||
{
|
||||
Clear();
|
||||
_treeController = treeController;
|
||||
SubscribeScrollRect(storyScrollRect);
|
||||
SetStoryScrollRect(storyScrollRect);
|
||||
|
||||
if (markerContainer == null || markerPrefab == null || storyData == null)
|
||||
{
|
||||
@@ -121,6 +131,7 @@ namespace Ichni.Story.UI
|
||||
|
||||
_markers.Clear();
|
||||
_treeController = null;
|
||||
SetStoryScrollRect(null);
|
||||
|
||||
if (progressText != null)
|
||||
{
|
||||
@@ -142,6 +153,8 @@ namespace Ichni.Story.UI
|
||||
StoryBlockState state = marker.anchorView.state;
|
||||
bool visible = state == StoryBlockState.Current || state == StoryBlockState.Completed;
|
||||
marker.view.SetVisible(visible);
|
||||
marker.view.SetInteractable(visible && _treeController != null &&
|
||||
_treeController.HasRollbackSnapshot(marker.definition.markerId));
|
||||
|
||||
if (state == StoryBlockState.Completed && marker.definition.contributesToMainProgress)
|
||||
{
|
||||
@@ -344,16 +357,47 @@ namespace Ichni.Story.UI
|
||||
view.name = $"StoryTimelineMarker_{marker.markerId}";
|
||||
view.SetLabel(GetLocalizedLabel(marker.labelKey));
|
||||
|
||||
_markers.Add(new RuntimeMarker
|
||||
RuntimeMarker runtimeMarker = new RuntimeMarker
|
||||
{
|
||||
definition = marker,
|
||||
anchorDefinition = anchorDefinition,
|
||||
anchorView = anchorView,
|
||||
view = view
|
||||
});
|
||||
};
|
||||
view.BindClick(() => RequestRollback(runtimeMarker));
|
||||
_markers.Add(runtimeMarker);
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 点击可回滚 Marker 后显示通用 SelectionBox。实际恢复由 StoryTreeController 处理,
|
||||
/// 因而 Timeline 只承担 UI 入口,不保存任何独立剧情状态。
|
||||
/// </summary>
|
||||
private void RequestRollback(RuntimeMarker marker)
|
||||
{
|
||||
if (marker == null || _treeController == null ||
|
||||
!_treeController.HasRollbackSnapshot(marker.definition.markerId))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
MessageUIPage messageUIPage = MessageUIPage.instance;
|
||||
if (messageUIPage == null)
|
||||
{
|
||||
Debug.LogWarning("[StoryTimeline] MessageUIPage 未就绪,无法显示剧情回滚确认框。");
|
||||
return;
|
||||
}
|
||||
|
||||
string markerLabel = GetLocalizedLabel(marker.definition.labelKey);
|
||||
messageUIPage.ShowSelectionBox(
|
||||
"重开本章节",
|
||||
$"将重置“{markerLabel}”所在列及其之后的剧情选择与进度。歌曲成绩、解锁内容、设置和其它章节不会受到影响。",
|
||||
new List<SelectionBoxOption>
|
||||
{
|
||||
new SelectionBoxOption("重开此时间点", () => _treeController.TryRollbackToMarker(marker.definition.markerId)),
|
||||
new SelectionBoxOption("取消", () => { })
|
||||
});
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 使用 StoryTimeline 指定的 Unity Localization String Table 解析 Label Key。
|
||||
/// String Table 或条目尚未配置时回退显示 Key,确保开发阶段仍能从 UI 上定位缺失翻译。
|
||||
@@ -385,23 +429,54 @@ namespace Ichni.Story.UI
|
||||
}
|
||||
}
|
||||
|
||||
private void SubscribeScrollRect(ScrollRect scrollRect)
|
||||
/// <summary>
|
||||
/// 设置当前章节 StoryTree 使用的 ScrollRect。
|
||||
/// 该引用在 Timeline 暂时禁用期间仍会保留,以便 StoryPage 恢复显示时重新建立位置同步。
|
||||
/// </summary>
|
||||
private void SetStoryScrollRect(ScrollRect scrollRect)
|
||||
{
|
||||
if (_subscribedScrollRect == scrollRect)
|
||||
if (_storyScrollRect == scrollRect)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
if (_subscribedScrollRect != null)
|
||||
UnsubscribeFromStoryScrollRect();
|
||||
_storyScrollRect = scrollRect;
|
||||
SubscribeToStoryScrollRect();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 当 Timeline 处于启用状态时挂接 StoryTree 的拖动回调。
|
||||
/// 使用独立标志防止 OnEnable、Build 等生命周期入口重复注册同一个监听。
|
||||
/// </summary>
|
||||
private void SubscribeToStoryScrollRect()
|
||||
{
|
||||
if (_isScrollRectSubscribed || !isActiveAndEnabled || _storyScrollRect == null)
|
||||
{
|
||||
_subscribedScrollRect.onValueChanged.RemoveListener(OnStoryTreeScrolled);
|
||||
return;
|
||||
}
|
||||
|
||||
_subscribedScrollRect = scrollRect;
|
||||
if (_subscribedScrollRect != null)
|
||||
_storyScrollRect.onValueChanged.AddListener(OnStoryTreeScrolled);
|
||||
_isScrollRectSubscribed = true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 仅移除运行时事件监听,不清空 <see cref="_storyScrollRect"/>。
|
||||
/// 对话期间 StoryPage 被临时隐藏后,OnEnable 可据此自动恢复 Marker 与 Block 的同步。
|
||||
/// </summary>
|
||||
private void UnsubscribeFromStoryScrollRect()
|
||||
{
|
||||
if (!_isScrollRectSubscribed)
|
||||
{
|
||||
_subscribedScrollRect.onValueChanged.AddListener(OnStoryTreeScrolled);
|
||||
return;
|
||||
}
|
||||
|
||||
if (_storyScrollRect != null)
|
||||
{
|
||||
_storyScrollRect.onValueChanged.RemoveListener(OnStoryTreeScrolled);
|
||||
}
|
||||
|
||||
_isScrollRectSubscribed = false;
|
||||
}
|
||||
|
||||
private void OnStoryTreeScrolled(Vector2 _)
|
||||
|
||||
Reference in New Issue
Block a user