204 lines
7.9 KiB
C#
204 lines
7.9 KiB
C#
using System.Collections.Generic;
|
||
using UnityEngine;
|
||
using UnityEngine.UI;
|
||
using UnityEngine.EventSystems;
|
||
using TMPro;
|
||
using BaseGames.Core;
|
||
using BaseGames.Core.Events;
|
||
using BaseGames.Localization;
|
||
using BaseGames.Quest;
|
||
using QuestStateEnum = BaseGames.Core.Events.QuestState;
|
||
|
||
namespace BaseGames.UI.Inventory
|
||
{
|
||
/// <summary>
|
||
/// 全屏任务日志 Tab(Journal/Quests 分栏)。
|
||
/// 左侧列出进行中 / 已完成任务,右侧详情显示选中任务的名称、描述与目标进度。
|
||
///
|
||
/// 数据来源:ServiceLocator 获取 <see cref="IQuestManager"/>,读取 GetQuestsInState 快照。
|
||
/// 与 HUD 的 <see cref="BaseGames.UI.HUD.QuestTrackerWidget"/> 不同:后者只显示当前追踪任务,
|
||
/// 本面板提供完整列表浏览。本地化统一使用 Quest 表。
|
||
/// </summary>
|
||
public class QuestLogPanel : MonoBehaviour, IFocusable
|
||
{
|
||
[Header("任务列表")]
|
||
[SerializeField] private Transform _listContainer;
|
||
[Tooltip("任务行模板(含 Button + TMP_Text),kept inactive。")]
|
||
[SerializeField] private Button _rowTemplate;
|
||
|
||
[Header("详情")]
|
||
[SerializeField] private TMP_Text _detailTitle;
|
||
[SerializeField] private TMP_Text _detailDesc;
|
||
[SerializeField] private Transform _objectiveContainer;
|
||
[Tooltip("目标行模板(TMP_Text),kept inactive。")]
|
||
[SerializeField] private TMP_Text _objectiveRowTemplate;
|
||
[Tooltip("无进行中任务时的提示节点。")]
|
||
[SerializeField] private GameObject _emptyHint;
|
||
|
||
[Header("Event Channels - 刷新触发(任一即重建)")]
|
||
[SerializeField] private QuestStateChangedEventChannel _onQuestStateChanged;
|
||
|
||
private IQuestManager _quests;
|
||
private readonly List<string> _activeIds = new();
|
||
private readonly List<Button> _rows = new();
|
||
private readonly Queue<Button> _rowPool = new();
|
||
private readonly List<TMP_Text> _objRows = new();
|
||
private readonly Queue<TMP_Text> _objPool = new();
|
||
private string _selectedQuestId;
|
||
private readonly CompositeDisposable _subs = new();
|
||
|
||
private void Awake()
|
||
{
|
||
if (_rowTemplate != null) _rowTemplate.gameObject.SetActive(false);
|
||
if (_objectiveRowTemplate != null) _objectiveRowTemplate.gameObject.SetActive(false);
|
||
}
|
||
|
||
private void OnEnable()
|
||
{
|
||
_quests = ServiceLocator.GetOrDefault<IQuestManager>();
|
||
_onQuestStateChanged?.Subscribe(OnStateChanged).AddTo(_subs);
|
||
Rebuild();
|
||
}
|
||
|
||
private void OnDisable()
|
||
{
|
||
_subs.Clear();
|
||
RecycleRows();
|
||
RecycleObjectives();
|
||
}
|
||
|
||
private void OnStateChanged(QuestStateChangedEvent _) => Rebuild();
|
||
|
||
// ── 列表重建 ──────────────────────────────────────────────────────────
|
||
private void Rebuild()
|
||
{
|
||
RecycleRows();
|
||
if (_quests == null || _listContainer == null || _rowTemplate == null) return;
|
||
|
||
_activeIds.Clear();
|
||
CollectInto(QuestStateEnum.Active);
|
||
CollectInto(QuestStateEnum.Completed);
|
||
|
||
if (_emptyHint != null) _emptyHint.SetActive(_activeIds.Count == 0);
|
||
|
||
foreach (var id in _activeIds)
|
||
{
|
||
var row = SpawnRow();
|
||
BindRow(row, id);
|
||
_rows.Add(row);
|
||
}
|
||
|
||
if (_activeIds.Count > 0) SelectQuest(_activeIds[0]);
|
||
else ClearDetail();
|
||
}
|
||
|
||
private void CollectInto(QuestStateEnum state)
|
||
{
|
||
var snapshot = _quests.GetQuestsInState(state);
|
||
for (int i = 0; i < snapshot.Count; i++)
|
||
if (!_activeIds.Contains(snapshot[i])) _activeIds.Add(snapshot[i]);
|
||
}
|
||
|
||
private Button SpawnRow()
|
||
{
|
||
Button row = _rowPool.Count > 0 ? _rowPool.Dequeue() : Instantiate(_rowTemplate, _listContainer);
|
||
row.transform.SetParent(_listContainer, false);
|
||
row.gameObject.SetActive(true);
|
||
return row;
|
||
}
|
||
|
||
private void BindRow(Button row, string questId)
|
||
{
|
||
var label = row.GetComponentInChildren<TMP_Text>(includeInactive: true);
|
||
if (label != null) label.text = ResolveQuestTitle(questId);
|
||
row.onClick.RemoveAllListeners();
|
||
string captured = questId;
|
||
row.onClick.AddListener(() => SelectQuest(captured));
|
||
}
|
||
|
||
private void RecycleRows()
|
||
{
|
||
foreach (var row in _rows)
|
||
{
|
||
if (row == null) continue;
|
||
row.gameObject.SetActive(false);
|
||
_rowPool.Enqueue(row);
|
||
}
|
||
_rows.Clear();
|
||
}
|
||
|
||
// ── 详情 ──────────────────────────────────────────────────────────────
|
||
private void SelectQuest(string questId)
|
||
{
|
||
_selectedQuestId = questId;
|
||
if (_quests == null || !_quests.TryGetQuest(questId, out var quest) || quest == null)
|
||
{
|
||
ClearDetail();
|
||
return;
|
||
}
|
||
|
||
if (_detailTitle != null) _detailTitle.text = ResolveQuestTitle(questId);
|
||
if (_detailDesc != null)
|
||
{
|
||
string loc = LocalizationManager.Get(quest.descriptionKey, LocalizationTable.Quest);
|
||
_detailDesc.text = !string.IsNullOrEmpty(loc) && loc != quest.descriptionKey ? loc : string.Empty;
|
||
}
|
||
|
||
RebuildObjectives(quest);
|
||
}
|
||
|
||
private void RebuildObjectives(QuestSO quest)
|
||
{
|
||
RecycleObjectives();
|
||
if (_objectiveContainer == null || _objectiveRowTemplate == null || quest.objectives == null) return;
|
||
|
||
foreach (var obj in quest.objectives)
|
||
{
|
||
if (obj == null) continue;
|
||
var row = _objPool.Count > 0 ? _objPool.Dequeue() : Instantiate(_objectiveRowTemplate, _objectiveContainer);
|
||
row.transform.SetParent(_objectiveContainer, false);
|
||
row.gameObject.SetActive(true);
|
||
string loc = LocalizationManager.Get(obj.displayTextKey, LocalizationTable.Quest);
|
||
row.text = !string.IsNullOrEmpty(loc) && loc != obj.displayTextKey ? loc : obj.displayTextKey;
|
||
_objRows.Add(row);
|
||
}
|
||
}
|
||
|
||
private void RecycleObjectives()
|
||
{
|
||
foreach (var row in _objRows)
|
||
{
|
||
if (row == null) continue;
|
||
row.gameObject.SetActive(false);
|
||
_objPool.Enqueue(row);
|
||
}
|
||
_objRows.Clear();
|
||
}
|
||
|
||
private void ClearDetail()
|
||
{
|
||
if (_detailTitle != null) _detailTitle.text = string.Empty;
|
||
if (_detailDesc != null) _detailDesc.text = string.Empty;
|
||
RecycleObjectives();
|
||
}
|
||
|
||
private string ResolveQuestTitle(string questId)
|
||
{
|
||
if (_quests != null && _quests.TryGetQuest(questId, out var quest) && quest != null
|
||
&& !string.IsNullOrEmpty(quest.displayNameKey))
|
||
{
|
||
string loc = LocalizationManager.Get(quest.displayNameKey, LocalizationTable.Quest);
|
||
return !string.IsNullOrEmpty(loc) && loc != quest.displayNameKey ? loc : questId;
|
||
}
|
||
return questId;
|
||
}
|
||
|
||
// ── IFocusable ────────────────────────────────────────────────────────
|
||
public void OnFocusRestored()
|
||
{
|
||
if (_rows.Count > 0 && _rows[0] != null)
|
||
EventSystem.current?.SetSelectedGameObject(_rows[0].gameObject);
|
||
}
|
||
}
|
||
}
|