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;
}
}
}