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 _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 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 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); } } } }