Files
zeling_v2/Assets/_Game/Scripts/Progression/BestiaryService.cs
2026-06-08 11:26:17 +08:00

84 lines
4.0 KiB
C#
Raw 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 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;
}
}
}