feat: 优化存档管理,增强链状态获取逻辑,添加 ISaveable 接口支持
This commit is contained in:
@@ -195,7 +195,12 @@ namespace BaseGames.Core.Save
|
||||
public void SetChainCompleted(string chainId)
|
||||
{ _current ??= new SaveData(); _current.EventChains.ChainStates[chainId] = "Completed"; }
|
||||
public IEnumerable<string> GetCompletedChains()
|
||||
=> _current?.EventChains.ChainStates.Keys ?? Enumerable.Empty<string>();
|
||||
{
|
||||
if (_current?.EventChains?.ChainStates == null) return System.Linq.Enumerable.Empty<string>();
|
||||
return _current.EventChains.ChainStates
|
||||
.Where(kv => kv.Value == "Completed")
|
||||
.Select(kv => kv.Key);
|
||||
}
|
||||
|
||||
public bool IsFirstClear(string challengeId)
|
||||
{
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
using BaseGames.Player.States;
|
||||
@@ -26,6 +27,9 @@ namespace BaseGames.Equipment
|
||||
Debug.Assert(_toolCatalog != null, "[ToolSlotManager] _toolCatalog 未赋值,请在 Inspector 中指定 ToolCatalogSO。", this);
|
||||
}
|
||||
|
||||
private void OnEnable() => ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
|
||||
private void OnDisable() => ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
|
||||
|
||||
// ── 装备 ─────────────────────────────────────────────────────────────
|
||||
public void EquipTool(int slotIndex, ToolSO tool)
|
||||
{
|
||||
|
||||
@@ -3,6 +3,7 @@ using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Events;
|
||||
using BaseGames.Core.Save;
|
||||
using UnityEngine;
|
||||
|
||||
namespace BaseGames.EventChain
|
||||
@@ -12,7 +13,7 @@ namespace BaseGames.EventChain
|
||||
/// 订阅游戏各系统的 SO 事件频道,转发为中继 C# 事件供 ChainCondition.Register() 绑定。
|
||||
/// 每当收到新事件时,检查所有链的触发条件。
|
||||
/// </summary>
|
||||
public class EventChainManager : MonoBehaviour
|
||||
public class EventChainManager : MonoBehaviour, ISaveable
|
||||
{
|
||||
[Header("所有事件链")]
|
||||
[SerializeField] private EventChainSO[] _chains;
|
||||
@@ -49,11 +50,8 @@ namespace BaseGames.EventChain
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
// 从存档恢复已完成链
|
||||
var sm = ServiceLocator.GetOrDefault<ISaveService>();
|
||||
if (sm != null)
|
||||
foreach (var id in sm.GetCompletedChains())
|
||||
_completedChains.Add(id);
|
||||
// 不在此处读取存档数据——LoadAsync 尚未完成,_current 为 null。
|
||||
// 已完成链的恢复由 ISaveable.OnLoad 在 LoadAsync 完成后处理。
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
@@ -64,6 +62,8 @@ namespace BaseGames.EventChain
|
||||
_onRoomEntered?.Subscribe(id => { OnRoomEntered?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
|
||||
_onDialogueCompleted?.Subscribe(id => { OnDialogueCompleted?.Invoke(id); EvaluateAll(); }).AddTo(_subs);
|
||||
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
|
||||
|
||||
// 向每个 Condition 注册中继事件
|
||||
// 先重置 SO 资产上的运行时状态,防止跨 PlayMode 会话或多场景加载时状态残留
|
||||
if (_chains == null) return;
|
||||
@@ -79,6 +79,7 @@ namespace BaseGames.EventChain
|
||||
private void OnDisable()
|
||||
{
|
||||
_subs.Clear();
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
|
||||
|
||||
if (_chains == null) return;
|
||||
foreach (var chain in _chains)
|
||||
@@ -87,6 +88,25 @@ namespace BaseGames.EventChain
|
||||
cond?.Unregister(this);
|
||||
}
|
||||
|
||||
// ── ISaveable ─────────────────────────────────────────────────────────
|
||||
/// <summary>
|
||||
/// SetChainCompleted 通过 ISaveService 直接写入 SaveData,OnSave 无需操作。
|
||||
/// </summary>
|
||||
public void OnSave(SaveData data) { }
|
||||
|
||||
/// <summary>
|
||||
/// LoadAsync 完成后由 GameSaveManager.Register 补发,恢复本会话前已完成的链。
|
||||
/// 仅恢复状态为 "Completed" 的条目;其他状态(如未来扩展的 "Failed")不影响重入检查。
|
||||
/// </summary>
|
||||
public void OnLoad(SaveData data)
|
||||
{
|
||||
_completedChains.Clear();
|
||||
if (data?.EventChains?.ChainStates == null) return;
|
||||
foreach (var kv in data.EventChains.ChainStates)
|
||||
if (kv.Value == "Completed")
|
||||
_completedChains.Add(kv.Key);
|
||||
}
|
||||
|
||||
// ── 评估逻辑 ──────────────────────────────────────────────────────
|
||||
/// <summary>收到新事件时立即评估所有链条件(无帧延迟)。</summary>
|
||||
private void EvaluateAll() => DoEvaluateAll();
|
||||
|
||||
@@ -1,6 +1,4 @@
|
||||
using BaseGames.Combat;
|
||||
using BaseGames.Core;
|
||||
using BaseGames.Core.Save;
|
||||
using BaseGames.Feedback;
|
||||
using UnityEngine;
|
||||
|
||||
@@ -8,10 +6,11 @@ namespace BaseGames.World
|
||||
{
|
||||
/// <summary>
|
||||
/// 假墙(秘密通道)。外观与普通墙相同,可通过攻击/接近揭示并穿越。
|
||||
/// 揭示后禁用碰撞体(不销毁),状态持久化到 WorldSaveData。
|
||||
/// 揭示后禁用碰撞体(不销毁),状态通过 WorldStateRegistry 持久化。
|
||||
/// WorldStateRegistrySaver(ISaveable)统一负责将 Door 分类写入存档。
|
||||
/// </summary>
|
||||
[RequireComponent(typeof(Collider2D))]
|
||||
public class FalseWall : MonoBehaviour, IDamageable, ISaveable
|
||||
public class FalseWall : MonoBehaviour, IDamageable
|
||||
{
|
||||
public enum RevealCondition { Proximity, AttackOnce, AlwaysOpen }
|
||||
|
||||
@@ -27,6 +26,10 @@ namespace BaseGames.World
|
||||
[SerializeField] private SpriteRenderer _renderer;
|
||||
[SerializeField] private SceneFeedback _revealFeedback;
|
||||
|
||||
[Header("World State")]
|
||||
[Tooltip("绑定场景唯一的 WorldStateRegistry ScriptableObject,用于持久化揭示状态。")]
|
||||
[SerializeField] private WorldStateRegistry _registry;
|
||||
|
||||
private bool _isRevealed;
|
||||
|
||||
// ── IDamageable ───────────────────────────────────────────────────────
|
||||
@@ -42,16 +45,6 @@ namespace BaseGames.World
|
||||
|
||||
// ── Unity Lifecycle ───────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Register(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
ServiceLocator.GetOrDefault<ISaveableRegistry>()?.Unregister(this);
|
||||
}
|
||||
|
||||
private void Start()
|
||||
{
|
||||
if (_revealCondition == RevealCondition.AlwaysOpen)
|
||||
@@ -60,21 +53,13 @@ namespace BaseGames.World
|
||||
_isRevealed = true;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// ── ISaveable ─────────────────────────────────────────────────────────
|
||||
public void OnSave(SaveData data)
|
||||
// 从 WorldStateRegistry 恢复揭示状态(注册表由 WorldStateRegistrySaver.OnLoad 填充)
|
||||
if (!string.IsNullOrEmpty(_wallId) && (_registry?.IsDoorOpened(_wallId) ?? false))
|
||||
{
|
||||
if (_isRevealed && !string.IsNullOrEmpty(_wallId)
|
||||
&& !data.World.OpenedDoors.Contains(_wallId))
|
||||
data.World.OpenedDoors.Add(_wallId);
|
||||
_isRevealed = true;
|
||||
SetPassThroughImmediate();
|
||||
}
|
||||
|
||||
public void OnLoad(SaveData data)
|
||||
{
|
||||
if (string.IsNullOrEmpty(_wallId)) return;
|
||||
_isRevealed = data.World.OpenedDoors.Contains(_wallId);
|
||||
if (_isRevealed) SetPassThroughImmediate();
|
||||
}
|
||||
|
||||
private void OnTriggerEnter2D(Collider2D other)
|
||||
@@ -90,6 +75,7 @@ namespace BaseGames.World
|
||||
private void Reveal()
|
||||
{
|
||||
_isRevealed = true;
|
||||
_registry?.MarkDoorOpened(_wallId);
|
||||
_revealFeedback?.Play();
|
||||
SetPassThroughImmediate();
|
||||
}
|
||||
@@ -118,3 +104,4 @@ namespace BaseGames.World
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user