Files
zeling_v2/Assets/_Game/Scripts/World/SavePoint.cs

117 lines
4.5 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;
using System.Threading.Tasks;
using UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Core.Save;
namespace BaseGames.World
{
/// <summary>
/// 存档点(也是复活点)。
/// 玩家互动后:恢复 HP → 写入存档(场景、出生点 ID、世界状态→ 广播激活事件。
/// 传送点(快速旅行面板)是独立的 TeleportStation 组件,不在此处处理。
///
/// 继承 SaveableMonoBehaviour 以自动向 ISaveableRegistry 注册/注销,
/// 确保 GameSaveManager 在 SaveAsync/LoadAsync 时回调本组件。
///
/// OnSave 仅在 _isActivated=true 时写入复活字段,
/// 防止自动存档期间未激活的存档点覆盖玩家的正确复活位置。
/// </summary>
public class SavePoint : SaveableMonoBehaviour, IInteractable
{
[Header("Config")]
[SerializeField] private string _savePointId;
[SerializeField] private bool _restoreSpring = true;
[Header("Event Channels - Listen")]
[Tooltip("EVT_SceneLoaded — 用于追踪当前场景名,写入 Player.Scene")]
[SerializeField] private StringEventChannelSO _onSceneLoaded;
[Header("Event Channels - Raise")]
[SerializeField] private StringEventChannelSO _onSavePointActivated;
private bool _isActivated;
private string _currentSceneName;
private readonly CompositeDisposable _subs = new();
protected override void OnEnable()
{
base.OnEnable(); // 向 ISaveableRegistry 注册,确保 OnSave/OnLoad 被回调
_onSceneLoaded?.Subscribe(OnSceneLoaded).AddTo(_subs);
}
protected override void OnDisable()
{
base.OnDisable(); // 从 ISaveableRegistry 注销
_subs.Clear();
}
private void OnSceneLoaded(string sceneName) => _currentSceneName = sceneName;
// ── IInteractable ──────────────────────────────────────────────────────
public bool CanInteract => true;
public string InteractPrompt => _isActivated ? "休息" : "激活";
public void Interact(Transform player)
{
_isActivated = true;
var restorer = player.GetComponentInChildren<IRestoreOnSave>();
if (restorer != null)
{
restorer.FullRestore();
if (_restoreSpring) restorer.RestoreSpring();
}
_onSavePointActivated?.Raise(_savePointId);
// 触发存档OnSave() 由 SaveAsync 回调所有 ISaveable包含本组件
var svc = ServiceLocator.GetOrDefault<ISaveService>();
if (svc != null)
RunFireAndForget(svc.SaveAsync(svc.ActiveSlot));
else
Debug.LogWarning("[SavePoint] ISaveService 未注册,跳过存档。", this);
}
public void OnPlayerEnterRange(Transform player) { }
public void OnPlayerExitRange() { }
// ── ISaveable通过 SaveableMonoBehaviour 注册)────────────────────────
public bool IsActivated => _isActivated;
public void SetActivated(bool val) => _isActivated = val;
public override void OnSave(SaveData data)
{
// 仅在本存档点已激活(玩家坐过)时更新复活字段。
// 自动存档也会触发此方法,必须守卫以防止覆盖玩家真正的复活位置。
if (!_isActivated) return;
if (!string.IsNullOrEmpty(_currentSceneName))
data.Player.Scene = _currentSceneName;
if (!string.IsNullOrEmpty(_savePointId))
data.Meta.SavePointId = _savePointId;
if (!string.IsNullOrEmpty(_savePointId)
&& !data.World.ActivatedSavePoints.Contains(_savePointId))
data.World.ActivatedSavePoints.Add(_savePointId);
}
public override void OnLoad(SaveData data)
{
_isActivated = !string.IsNullOrEmpty(_savePointId)
&& data.World.ActivatedSavePoints.Contains(_savePointId);
}
private static async void RunFireAndForget(Task task)
{
try { await task; }
catch (Exception e)
{
Debug.LogError($"[SavePoint] 存档失败: {e.Message}\n{e.StackTrace}");
}
}
}
}