using System.Collections.Generic; using UnityEngine; using BaseGames.Core; using BaseGames.Core.Events; using BaseGames.Core.Save; namespace BaseGames.Progression { /// /// 图鉴追踪服务(仿 的 ISaveable + ServiceLocator 模式)。 /// 订阅 EVT_EnemyDied(payload=enemyId),记录已发现敌人与击杀次数,持久化到 。 /// 仅依赖字符串 enemyId 与事件频道,不引用敌人程序集(asmdef 解耦);敌人代码零改动。 /// 挂在 Persistent 的 [GameManagers] 下,OnEnable 注册 IBestiaryService 与 ISaveableRegistry。 /// public class BestiaryService : MonoBehaviour, ISaveable, IBestiaryService { [Header("事件频道")] [Tooltip("EVT_EnemyDied:payload = enemyId。敌人 / Boss 死亡时由 EnemyBase 广播。")] [SerializeField] private StringEventChannelSO _onEnemyDied; [Tooltip("EVT_BestiaryUpdated:payload = enemyId。新发现 / 击杀计数更新时广播,驱动图鉴 UI 实时刷新。")] [SerializeField] private StringEventChannelSO _onBestiaryUpdated; private readonly HashSet _discovered = new(); private readonly Dictionary _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(this); ServiceLocator.GetOrDefault()?.Register(this); _onEnemyDied?.Subscribe(OnEnemyDied).AddTo(_subs); } private void OnDisable() { ServiceLocator.Unregister(this); ServiceLocator.GetOrDefault()?.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; } } }