363 lines
9.9 KiB
C#
363 lines
9.9 KiB
C#
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);
|
||
}
|
||
}
|
||
}
|
||
}
|