摄像机区域的架构改动
This commit is contained in:
140
Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs
Normal file
140
Assets/_Game/Scripts/Support/AntiSoftlock/AntiSoftlockSystem.cs
Normal file
@@ -0,0 +1,140 @@
|
||||
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,
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user