UI 系统
This commit is contained in:
83
Assets/_Game/Scripts/Progression/BestiaryService.cs
Normal file
83
Assets/_Game/Scripts/Progression/BestiaryService.cs
Normal 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_EnemyDied:payload = enemyId。敌人 / Boss 死亡时由 EnemyBase 广播。")]
|
||||
[SerializeField] private StringEventChannelSO _onEnemyDied;
|
||||
[Tooltip("EVT_BestiaryUpdated:payload = 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
Assets/_Game/Scripts/Progression/BestiaryService.cs.meta
Normal file
11
Assets/_Game/Scripts/Progression/BestiaryService.cs.meta
Normal file
@@ -0,0 +1,11 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 98b6746a3664fba43a4aad88a2b650ac
|
||||
MonoImporter:
|
||||
externalObjects: {}
|
||||
serializedVersion: 2
|
||||
defaultReferences: []
|
||||
executionOrder: 0
|
||||
icon: {instanceID: 0}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
Reference in New Issue
Block a user