using System.Collections; using UnityEngine; using BaseGames.Core; namespace BaseGames.Combat { /// /// 命中冻帧服务接口。 /// public interface IHitStopService { /// 冻帧 帧(以 fixedDeltaTime 换算为实际时长)。 void FreezeFrames(int frames); /// 冻帧指定时长(Unscaled 秒)。 void FreezeDuration(float unscaledSeconds); /// 游戏正常时间缩放(默认 1);子弹时间等功能修改此属性。 float BaseTimeScale { get; set; } } /// /// 命中冻帧服务(HitStop)(架构 06_CombatModule §16)。 /// 通过短暂将 Time.timeScale 设为 0 实现"冻帧"效果,强化打击感。 /// 常驻 Persistent 场景,由 GameManager 持有;通过 ServiceLocator 注册访问。 /// /// 设计说明: /// - 多次并发请求取最长持续时间(StopCoroutine + 重启) /// - 使用 WaitForSecondsRealtime 确保 timeScale=0 时协程仍能恢复 /// - OnDestroy 强制还原 timeScale,防止异常退出导致游戏卡死 /// [DefaultExecutionOrder(-400)] public class HitStopManager : MonoBehaviour, IHitStopService { /// 游戏正常时间缩放(默认 1);通过属性读取以便外部修改子弹时间时保留基准值。 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() != null) { Destroy(gameObject); return; } ServiceLocator.Register(this); } private void OnDestroy() { // 安全恢复:防止场景卸载/异常退出时 timeScale 永久为 0 Time.timeScale = _baseTimeScale; ServiceLocator.Unregister(this); } // ── 公共 API ────────────────────────────────────────────────────── /// /// 冻帧 帧(以 fixedDeltaTime 换算为实际时长)。 /// 若已有冻帧进行中,取两者中持续时间较长的(避免短请求截断较长的冻帧)。 /// /// 冻帧帧数(fixedDeltaTime 单位)。0 或负数无效。 public void FreezeFrames(int frames) { if (frames <= 0) return; FreezeDuration(frames * Time.fixedDeltaTime); } /// /// 冻帧指定时长(Unscaled 实际秒数)。 /// 若已有冻帧进行中,取两者中较长的。 /// /// 实际时长(秒),不受 timeScale 影响。 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; } } }