Files
2026-07-26 18:07:34 +08:00

363 lines
9.9 KiB
C#
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System.Collections.Generic;
using AibisDream.SaveSystem;
using DG.Tweening;
using UnityEngine;
using UnityEngine.UI;
namespace AibisDream.FixSystem
{
public class TaskPanel : MonoBehaviour, ILineView
{
[Header("面板位置")]
[SerializeField] private Transform taskRoot;
[SerializeField] private Transform hiddenAnchor;
[SerializeField] private Transform shownAnchor;
[Header("任务列表")]
[SerializeField] private RectTransform taskListParent;
[SerializeField] private TaskItem taskItemPrefab;
[Header("动画")]
[SerializeField, Min(0f)] private float animationDuration = 1f;
[SerializeField] private Ease animationEase = Ease.OutQuad;
private readonly List<TaskEntry> _tasks = new();
private Tween _moveTween;
private bool _requestedVisible;
private bool _temporarilyHidden;
public bool RequestedVisible => _requestedVisible;
public bool EffectiveVisible => _requestedVisible && !_temporarilyHidden;
private sealed class TaskEntry
{
public string LineId;
public string Text;
public TaskItem View;
}
private void Awake()
{
ValidateConfiguration();
}
private void OnEnable()
{
RefreshVisual(immediate: true);
}
private void OnDisable()
{
KillMoveTween();
}
public void SetVisible(bool visible, bool immediate = false)
{
if (_requestedVisible == visible)
{
if (immediate)
{
RefreshVisual(immediate: true);
}
return;
}
var wasEffectivelyVisible = EffectiveVisible;
_requestedVisible = visible;
if (wasEffectivelyVisible != EffectiveVisible || immediate)
{
RefreshVisual(immediate);
}
}
public void SetTemporarilyHidden(bool hidden, bool immediate = false)
{
if (_temporarilyHidden == hidden)
{
if (immediate)
{
RefreshVisual(immediate: true);
}
return;
}
var wasEffectivelyVisible = EffectiveVisible;
_temporarilyHidden = hidden;
if (wasEffectivelyVisible != EffectiveVisible || immediate)
{
RefreshVisual(immediate);
}
}
public void CompleteTask(string lineId)
{
var index = FindTaskIndex(lineId);
if (index < 0)
{
return;
}
DestroyTaskView(_tasks[index].View);
_tasks.RemoveAt(index);
MarkTaskLayoutDirty();
}
public void ClearTasks()
{
foreach (var task in _tasks)
{
DestroyTaskView(task.View);
}
_tasks.Clear();
MarkTaskLayoutDirty();
}
public void RunLine(LineSyncToken token)
{
var lineId = token.lineInfo.lineId;
if (string.IsNullOrWhiteSpace(lineId))
{
Debug.LogWarning($"[{nameof(TaskPanel)}] 忽略缺少 lineId 的任务文本。", this);
}
else if (FindTaskIndex(lineId) < 0)
{
AddTaskItem(lineId, token.lineInfo.lineText);
}
CompleteLineImmediately(token);
}
public void CaptureSnapshot(out bool requestedVisible, List<TaskEntrySnapshotDto> tasks)
{
requestedVisible = RequestedVisible;
tasks.Clear();
foreach (var task in _tasks)
{
tasks.Add(new TaskEntrySnapshotDto
{
lineId = task.LineId,
text = task.Text
});
}
}
public void ApplySnapshot(bool requestedVisible, List<TaskEntrySnapshotDto> tasks)
{
ClearTasks();
if (tasks != null)
{
foreach (var entry in tasks)
{
if (entry == null || string.IsNullOrWhiteSpace(entry.lineId))
{
Debug.LogWarning($"[{nameof(TaskPanel)}] 跳过缺少 lineId 的任务快照。", this);
continue;
}
if (FindTaskIndex(entry.lineId) >= 0)
{
Debug.LogWarning($"[{nameof(TaskPanel)}] 跳过重复任务快照 '{entry.lineId}'。", this);
continue;
}
AddTaskItem(entry.lineId, entry.text ?? string.Empty);
}
}
SetVisible(requestedVisible, immediate: true);
}
private void RefreshVisual(bool immediate)
{
KillMoveTween();
var visible = EffectiveVisible;
if (visible)
{
SetTaskTextVisible(true);
MarkTaskLayoutDirty();
}
if (taskRoot == null || hiddenAnchor == null || shownAnchor == null)
{
if (!visible)
{
SetTaskTextVisible(false);
}
return;
}
var targetPosition = visible
? shownAnchor.localPosition
: hiddenAnchor.localPosition;
if (immediate || animationDuration <= 0f)
{
taskRoot.localPosition = targetPosition;
if (!visible)
{
SetTaskTextVisible(false);
}
return;
}
var tween = taskRoot
.DOLocalMove(targetPosition, animationDuration)
.SetEase(animationEase);
_moveTween = tween;
tween.OnComplete(() =>
{
if (_moveTween != tween)
{
return;
}
_moveTween = null;
if (!EffectiveVisible)
{
SetTaskTextVisible(false);
}
});
}
private void AddTaskItem(string lineId, string text)
{
TaskItem view = null;
if (taskItemPrefab == null || taskListParent == null)
{
Debug.LogError(
$"[{nameof(TaskPanel)}] 无法创建任务 '{lineId}'TaskItem Prefab 或任务列表父节点未配置。",
this);
}
else
{
view = Instantiate(taskItemPrefab, taskListParent);
view.SetText(text);
}
_tasks.Add(new TaskEntry
{
LineId = lineId,
Text = text,
View = view
});
MarkTaskLayoutDirty();
}
private int FindTaskIndex(string lineId)
{
if (string.IsNullOrEmpty(lineId))
{
return -1;
}
for (var i = 0; i < _tasks.Count; i++)
{
if (_tasks[i].LineId == lineId)
{
return i;
}
}
return -1;
}
private static void CompleteLineImmediately(LineSyncToken token)
{
token.TextStart();
token.TextShown();
token.ForceAdvance();
}
private static void DestroyTaskView(TaskItem view)
{
if (view == null)
{
return;
}
view.gameObject.SetActive(false);
Destroy(view.gameObject);
}
private void SetTaskTextVisible(bool visible)
{
if (taskListParent != null && taskListParent.gameObject.activeSelf != visible)
{
taskListParent.gameObject.SetActive(visible);
}
}
private void MarkTaskLayoutDirty()
{
if (taskListParent != null)
{
LayoutRebuilder.MarkLayoutForRebuild(taskListParent);
}
}
private void KillMoveTween()
{
if (_moveTween == null)
{
return;
}
var tween = _moveTween;
_moveTween = null;
tween.Kill();
}
private void ValidateConfiguration()
{
if (taskRoot == null)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 未配置 Task Root。", this);
}
if (hiddenAnchor == null)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 未配置 Hidden Anchor。", this);
}
if (shownAnchor == null)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 未配置 Shown Anchor。", this);
}
if (taskRoot != null
&& hiddenAnchor != null
&& shownAnchor != null
&& (taskRoot.parent != hiddenAnchor.parent || taskRoot.parent != shownAnchor.parent))
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 的 Task Root 与两个 Anchor 必须拥有同一个父节点。", this);
}
if (taskListParent == null)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 未配置任务列表父节点。", this);
}
if (taskItemPrefab == null)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 未配置 TaskItem Prefab。", this);
}
else if (!taskItemPrefab.IsConfigured)
{
Debug.LogError($"[{nameof(TaskPanel)}] {name} 的 TaskItem Prefab 未绑定文本组件。", this);
}
}
}
}