Files
zeling_v2/Assets/_Game/Scripts/UI/Inventory/QuestLogPanel.cs
2026-06-05 18:41:33 +08:00

204 lines
7.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 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>
/// 全屏任务日志 TabJournal/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_Textkept inactive。")]
[SerializeField] private Button _rowTemplate;
[Header("详情")]
[SerializeField] private TMP_Text _detailTitle;
[SerializeField] private TMP_Text _detailDesc;
[SerializeField] private Transform _objectiveContainer;
[Tooltip("目标行模板TMP_Textkept 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);
}
}
}