存档完善和修复
This commit is contained in:
113
Assets/_Game/Scripts/World/DeathShadeManager.cs
Normal file
113
Assets/_Game/Scripts/World/DeathShadeManager.cs
Normal file
@@ -0,0 +1,113 @@
|
||||
using UnityEngine;
|
||||
using UnityEngine.SceneManagement;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
|
||||
namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 管理死亡遗骸(Death Shade)的生命周期与持久化。
|
||||
///
|
||||
/// 挂载位置:Persistent 场景的根 GameObject(DontDestroyOnLoad)。
|
||||
///
|
||||
/// 流程:
|
||||
/// 玩家死亡 → OnPlayerDied:记录死亡位置与灵珠量,在当前场景生成遗骸。
|
||||
/// 切换场景 → OnSceneLoaded:若新场景即遗骸所在场景,自动生成遗骸。
|
||||
/// 回收遗骸 → OnShadeCollected:清除待存储的遗骸数据。
|
||||
/// 存档 → OnSave:将遗骸数据(null = 无遗骸)写入 SaveData.Player.DeathShade。
|
||||
/// 读档 → OnLoad:从 SaveData 恢复遗骸数据;实际 GameObject 在下次进入该场景时生成。
|
||||
/// </summary>
|
||||
public class DeathShadeManager : SaveableMonoBehaviour
|
||||
{
|
||||
[SerializeField] private DeathShade _shadePrefab;
|
||||
[SerializeField] private VoidEventChannelSO _onPlayerDied;
|
||||
[SerializeField] private StringEventChannelSO _onShadeCollected;
|
||||
|
||||
private DeathShadeSaveData _pendingSave;
|
||||
private DeathShade _activeShade;
|
||||
|
||||
private readonly CompositeDisposable _subs = new();
|
||||
|
||||
protected override void OnEnable()
|
||||
{
|
||||
base.OnEnable();
|
||||
_onPlayerDied? .Subscribe(OnPlayerDied) .AddTo(_subs);
|
||||
_onShadeCollected?.Subscribe(OnShadeCollected).AddTo(_subs);
|
||||
SceneManager.sceneLoaded += OnSceneLoaded;
|
||||
}
|
||||
|
||||
protected override void OnDisable()
|
||||
{
|
||||
base.OnDisable();
|
||||
_subs.Clear();
|
||||
SceneManager.sceneLoaded -= OnSceneLoaded;
|
||||
}
|
||||
|
||||
private void OnPlayerDied()
|
||||
{
|
||||
var playerGo = GameObject.FindWithTag("Player");
|
||||
if (playerGo == null) return;
|
||||
|
||||
int lingZhu = playerGo.GetComponent<ILingZhuProvider>()?.CurrentLingZhu ?? 0;
|
||||
var pos = (Vector2)playerGo.transform.position;
|
||||
string sceneId = SceneManager.GetActiveScene().name;
|
||||
|
||||
if (_activeShade != null)
|
||||
{
|
||||
Destroy(_activeShade.gameObject);
|
||||
_activeShade = null;
|
||||
}
|
||||
|
||||
_pendingSave = new DeathShadeSaveData
|
||||
{
|
||||
PosX = pos.x,
|
||||
PosY = pos.y,
|
||||
SceneId = sceneId,
|
||||
LingZhuAmount = lingZhu,
|
||||
};
|
||||
|
||||
TrySpawnShade(sceneId);
|
||||
}
|
||||
|
||||
private void OnSceneLoaded(Scene scene, LoadSceneMode _)
|
||||
{
|
||||
_activeShade = null; // 前一场景的 GameObject 已被 Unity 卸载
|
||||
TrySpawnShade(scene.name);
|
||||
}
|
||||
|
||||
private void TrySpawnShade(string sceneName)
|
||||
{
|
||||
if (_shadePrefab == null || _pendingSave == null) return;
|
||||
if (_pendingSave.SceneId != sceneName) return;
|
||||
|
||||
_activeShade = Instantiate(_shadePrefab);
|
||||
_activeShade.Initialize(
|
||||
_pendingSave.LingZhuAmount,
|
||||
_pendingSave.SceneId,
|
||||
new Vector2(_pendingSave.PosX, _pendingSave.PosY));
|
||||
}
|
||||
|
||||
private void OnShadeCollected(string _)
|
||||
{
|
||||
_pendingSave = null;
|
||||
_activeShade = null;
|
||||
}
|
||||
|
||||
public override void OnSave(SaveData data)
|
||||
{
|
||||
data.Player.DeathShade = _pendingSave;
|
||||
}
|
||||
|
||||
public override void OnLoad(SaveData data)
|
||||
{
|
||||
if (_activeShade != null)
|
||||
{
|
||||
Destroy(_activeShade.gameObject);
|
||||
_activeShade = null;
|
||||
}
|
||||
_pendingSave = data.Player.DeathShade;
|
||||
// 遗骸 GameObject 将在 OnSceneLoaded 中按需生成
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -17,8 +17,9 @@ namespace BaseGames.World
|
||||
|
||||
/// <summary>
|
||||
/// 运行时世界状态缓存。ScriptableObject,通过 [SerializeField] 注入各组件。
|
||||
/// SaveManager.SaveAsync 调用 GetAllFlags();
|
||||
/// SaveManager.LoadAsync 调用 LoadFromSave(data.World)。
|
||||
/// WorldStateRegistrySaver(ISaveable)负责将本对象与存档管道连接:
|
||||
/// SaveAsync → WorldStateRegistrySaver.OnSave → 写出全部分类到 SaveData;
|
||||
/// LoadAsync → WorldStateRegistrySaver.OnLoad → 调用 LoadFromSave(data) 恢复缓存。
|
||||
/// </summary>
|
||||
[CreateAssetMenu(menuName = "BaseGames/World/WorldStateRegistry")]
|
||||
public class WorldStateRegistry : ScriptableObject
|
||||
@@ -77,37 +78,34 @@ namespace BaseGames.World
|
||||
|
||||
// ── Persistence ───────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>从存档数据恢复全部状态。由 SaveManager.LoadAsync 调用。</summary>
|
||||
public void LoadFromSave(WorldSaveData data)
|
||||
/// <summary>
|
||||
/// 从存档数据恢复全部状态。由 WorldStateRegistrySaver.OnLoad 调用。
|
||||
/// </summary>
|
||||
public void LoadFromSave(SaveData data)
|
||||
{
|
||||
_states.Clear();
|
||||
|
||||
if (data == null) return;
|
||||
|
||||
foreach (var id in data.CollectedIds) Mark(WorldObjectCategory.Collectible, id);
|
||||
foreach (var id in data.ActivatedSavePoints) Mark(WorldObjectCategory.SavePoint, id);
|
||||
foreach (var id in data.OpenedDoors) Mark(WorldObjectCategory.Door, id);
|
||||
foreach (var id in data.DestroyedObjectIds) Mark(WorldObjectCategory.Destroyed, id);
|
||||
foreach (var id in data.World.CollectedIds) Mark(WorldObjectCategory.Collectible, id);
|
||||
foreach (var id in data.World.ActivatedSavePoints) Mark(WorldObjectCategory.SavePoint, id);
|
||||
foreach (var id in data.World.OpenedDoors) Mark(WorldObjectCategory.Door, id);
|
||||
foreach (var id in data.World.DestroyedObjectIds) Mark(WorldObjectCategory.Destroyed, id);
|
||||
|
||||
if (data.Switches != null)
|
||||
foreach (var kv in data.Switches)
|
||||
if (data.EventChains?.WorldFlags != null)
|
||||
foreach (var kv in data.EventChains.WorldFlags)
|
||||
if (kv.Value) Mark(WorldObjectCategory.Flag, kv.Key);
|
||||
}
|
||||
|
||||
/// <summary>获取所有通用标记,供 SaveManager 持久化。</summary>
|
||||
public HashSet<string> GetAllFlags()
|
||||
/// <summary>
|
||||
/// 返回指定分类中所有已标记的 ID(快照副本)。
|
||||
/// 由 WorldStateRegistrySaver.OnSave 调用,将运行时状态写入 SaveData。
|
||||
/// </summary>
|
||||
public IReadOnlyCollection<string> GetAllIds(WorldObjectCategory category)
|
||||
{
|
||||
if (_states.TryGetValue(WorldObjectCategory.Flag, out var flags))
|
||||
return new HashSet<string>(flags);
|
||||
return new HashSet<string>();
|
||||
}
|
||||
|
||||
/// <summary>获取所有已摧毁对象 ID,供 SaveManager 持久化。</summary>
|
||||
public HashSet<string> GetAllDestroyedIds()
|
||||
{
|
||||
if (_states.TryGetValue(WorldObjectCategory.Destroyed, out var set))
|
||||
if (_states.TryGetValue(category, out var set))
|
||||
return new HashSet<string>(set);
|
||||
return new HashSet<string>();
|
||||
return System.Array.Empty<string>();
|
||||
}
|
||||
|
||||
/// <summary>重置所有状态(开始新游戏时调用)。</summary>
|
||||
|
||||
42
Assets/_Game/Scripts/World/WorldStateRegistrySaver.cs
Normal file
42
Assets/_Game/Scripts/World/WorldStateRegistrySaver.cs
Normal file
@@ -0,0 +1,42 @@
|
||||
using BaseGames.Core.Save;
|
||||
|
||||
namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 将 WorldStateRegistry 接入 ISaveable 存档管道的桥接组件。
|
||||
///
|
||||
/// 挂载位置:Persistent 场景的根 GameObject(与 GameServiceRegistrar 同级)。
|
||||
/// 需在 Inspector 中绑定场景中唯一的 WorldStateRegistry ScriptableObject 资源。
|
||||
///
|
||||
/// OnSave:从 Registry 读取 Collectible / Destroyed / Flag 三个分类,写入 SaveData。
|
||||
/// SavePoint 和 Door 两个分类由各自的 SaveableMonoBehaviour 独立写入,此处跳过。
|
||||
/// OnLoad:将完整的 SaveData 传递给 Registry,恢复所有分类的运行时缓存。
|
||||
/// 这是 Registry 从空白变为"知道世界状态"的唯一时机。
|
||||
/// </summary>
|
||||
public class WorldStateRegistrySaver : SaveableMonoBehaviour
|
||||
{
|
||||
[UnityEngine.SerializeField] private WorldStateRegistry _registry;
|
||||
|
||||
public override void OnSave(SaveData data)
|
||||
{
|
||||
if (_registry == null) return;
|
||||
|
||||
data.World.CollectedIds.Clear();
|
||||
foreach (var id in _registry.GetAllIds(WorldObjectCategory.Collectible))
|
||||
data.World.CollectedIds.Add(id);
|
||||
|
||||
data.World.DestroyedObjectIds.Clear();
|
||||
foreach (var id in _registry.GetAllIds(WorldObjectCategory.Destroyed))
|
||||
data.World.DestroyedObjectIds.Add(id);
|
||||
|
||||
foreach (var id in _registry.GetAllIds(WorldObjectCategory.Flag))
|
||||
data.EventChains.WorldFlags[id] = true;
|
||||
}
|
||||
|
||||
public override void OnLoad(SaveData data)
|
||||
{
|
||||
if (_registry == null) return;
|
||||
_registry.LoadFromSave(data);
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user