UI 系统

This commit is contained in:
2026-06-08 11:26:17 +08:00
parent 1897658a00
commit b582317692
94 changed files with 33540 additions and 3726 deletions

View File

@@ -0,0 +1,83 @@
using System.Collections.Generic;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.Progression
{
/// <summary>
/// 图鉴追踪服务(仿 <see cref="BaseGames.Equipment.EquipmentManager"/> 的 ISaveable + ServiceLocator 模式)。
/// 订阅 EVT_EnemyDied(payload=enemyId),记录已发现敌人与击杀次数,持久化到 <see cref="SaveData.Journal"/>。
/// 仅依赖字符串 enemyId 与事件频道不引用敌人程序集asmdef 解耦);敌人代码零改动。
/// 挂在 Persistent 的 [GameManagers] 下OnEnable 注册 IBestiaryService 与 ISaveableRegistry。
/// </summary>
public class BestiaryService : MonoBehaviour, ISaveable, IBestiaryService
{
[Header("事件频道")]
[Tooltip("EVT_EnemyDiedpayload = enemyId。敌人 / Boss 死亡时由 EnemyBase 广播。")]
[SerializeField] private StringEventChannelSO _onEnemyDied;
[Tooltip("EVT_BestiaryUpdatedpayload = enemyId。新发现 / 击杀计数更新时广播,驱动图鉴 UI 实时刷新。")]
[SerializeField] private StringEventChannelSO _onBestiaryUpdated;
private readonly HashSet<string> _discovered = new();
private readonly Dictionary<string, int> _killCounts = new();
private readonly CompositeDisposable _subs = new();
// ── IBestiaryService ─────────────────────────────────────────────────
public int DiscoveredCount => _discovered.Count;
public bool IsDiscovered(string enemyId)
=> !string.IsNullOrEmpty(enemyId) && _discovered.Contains(enemyId);
public int GetKillCount(string enemyId)
=> !string.IsNullOrEmpty(enemyId) && _killCounts.TryGetValue(enemyId, out var n) ? n : 0;
// ── 生命周期 ──────────────────────────────────────────────────────────
private void OnEnable()
{
ServiceLocator.Register<IBestiaryService>(this);
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
_onEnemyDied?.Subscribe(OnEnemyDied).AddTo(_subs);
}
private void OnDisable()
{
ServiceLocator.Unregister<IBestiaryService>(this);
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
_subs.Clear();
}
// ── 解锁处理 ──────────────────────────────────────────────────────────
private void OnEnemyDied(string enemyId)
{
if (string.IsNullOrEmpty(enemyId)) return;
_discovered.Add(enemyId);
_killCounts.TryGetValue(enemyId, out var n);
_killCounts[enemyId] = n + 1;
_onBestiaryUpdated?.Raise(enemyId);
}
// ── ISaveable ─────────────────────────────────────────────────────────
public void OnSave(SaveData data)
{
var j = data.Journal;
j.DiscoveredEnemyIds.Clear();
foreach (var id in _discovered) j.DiscoveredEnemyIds.Add(id);
j.EnemyKillCounts.Clear();
foreach (var kv in _killCounts) j.EnemyKillCounts[kv.Key] = kv.Value;
}
public void OnLoad(SaveData data)
{
_discovered.Clear();
_killCounts.Clear();
var j = data.Journal;
if (j == null) return;
if (j.DiscoveredEnemyIds != null)
foreach (var id in j.DiscoveredEnemyIds) _discovered.Add(id);
if (j.EnemyKillCounts != null)
foreach (var kv in j.EnemyKillCounts) _killCounts[kv.Key] = kv.Value;
}
}
}

View File

@@ -0,0 +1,11 @@
fileFormatVersion: 2
guid: 98b6746a3664fba43a4aad88a2b650ac
MonoImporter:
externalObjects: {}
serializedVersion: 2
defaultReferences: []
executionOrder: 0
icon: {instanceID: 0}
userData:
assetBundleName:
assetBundleVariant: