多轮审查和修复
This commit is contained in:
102
Assets/Scripts/Combat/HitStopManager.cs
Normal file
102
Assets/Scripts/Combat/HitStopManager.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using System.Collections;
|
||||
using UnityEngine;
|
||||
using BaseGames.Core;
|
||||
|
||||
namespace BaseGames.Combat
|
||||
{
|
||||
/// <summary>
|
||||
/// 命中冻帧服务接口。
|
||||
/// </summary>
|
||||
public interface IHitStopService
|
||||
{
|
||||
/// <summary>冻帧 <paramref name="frames"/> 帧(以 fixedDeltaTime 换算为实际时长)。</summary>
|
||||
void FreezeFrames(int frames);
|
||||
/// <summary>冻帧指定时长(Unscaled 秒)。</summary>
|
||||
void FreezeDuration(float unscaledSeconds);
|
||||
/// <summary>游戏正常时间缩放(默认 1);子弹时间等功能修改此属性。</summary>
|
||||
float BaseTimeScale { get; set; }
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 命中冻帧服务(HitStop)(架构 06_CombatModule §16)。
|
||||
/// 通过短暂将 Time.timeScale 设为 0 实现"冻帧"效果,强化打击感。
|
||||
/// 常驻 Persistent 场景,由 GameManager 持有;通过 ServiceLocator 注册访问。
|
||||
///
|
||||
/// 设计说明:
|
||||
/// - 多次并发请求取最长持续时间(StopCoroutine + 重启)
|
||||
/// - 使用 WaitForSecondsRealtime 确保 timeScale=0 时协程仍能恢复
|
||||
/// - OnDestroy 强制还原 timeScale,防止异常退出导致游戏卡死
|
||||
/// </summary>
|
||||
[DefaultExecutionOrder(-400)]
|
||||
public class HitStopManager : MonoBehaviour, IHitStopService
|
||||
{
|
||||
/// <summary>游戏正常时间缩放(默认 1);通过属性读取以便外部修改子弹时间时保留基准值。</summary>
|
||||
public float BaseTimeScale
|
||||
{
|
||||
get => _baseTimeScale;
|
||||
set => _baseTimeScale = Mathf.Clamp(value, 0.01f, 10f);
|
||||
}
|
||||
private float _baseTimeScale = 1f;
|
||||
|
||||
private Coroutine _activeRoutine;
|
||||
private float _freezeEndTime;
|
||||
|
||||
// ── 生命周期 ──────────────────────────────────────────────────────
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
if (ServiceLocator.GetOrDefault<IHitStopService>() != null) { Destroy(gameObject); return; }
|
||||
ServiceLocator.Register<IHitStopService>(this);
|
||||
}
|
||||
|
||||
private void OnDestroy()
|
||||
{
|
||||
// 安全恢复:防止场景卸载/异常退出时 timeScale 永久为 0
|
||||
Time.timeScale = _baseTimeScale;
|
||||
ServiceLocator.Unregister<IHitStopService>(this);
|
||||
}
|
||||
|
||||
// ── 公共 API ──────────────────────────────────────────────────────
|
||||
|
||||
/// <summary>
|
||||
/// 冻帧 <paramref name="frames"/> 帧(以 fixedDeltaTime 换算为实际时长)。
|
||||
/// 若已有冻帧进行中,取两者中持续时间较长的(避免短请求截断较长的冻帧)。
|
||||
/// </summary>
|
||||
/// <param name="frames">冻帧帧数(fixedDeltaTime 单位)。0 或负数无效。</param>
|
||||
public void FreezeFrames(int frames)
|
||||
{
|
||||
if (frames <= 0) return;
|
||||
FreezeDuration(frames * Time.fixedDeltaTime);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 冻帧指定时长(Unscaled 实际秒数)。
|
||||
/// 若已有冻帧进行中,取两者中较长的。
|
||||
/// </summary>
|
||||
/// <param name="unscaledSeconds">实际时长(秒),不受 timeScale 影响。</param>
|
||||
public void FreezeDuration(float unscaledSeconds)
|
||||
{
|
||||
if (unscaledSeconds <= 0f) return;
|
||||
|
||||
float newEndTime = Time.unscaledTime + unscaledSeconds;
|
||||
// 已有更长的冻帧进行中,放弃短请求,避免截断
|
||||
if (_activeRoutine != null && newEndTime <= _freezeEndTime) return;
|
||||
|
||||
_freezeEndTime = newEndTime;
|
||||
if (_activeRoutine != null)
|
||||
StopCoroutine(_activeRoutine);
|
||||
|
||||
_activeRoutine = StartCoroutine(FreezeRoutine(unscaledSeconds));
|
||||
}
|
||||
|
||||
// ── 内部实现 ──────────────────────────────────────────────────────
|
||||
|
||||
private IEnumerator FreezeRoutine(float unscaledSeconds)
|
||||
{
|
||||
Time.timeScale = 0f;
|
||||
yield return new WaitForSecondsRealtime(unscaledSeconds);
|
||||
Time.timeScale = _baseTimeScale;
|
||||
_activeRoutine = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user