Files
2026-04-18 13:57:19 -04:00

253 lines
8.2 KiB
C#
Raw Permalink 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;
using System.Collections.Generic;
using Cielonos.MainGame;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 时间缩放通道数据,用于事件传输。
/// </summary>
[Serializable]
public struct TimeScaleChannelData
{
public bool active;
public TimeScaleMode mode;
public float fixedValue;
public AnimationCurve curve;
public float remapZero;
public float remapOne;
/// <summary>
/// 根据归一化进度计算当前通道的时间缩放值。
/// </summary>
public float Evaluate(float normalizedTime)
{
if (!active) return 1f;
if (mode == TimeScaleMode.Fixed)
{
return fixedValue;
}
float curveValue = curve != null ? curve.Evaluate(normalizedTime) : 0f;
return Mathf.LerpUnclamped(remapZero, remapOne, curveValue);
}
}
/// <summary>
/// 时间缩放震动事件。
/// </summary>
public struct TimeScaleShakeEvent
{
private static event ShakeDelegate OnEvent;
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
private static void RuntimeInitialization() { OnEvent = null; }
public delegate void ShakeDelegate(
float duration,
TimeScaleChannelData global,
TimeScaleChannelData player,
TimeScaleChannelData enemy,
TimeScaleChannelData allied,
TimeScaleChannelData nonPlayer,
bool stop
);
/// <summary>
/// 注册震动监听。
/// </summary>
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
/// <summary>
/// 取消震动监听。
/// </summary>
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
/// <summary>
/// 触发时间缩放震动事件。
/// </summary>
public static void Trigger(
float duration,
TimeScaleChannelData global = default,
TimeScaleChannelData player = default,
TimeScaleChannelData enemy = default,
TimeScaleChannelData allied = default,
TimeScaleChannelData nonPlayer = default,
bool stop = false)
{
OnEvent?.Invoke(duration, global, player, enemy, allied, nonPlayer, stop);
}
}
/// <summary>
/// 时间缩放震动实例。
/// </summary>
public class TimeScaleShakeInstance
{
public readonly float Duration;
public readonly TimeScaleChannelData Global;
public readonly TimeScaleChannelData Player;
public readonly TimeScaleChannelData Enemy;
public readonly TimeScaleChannelData Allied;
public readonly TimeScaleChannelData NonPlayer;
public float Timer;
public TimeScaleShakeInstance(
float duration,
TimeScaleChannelData global,
TimeScaleChannelData player,
TimeScaleChannelData enemy,
TimeScaleChannelData allied,
TimeScaleChannelData nonPlayer)
{
Duration = duration;
Global = global;
Player = player;
Enemy = enemy;
Allied = allied;
NonPlayer = nonPlayer;
Timer = 0f;
}
public bool IsFinished => Timer >= Duration;
}
/// <summary>
/// TimeManager 的时间缩放震动聚合器。
/// 管理多个并发时间缩放实例。
/// 当有活跃实例时,各通道取"最后激活"实例的值last wins
/// 全部结束后恢复初始值。
/// </summary>
[AddComponentMenu("SLS Utilities/Feedback Shakers/Time Scale Shaker")]
public class TimeScaleShaker : MonoBehaviour
{
private float _initGlobal;
private float _initPlayer;
private float _initEnemy;
private float _initAllied;
private float _initNonPlayer;
private bool _resolved;
private readonly List<TimeScaleShakeInstance> _activeShakes =
new List<TimeScaleShakeInstance>();
private void Awake()
{
_resolved = TryResolve();
}
private void OnEnable()
{
TimeScaleShakeEvent.Register(OnShakeEvent);
}
private void OnDisable()
{
TimeScaleShakeEvent.Unregister(OnShakeEvent);
StopAll();
}
private void Update()
{
if (!_resolved || _activeShakes.Count == 0) return;
if (TimeManager.Instance == null) return;
float dt = Time.deltaTime;
// 各通道取最后激活实例的值
float globalVal = _initGlobal;
float playerVal = _initPlayer;
float enemyVal = _initEnemy;
float alliedVal = _initAllied;
float nonPlayerVal = _initNonPlayer;
bool hasGlobal = false, hasPlayer = false, hasEnemy = false;
bool hasAllied = false, hasNonPlayer = false;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
TimeScaleShakeInstance shake = _activeShakes[i];
shake.Timer += dt;
float t = Mathf.Clamp01(shake.Timer / shake.Duration);
if (shake.Global.active) { globalVal = shake.Global.Evaluate(t); hasGlobal = true; }
if (shake.Player.active) { playerVal = shake.Player.Evaluate(t); hasPlayer = true; }
if (shake.Enemy.active) { enemyVal = shake.Enemy.Evaluate(t); hasEnemy = true; }
if (shake.Allied.active) { alliedVal = shake.Allied.Evaluate(t); hasAllied = true; }
if (shake.NonPlayer.active) { nonPlayerVal = shake.NonPlayer.Evaluate(t); hasNonPlayer = true; }
if (shake.IsFinished)
{
_activeShakes.RemoveAt(i);
}
}
if (hasGlobal) TimeManager.Instance.globalTimeScale.Value = globalVal;
if (hasPlayer) TimeManager.Instance.playerTimeScale.Value = playerVal;
if (hasEnemy) TimeManager.Instance.enemyTimeScale.Value = enemyVal;
if (hasAllied) TimeManager.Instance.alliedMinionTimeScale.Value = alliedVal;
if (hasNonPlayer) TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerVal;
if (_activeShakes.Count == 0)
{
Restore();
}
}
private void OnShakeEvent(
float duration,
TimeScaleChannelData global,
TimeScaleChannelData player,
TimeScaleChannelData enemy,
TimeScaleChannelData allied,
TimeScaleChannelData nonPlayer,
bool stop)
{
if (stop)
{
StopAll();
return;
}
if (!_resolved) _resolved = TryResolve();
if (!_resolved) return;
_activeShakes.Add(new TimeScaleShakeInstance(
duration, global, player, enemy, allied, nonPlayer
));
}
private bool TryResolve()
{
if (TimeManager.Instance == null) return false;
_initGlobal = TimeManager.Instance.globalTimeScale.Value;
_initPlayer = TimeManager.Instance.playerTimeScale.Value;
_initEnemy = TimeManager.Instance.enemyTimeScale.Value;
_initAllied = TimeManager.Instance.alliedMinionTimeScale.Value;
_initNonPlayer = TimeManager.Instance.nonPlayerTimeScale.Value;
return true;
}
private void Restore()
{
if (TimeManager.Instance == null) return;
TimeManager.Instance.globalTimeScale.Value = _initGlobal;
TimeManager.Instance.playerTimeScale.Value = _initPlayer;
TimeManager.Instance.enemyTimeScale.Value = _initEnemy;
TimeManager.Instance.alliedMinionTimeScale.Value = _initAllied;
TimeManager.Instance.nonPlayerTimeScale.Value = _initNonPlayer;
}
private void StopAll()
{
_activeShakes.Clear();
Restore();
}
}
}