Files
zeling_v2/Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs

142 lines
4.9 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 UnityEngine;
using BaseGames.Core;
using BaseGames.Core.Events;
using BaseGames.Input;
using BaseGames.Player;
using BaseGames.Player.States;
using BaseGames.Progression;
namespace BaseGames.Support.AntiSoftlock
{
/// <summary>
/// 防软锁系统(架构 16_SupportingModules §5
/// 检测玩家长时间静止且无法移动的情况,提供逃脱选项。
/// </summary>
public class AntiSoftlockSystem : MonoBehaviour
{
[Header("配置")]
[Tooltip("玩家静止超过此秒数后显示逃脱提示")]
[SerializeField] private float _stuckThresholdSeconds = 30f;
[Tooltip("移动速度低于此值视为静止")]
[SerializeField] private float _minVelocityThreshold = 0.1f;
[Header("逃脱路径列表(按优先级降序排列)")]
[SerializeField] private RoomEscapeInfoSO[] _escapeOptions;
[Header("事件频道")]
[SerializeField] private SceneLoadRequestEventChannelSO _onSceneLoadRequest;
[SerializeField] private VoidEventChannelSO _onShowEscapeUI;
[SerializeField] private VoidEventChannelSO _onHideEscapeUI;
/// <summary>
/// 玩家生成事件频道PlayerController.Start() 广播)。
/// 替代 FindFirstObjectByType 的全场景扫描。
/// </summary>
[SerializeField] private TransformEventChannelSO _onPlayerSpawned;
[Header("引用")]
[SerializeField] private InputReaderSO _input;
private PlayerController _player;
private Rigidbody2D _playerRb;
private float _stuckTimer;
private bool _escapeUIVisible;
private Vector2 _lastPos;
private readonly BaseGames.Core.Events.CompositeDisposable _subs = new();
private void OnEnable()
{
if (_onPlayerSpawned != null)
_onPlayerSpawned.Subscribe(OnPlayerSpawned).AddTo(_subs);
}
private void OnDisable() => _subs.Clear();
private void OnPlayerSpawned(Transform playerTransform)
{
if (playerTransform == null) return;
_player = playerTransform.GetComponent<PlayerController>();
_playerRb = playerTransform.GetComponent<Rigidbody2D>();
_lastPos = playerTransform.position;
_stuckTimer = 0f;
}
private void Update()
{
if (_player == null || !_player.Stats.IsAlive) return;
Vector2 pos = _player.transform.position;
float vel = _playerRb != null ? _playerRb.velocity.magnitude : Vector2.Distance(pos, _lastPos) / Time.deltaTime;
if (vel > _minVelocityThreshold)
{
_stuckTimer = 0f;
_lastPos = pos;
if (_escapeUIVisible) HideEscapeUI();
return;
}
_lastPos = pos;
_stuckTimer += Time.deltaTime;
if (_stuckTimer >= _stuckThresholdSeconds && !_escapeUIVisible)
ShowEscapeUI();
}
private void ShowEscapeUI()
{
_escapeUIVisible = true;
_onShowEscapeUI?.Raise();
}
private void HideEscapeUI()
{
_escapeUIVisible = false;
_onHideEscapeUI?.Raise();
}
/// <summary>执行最优先的逃脱路径(由 UI 按钮调用)。</summary>
public void ExecuteEscape()
{
var escape = ChooseBestEscape();
HideEscapeUI();
var sm = ServiceLocator.GetOrDefault<ISaveService>();
string scene = string.IsNullOrEmpty(escape?.targetScene)
? sm?.LastCheckpointScene
: escape.targetScene;
string spawn = string.IsNullOrEmpty(escape?.spawnId)
? sm?.LastCheckpointSpawnId
: escape.spawnId;
_onSceneLoadRequest?.Raise(new SceneLoadRequest
{
SceneName = scene,
EntryId = spawn,
TransitionType = TransitionType.Scene,
ShowLoadingScreen = true,
IsRespawn = true,
});
}
private RoomEscapeInfoSO ChooseBestEscape()
{
if (_escapeOptions == null || _escapeOptions.Length == 0) return null;
RoomEscapeInfoSO best = null;
int bestPriority = int.MinValue;
foreach (var opt in _escapeOptions)
{
if (opt == null) continue;
if (opt.requiredAbility != BaseGames.Player.AbilityType.None
&& (_player?.Stats.HasAbility(opt.requiredAbility) == false)) continue;
if (opt.priority > bestPriority)
{
bestPriority = opt.priority;
best = opt;
}
}
return best;
}
}
}