Files
zeling_v2/Assets/_Game/Scripts/Combat/HitStopManager.cs

103 lines
4.3 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 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;
}
}
}