地图系统

This commit is contained in:
2026-06-05 18:41:33 +08:00
parent 613f2a4d13
commit fe4fd60083
234 changed files with 33090 additions and 4899 deletions

View File

@@ -0,0 +1,203 @@
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);
}
}
}