Files
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

318 lines
11 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 SLSUtilities.Feedback;
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(
FeedbackContext feedbackContext,
float duration,
int priority,
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(
FeedbackContext feedbackContext,
float duration,
int priority = 0,
TimeScaleChannelData global = default,
TimeScaleChannelData player = default,
TimeScaleChannelData enemy = default,
TimeScaleChannelData allied = default,
TimeScaleChannelData nonPlayer = default,
bool stop = false)
{
OnEvent?.Invoke(feedbackContext, duration, priority, 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 readonly int Priority;
public readonly int Sequence;
public readonly FeedbackPlayer PlayerOwner;
public readonly int RuntimeFeedbackId;
public readonly int TrackIndex;
public readonly int ClipIndex;
public float Timer;
public TimeScaleShakeInstance(
FeedbackContext feedbackContext,
float duration,
int priority,
int sequence,
TimeScaleChannelData global,
TimeScaleChannelData player,
TimeScaleChannelData enemy,
TimeScaleChannelData allied,
TimeScaleChannelData nonPlayer)
{
Duration = duration;
Global = global;
Player = player;
Enemy = enemy;
Allied = allied;
NonPlayer = nonPlayer;
Priority = priority;
Sequence = sequence;
PlayerOwner = feedbackContext.player;
RuntimeFeedbackId = feedbackContext.runtimeFeedbackId;
TrackIndex = feedbackContext.trackIndex;
ClipIndex = feedbackContext.clipIndex;
Timer = 0f;
}
public bool IsFinished => Timer >= Duration;
public bool Matches(FeedbackContext feedbackContext)
{
return PlayerOwner == feedbackContext.player &&
RuntimeFeedbackId == feedbackContext.runtimeFeedbackId &&
TrackIndex == feedbackContext.trackIndex &&
ClipIndex == feedbackContext.clipIndex;
}
}
/// <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 int _nextSequence;
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;
ApplyActiveShakes(Time.deltaTime);
}
private void ApplyActiveShakes(float deltaTime)
{
// 各通道取最后激活实例的值
float globalVal = _initGlobal;
float playerVal = _initPlayer;
float enemyVal = _initEnemy;
float alliedVal = _initAllied;
float nonPlayerVal = _initNonPlayer;
TimeScaleShakeInstance globalWinner = null;
TimeScaleShakeInstance playerWinner = null;
TimeScaleShakeInstance enemyWinner = null;
TimeScaleShakeInstance alliedWinner = null;
TimeScaleShakeInstance nonPlayerWinner = null;
for (int i = _activeShakes.Count - 1; i >= 0; i--)
{
TimeScaleShakeInstance shake = _activeShakes[i];
shake.Timer += deltaTime;
if (shake.IsFinished)
{
_activeShakes.RemoveAt(i);
continue;
}
if (shake.Global.active && HasHigherOrder(shake, globalWinner)) globalWinner = shake;
if (shake.Player.active && HasHigherOrder(shake, playerWinner)) playerWinner = shake;
if (shake.Enemy.active && HasHigherOrder(shake, enemyWinner)) enemyWinner = shake;
if (shake.Allied.active && HasHigherOrder(shake, alliedWinner)) alliedWinner = shake;
if (shake.NonPlayer.active && HasHigherOrder(shake, nonPlayerWinner)) nonPlayerWinner = shake;
}
if (globalWinner != null) globalVal = globalWinner.Global.Evaluate(GetNormalizedTime(globalWinner));
if (playerWinner != null) playerVal = playerWinner.Player.Evaluate(GetNormalizedTime(playerWinner));
if (enemyWinner != null) enemyVal = enemyWinner.Enemy.Evaluate(GetNormalizedTime(enemyWinner));
if (alliedWinner != null) alliedVal = alliedWinner.Allied.Evaluate(GetNormalizedTime(alliedWinner));
if (nonPlayerWinner != null) nonPlayerVal = nonPlayerWinner.NonPlayer.Evaluate(GetNormalizedTime(nonPlayerWinner));
TimeManager.Instance.globalTimeScale.Value = globalVal;
TimeManager.Instance.playerTimeScale.Value = playerVal;
TimeManager.Instance.enemyTimeScale.Value = enemyVal;
TimeManager.Instance.alliedMinionTimeScale.Value = alliedVal;
TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerVal;
if (_activeShakes.Count == 0)
{
Restore();
}
}
private void OnShakeEvent(
FeedbackContext feedbackContext,
float duration,
int priority,
TimeScaleChannelData global,
TimeScaleChannelData player,
TimeScaleChannelData enemy,
TimeScaleChannelData allied,
TimeScaleChannelData nonPlayer,
bool stop)
{
if (stop)
{
_activeShakes.RemoveAll(instance => instance != null && instance.Matches(feedbackContext));
if (_activeShakes.Count == 0)
{
Restore();
}
else if (_resolved && TimeManager.Instance != null)
{
ApplyActiveShakes(0f);
}
return;
}
if (!_resolved) _resolved = TryResolve();
if (!_resolved) return;
_activeShakes.Add(new TimeScaleShakeInstance(
feedbackContext, duration, priority, ++_nextSequence, global, player, enemy, allied, nonPlayer
));
ApplyActiveShakes(0f);
}
private static bool HasHigherOrder(TimeScaleShakeInstance candidate, TimeScaleShakeInstance current)
{
if (current == null) return true;
if (candidate.Priority != current.Priority) return candidate.Priority > current.Priority;
return candidate.Sequence > current.Sequence;
}
private static float GetNormalizedTime(TimeScaleShakeInstance shake)
{
return Mathf.Clamp01(shake.Timer / Mathf.Max(shake.Duration, 0.001f));
}
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();
}
}
}