狗屎Minimax坏我代码
This commit is contained in:
@@ -0,0 +1,226 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.Rendering.PostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// Anime ACES 震动事件。
|
||||
/// </summary>
|
||||
public struct AnimeACESShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel exposureCurve,
|
||||
FloatCurveChannel contrastCurve,
|
||||
FloatCurveChannel saturationCurve,
|
||||
FloatCurveChannel hueCurve,
|
||||
ColorCurveChannel colorFilterCurve,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel exposureCurve = default,
|
||||
FloatCurveChannel contrastCurve = default,
|
||||
FloatCurveChannel saturationCurve = default,
|
||||
FloatCurveChannel hueCurve = default,
|
||||
ColorCurveChannel colorFilterCurve = default,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, exposureCurve, contrastCurve, saturationCurve, hueCurve, colorFilterCurve, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Anime ACES 震动实例。
|
||||
/// </summary>
|
||||
public class AnimeACESShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public readonly FloatCurveChannel ExposureCurve;
|
||||
public readonly FloatCurveChannel ContrastCurve;
|
||||
public readonly FloatCurveChannel SaturationCurve;
|
||||
public readonly FloatCurveChannel HueCurve;
|
||||
public readonly ColorCurveChannel ColorFilterCurve;
|
||||
|
||||
public AnimeACESShakeInstance(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel exposureCurve,
|
||||
FloatCurveChannel contrastCurve,
|
||||
FloatCurveChannel saturationCurve,
|
||||
FloatCurveChannel hueCurve,
|
||||
ColorCurveChannel colorFilterCurve)
|
||||
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
|
||||
{
|
||||
ExposureCurve = exposureCurve;
|
||||
ContrastCurve = contrastCurve;
|
||||
SaturationCurve = saturationCurve;
|
||||
HueCurve = hueCurve;
|
||||
ColorFilterCurve = colorFilterCurve;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AnimeACES 的震动聚合器。
|
||||
/// </summary>
|
||||
[AddComponentMenu("SLS Utilities/Feedback Shakers/Anime ACES Shaker")]
|
||||
public class AnimeACESShaker : MonoBehaviour
|
||||
{
|
||||
private AnimeACES _component;
|
||||
private float _initExposure;
|
||||
private float _initContrast;
|
||||
private float _initSaturation;
|
||||
private float _initHue;
|
||||
private Color _initColorFilter;
|
||||
private bool _resolved;
|
||||
|
||||
private readonly List<AnimeACESShakeInstance> _activeShakes = new List<AnimeACESShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_resolved = TryResolve();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
AnimeACESShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
AnimeACESShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_resolved || _activeShakes.Count == 0) return;
|
||||
|
||||
float additiveExposure = 0f;
|
||||
float additiveContrast = 0f;
|
||||
float additiveSaturation = 0f;
|
||||
float additiveHue = 0f;
|
||||
Color colorFilterAccum = Color.white;
|
||||
bool hasColorFilter = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
AnimeACESShakeInstance shake = _activeShakes[i];
|
||||
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
|
||||
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
// Exposure
|
||||
if (shake.ExposureCurve.active)
|
||||
{
|
||||
additiveExposure += shake.ExposureCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
|
||||
// Contrast
|
||||
if (shake.ContrastCurve.active)
|
||||
{
|
||||
additiveContrast += shake.ContrastCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
|
||||
// Saturation
|
||||
if (shake.SaturationCurve.active)
|
||||
{
|
||||
additiveSaturation += shake.SaturationCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
|
||||
// Hue
|
||||
if (shake.HueCurve.active)
|
||||
{
|
||||
additiveHue += shake.HueCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
|
||||
// Color Filter
|
||||
if (shake.ColorFilterCurve.active)
|
||||
{
|
||||
colorFilterAccum = shake.ColorFilterCurve.Evaluate(normalizedTime);
|
||||
hasColorFilter = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
_component.exposure.value = _initExposure + additiveExposure;
|
||||
_component.contrast.value = _initContrast + additiveContrast;
|
||||
_component.saturation.value = _initSaturation + additiveSaturation;
|
||||
_component.huePreservation.value = _initHue + additiveHue;
|
||||
|
||||
if (hasColorFilter) _component.colorFilter.value = colorFilterAccum;
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel exposureCurve,
|
||||
FloatCurveChannel contrastCurve,
|
||||
FloatCurveChannel saturationCurve,
|
||||
FloatCurveChannel hueCurve,
|
||||
ColorCurveChannel colorFilterCurve,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
if (!_resolved) _resolved = TryResolve();
|
||||
if (!_resolved) return;
|
||||
|
||||
var instance = new AnimeACESShakeInstance(
|
||||
feedbackContext,
|
||||
exposureCurve,
|
||||
contrastCurve,
|
||||
saturationCurve,
|
||||
hueCurve,
|
||||
colorFilterCurve
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private bool TryResolve()
|
||||
{
|
||||
if (_component != null) return true;
|
||||
if (PostProcessingManager.Instance == null) return false;
|
||||
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
|
||||
|
||||
_initExposure = _component.exposure.value;
|
||||
_initContrast = _component.contrast.value;
|
||||
_initSaturation = _component.saturation.value;
|
||||
_initHue = _component.huePreservation.value;
|
||||
_initColorFilter = _component.colorFilter.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Restore()
|
||||
{
|
||||
if (!_resolved) return;
|
||||
_component.exposure.value = _initExposure;
|
||||
_component.contrast.value = _initContrast;
|
||||
_component.saturation.value = _initSaturation;
|
||||
_component.huePreservation.value = _initHue;
|
||||
_component.colorFilter.value = _initColorFilter;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: fc3cd16e5556b7a4eb66e5385e4f4e60
|
||||
@@ -0,0 +1,209 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.Rendering.PostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 色散震动事件。
|
||||
/// </summary>
|
||||
public struct ChromaticAberrationShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2CurveChannel center,
|
||||
bool modifyJitter,
|
||||
FloatCurveChannel jitterCurve,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter = false,
|
||||
Vector2CurveChannel center = default,
|
||||
bool modifyJitter = false,
|
||||
FloatCurveChannel jitterCurve = default,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, intensityCurve, modifyCenter, center, modifyJitter, jitterCurve, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 色散震动实例。
|
||||
/// </summary>
|
||||
public class ChromaticAberrationShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public readonly FloatCurveChannel intensityCurve;
|
||||
public readonly bool modifyCenter;
|
||||
public readonly Vector2CurveChannel center;
|
||||
public readonly bool modifyJitter;
|
||||
public readonly FloatCurveChannel jitterCurve;
|
||||
|
||||
public ChromaticAberrationShakeInstance(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2CurveChannel center,
|
||||
bool modifyJitter,
|
||||
FloatCurveChannel jitterCurve)
|
||||
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
|
||||
{
|
||||
this.intensityCurve = intensityCurve;
|
||||
this.modifyCenter = modifyCenter;
|
||||
this.center = center;
|
||||
this.modifyJitter = modifyJitter;
|
||||
this.jitterCurve = jitterCurve;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AdvancedChromaticAberration 的震动聚合器。
|
||||
/// </summary>
|
||||
[AddComponentMenu("SLS Utilities/Feedback Shakers/Chromatic Aberration Shaker")]
|
||||
public class ChromaticAberrationShaker : MonoBehaviour
|
||||
{
|
||||
private AdvancedChromaticAberration _component;
|
||||
private float _initialIntensity;
|
||||
private Vector2 _initialCenter;
|
||||
private float _initialJitter;
|
||||
private bool _resolved;
|
||||
|
||||
private readonly List<ChromaticAberrationShakeInstance> _activeShakes = new List<ChromaticAberrationShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_resolved = TryResolve();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
ChromaticAberrationShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
ChromaticAberrationShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_resolved || _activeShakes.Count == 0) return;
|
||||
|
||||
float additiveIntensity = 0f;
|
||||
float absoluteIntensity = 0f;
|
||||
bool hasAbsolute = false;
|
||||
float additiveJitter = 0f;
|
||||
|
||||
Vector2 latestCenter = _initialCenter;
|
||||
bool hasCenter = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
ChromaticAberrationShakeInstance shake = _activeShakes[i];
|
||||
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
|
||||
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
if (shake.intensityCurve.active)
|
||||
{
|
||||
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
|
||||
if (shake.intensityCurve.relativeToInitial)
|
||||
{
|
||||
additiveIntensity += curveValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
absoluteIntensity = curveValue;
|
||||
hasAbsolute = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shake.modifyJitter && shake.jitterCurve.active)
|
||||
{
|
||||
additiveJitter += shake.jitterCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
|
||||
if (shake.modifyCenter)
|
||||
{
|
||||
latestCenter = shake.center.Evaluate(normalizedTime, _initialCenter);
|
||||
hasCenter = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
float finalIntensity = hasAbsolute ? absoluteIntensity : _initialIntensity + additiveIntensity;
|
||||
_component.intensity.value = finalIntensity;
|
||||
_component.jitterIntensity.value = _initialJitter + additiveJitter;
|
||||
|
||||
if (hasCenter) _component.center.value = latestCenter;
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2CurveChannel center,
|
||||
bool modifyJitter,
|
||||
FloatCurveChannel jitterCurve,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
if (!_resolved) _resolved = TryResolve();
|
||||
if (!_resolved) return;
|
||||
|
||||
var instance = new ChromaticAberrationShakeInstance(
|
||||
feedbackContext, intensityCurve, modifyCenter, center, modifyJitter, jitterCurve
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private bool TryResolve()
|
||||
{
|
||||
if (_component != null) return true;
|
||||
if (PostProcessingManager.Instance == null) return false;
|
||||
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
|
||||
|
||||
_initialIntensity = _component.intensity.value;
|
||||
_initialCenter = _component.center.value;
|
||||
_initialJitter = _component.jitterIntensity.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Restore()
|
||||
{
|
||||
if (!_resolved) return;
|
||||
_component.intensity.value = _initialIntensity;
|
||||
_component.center.value = _initialCenter;
|
||||
_component.jitterIntensity.value = _initialJitter;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: c15f7649d43e18b448c0acb1f9163f2c
|
||||
@@ -0,0 +1,8 @@
|
||||
fileFormatVersion: 2
|
||||
guid: d812b009316d71f4fbfc8abb6e76bc18
|
||||
folderAsset: yes
|
||||
DefaultImporter:
|
||||
externalObjects: {}
|
||||
userData:
|
||||
assetBundleName:
|
||||
assetBundleVariant:
|
||||
@@ -0,0 +1,151 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个FOV震动实例的运行时状态。
|
||||
/// </summary>
|
||||
public class CameraFovShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public FloatCurveChannel fovCurve;
|
||||
|
||||
public CameraFovShakeInstance(FeedbackTimeSettings timeSettings, IFeedbackTimeProvider timeProvider,
|
||||
FloatCurveChannel fovCurve, float duration) : base(timeSettings, timeProvider, duration)
|
||||
{
|
||||
this.fovCurve = fovCurve;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 摄像机视野角(FOV)震动事件,用于解耦 Action 与 Shaker。
|
||||
/// </summary>
|
||||
public struct CameraFovShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel fovCurve,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel fovCurve,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, fovCurve, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// CinemachineCamera FOV 震动聚合器。挂载于 CinemachineCamera 上,
|
||||
/// 监听 CameraFovShakeEvent,叠加驱动 Lens.FieldOfView。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Cielonos/Feedback Shakers/Camera FOV Shaker")]
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
public class CameraFovShaker : MonoBehaviour
|
||||
{
|
||||
private CinemachineCamera _camera;
|
||||
private float _initialFov;
|
||||
private readonly List<CameraFovShakeInstance> _activeShakes = new List<CameraFovShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_camera = GetComponent<CinemachineCamera>();
|
||||
_initialFov = _camera.Lens.FieldOfView;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
CameraFovShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CameraFovShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_camera == null) return;
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
SetFov(_initialFov);
|
||||
return;
|
||||
}
|
||||
|
||||
float additiveFov = 0f;
|
||||
float absoluteFov = 0f;
|
||||
bool hasAbsolute = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
CameraFovShakeInstance shake = _activeShakes[i];
|
||||
shake.Tick();
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
if (shake.fovCurve.relativeToInitial)
|
||||
{
|
||||
additiveFov += shake.fovCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
absoluteFov = shake.fovCurve.Evaluate(normalizedTime);
|
||||
hasAbsolute = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
float finalFov = hasAbsolute ? absoluteFov : _initialFov + additiveFov;
|
||||
SetFov(finalFov);
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel fovCurve,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
|
||||
var instance = new CameraFovShakeInstance(
|
||||
feedbackContext.timeSettings,
|
||||
feedbackContext.player.TimeProvider,
|
||||
fovCurve,
|
||||
feedbackContext.duration
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private void SetFov(float fov)
|
||||
{
|
||||
LensSettings lens = _camera.Lens;
|
||||
lens.FieldOfView = fov;
|
||||
_camera.Lens = lens;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
if (_camera != null)
|
||||
{
|
||||
SetFov(_initialFov);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: dbbfef1d7cc479c47837ff154e4a7c5e
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个位移震动实例的运行时状态。
|
||||
/// </summary>
|
||||
public class CameraPositionShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public FloatCurveChannel intensityCurve;
|
||||
public Vector3 amplitude;
|
||||
|
||||
public CameraPositionShakeInstance(
|
||||
FeedbackTimeSettings timeSettings,
|
||||
IFeedbackTimeProvider timeProvider,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
float duration)
|
||||
: base(timeSettings, timeProvider, duration)
|
||||
{
|
||||
this.intensityCurve = intensityCurve;
|
||||
this.amplitude = amplitude;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前帧的偏移量。
|
||||
/// </summary>
|
||||
public Vector3 Evaluate()
|
||||
{
|
||||
float normalizedTime = duration > 0 ? timer / duration : 1f;
|
||||
float curveValue = intensityCurve.Evaluate(normalizedTime);
|
||||
return amplitude * curveValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 摄像机位移震动事件,用于解耦 Action 与 Shaker。
|
||||
/// </summary>
|
||||
public struct CameraPositionShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, intensityCurve, amplitude, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cinemachine 位移震动器。挂载于 CinemachineCamera 上,
|
||||
/// 监听 CameraPositionShakeEvent,驱动 CinemachineCameraOffset 实现叠加震动。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Cielonos/Feedback Shakers/Camera Position Shaker")]
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
[RequireComponent(typeof(CinemachineCameraOffset))]
|
||||
public class CinemachinePositionShaker : MonoBehaviour
|
||||
{
|
||||
private CinemachineCameraOffset _offsetComponent;
|
||||
private Vector3 _initialOffset;
|
||||
private readonly List<CameraPositionShakeInstance> _activeShakes = new List<CameraPositionShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_offsetComponent = GetComponent<CinemachineCameraOffset>();
|
||||
_initialOffset = _offsetComponent.Offset;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
CameraPositionShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CameraPositionShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_offsetComponent == null) return;
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
_offsetComponent.Offset = _initialOffset;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 totalOffset = Vector3.zero;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
CameraPositionShakeInstance shake = _activeShakes[i];
|
||||
shake.Tick();
|
||||
totalOffset += shake.Evaluate();
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
_offsetComponent.Offset = _initialOffset + totalOffset;
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
|
||||
var instance = new CameraPositionShakeInstance(
|
||||
feedbackContext.timeSettings,
|
||||
feedbackContext.player.TimeProvider,
|
||||
intensityCurve,
|
||||
amplitude,
|
||||
feedbackContext.duration
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
if (_offsetComponent != null)
|
||||
{
|
||||
_offsetComponent.Offset = _initialOffset;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 08a7d10a0525af448902b60235ecf4fc
|
||||
@@ -0,0 +1,153 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Cinemachine;
|
||||
using SLSUtilities.Feedback;
|
||||
using Unity.Cinemachine;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 单个旋转震动实例的运行时状态。
|
||||
/// </summary>
|
||||
public class CameraRotationShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public FloatCurveChannel intensityCurve;
|
||||
public Vector3 amplitude;
|
||||
|
||||
public CameraRotationShakeInstance(
|
||||
FeedbackTimeSettings timeSettings,
|
||||
IFeedbackTimeProvider timeProvider,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
float duration)
|
||||
: base(timeSettings, timeProvider, duration)
|
||||
{
|
||||
this.intensityCurve = intensityCurve;
|
||||
this.amplitude = amplitude;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 计算当前帧的偏移量。
|
||||
/// </summary>
|
||||
public Vector3 Evaluate()
|
||||
{
|
||||
float normalizedTime = duration > 0 ? timer / duration : 1f;
|
||||
float curveValue = intensityCurve.Evaluate(normalizedTime);
|
||||
return amplitude * curveValue;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 摄像机旋转震动事件,用于解耦 Action 与 Shaker。
|
||||
/// </summary>
|
||||
public struct CameraRotationShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, intensityCurve, amplitude, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cinemachine 旋转震动器。挂载于 CinemachineCamera 上,
|
||||
/// 监听 CameraRotationShakeEvent,驱动 CinemachineRotationOffset 实现叠加震动。
|
||||
/// X/Y 作用于 FollowTarget 旋转,Z 作用于 Dutch 倾斜。
|
||||
/// </summary>
|
||||
[AddComponentMenu("Cielonos/Feedback Shakers/Camera Rotation Shaker")]
|
||||
[RequireComponent(typeof(CinemachineCamera))]
|
||||
[RequireComponent(typeof(CinemachineRotationOffset))]
|
||||
public class CinemachineRotationShaker : MonoBehaviour
|
||||
{
|
||||
private CinemachineRotationOffset _rotationOffset;
|
||||
private Vector3 _initialRotation;
|
||||
private readonly List<CameraRotationShakeInstance> _activeShakes = new List<CameraRotationShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_rotationOffset = GetComponent<CinemachineRotationOffset>();
|
||||
_initialRotation = _rotationOffset.rotationOffset;
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
CameraRotationShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
CameraRotationShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (_rotationOffset == null) return;
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
_rotationOffset.rotationOffset = _initialRotation;
|
||||
return;
|
||||
}
|
||||
|
||||
Vector3 totalOffset = Vector3.zero;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
CameraRotationShakeInstance shake = _activeShakes[i];
|
||||
shake.Tick();
|
||||
totalOffset += shake.Evaluate();
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
_rotationOffset.rotationOffset = _initialRotation + totalOffset;
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
Vector3 amplitude,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
_activeShakes.Add(new CameraRotationShakeInstance(
|
||||
feedbackContext.timeSettings,
|
||||
feedbackContext.player.TimeProvider,
|
||||
intensityCurve,
|
||||
amplitude,
|
||||
feedbackContext.duration
|
||||
));
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
if (_rotationOffset != null)
|
||||
{
|
||||
_rotationOffset.rotationOffset = _initialRotation;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 5c54de88d6cf4304180fbd641401b1b6
|
||||
@@ -0,0 +1,175 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.Rendering.PostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 径向模糊震动事件。
|
||||
/// </summary>
|
||||
public struct RadialBlurShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(FeedbackContext feedbackContext, FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter, Vector2 center, bool stop);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext, FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter = false, Vector2 center = default, bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, intensityCurve, modifyCenter, center, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 径向模糊震动实例。
|
||||
/// </summary>
|
||||
public class RadialBlurShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public readonly FloatCurveChannel intensityCurve;
|
||||
public readonly bool modifyCenter;
|
||||
public readonly Vector2 center;
|
||||
|
||||
public RadialBlurShakeInstance(
|
||||
FeedbackContext feedbackContext, FloatCurveChannel intensityCurve, bool modifyCenter, Vector2 center) :
|
||||
base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
|
||||
{
|
||||
this.intensityCurve = intensityCurve;
|
||||
this.modifyCenter = modifyCenter;
|
||||
this.center = center;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// RadialBlur 的震动聚合器。
|
||||
/// </summary>
|
||||
[AddComponentMenu("SLS Utilities/Feedback Shakers/Radial Blur Shaker")]
|
||||
public class RadialBlurShaker : MonoBehaviour
|
||||
{
|
||||
private RadialBlur _component;
|
||||
private float _initialBlurRadius;
|
||||
private float _initialCenterX;
|
||||
private float _initialCenterY;
|
||||
private bool _resolved;
|
||||
|
||||
private readonly List<RadialBlurShakeInstance> _activeShakes = new List<RadialBlurShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_resolved = TryResolve();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
RadialBlurShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
RadialBlurShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_resolved || _activeShakes.Count == 0) return;
|
||||
|
||||
float additiveDelta = 0f;
|
||||
float absoluteTarget = 0f;
|
||||
bool hasAbsolute = false;
|
||||
|
||||
Vector2 latestCenter = new Vector2(_initialCenterX, _initialCenterY);
|
||||
bool hasCenter = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
RadialBlurShakeInstance shake = _activeShakes[i];
|
||||
shake.Tick();
|
||||
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
if (shake.intensityCurve.relativeToInitial)
|
||||
{
|
||||
// 相对模式:累加曲线值
|
||||
additiveDelta += shake.intensityCurve.Evaluate(normalizedTime);
|
||||
}
|
||||
else
|
||||
{
|
||||
// 绝对模式:使用曲线值作为目标值
|
||||
absoluteTarget = shake.intensityCurve.Evaluate(normalizedTime);
|
||||
hasAbsolute = true;
|
||||
}
|
||||
|
||||
if (shake.modifyCenter)
|
||||
{
|
||||
latestCenter = shake.center;
|
||||
hasCenter = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
float finalRadius = hasAbsolute ? absoluteTarget : _initialBlurRadius + additiveDelta;
|
||||
_component.blurRadius.value = finalRadius;
|
||||
|
||||
if (hasCenter)
|
||||
{
|
||||
_component.radialCenterX.value = latestCenter.x;
|
||||
_component.radialCenterY.value = latestCenter.y;
|
||||
}
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShakeEvent( FeedbackContext feedbackContext, FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter, Vector2 center, bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
if (!_resolved) _resolved = TryResolve();
|
||||
if (!_resolved) return;
|
||||
|
||||
var instance = new RadialBlurShakeInstance(feedbackContext, intensityCurve, modifyCenter, center);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private bool TryResolve()
|
||||
{
|
||||
if (_component != null) return true;
|
||||
if (PostProcessingManager.Instance == null) return false;
|
||||
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
|
||||
|
||||
_initialBlurRadius = _component.blurRadius.value;
|
||||
_initialCenterX = _component.radialCenterX.value;
|
||||
_initialCenterY = _component.radialCenterY.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Restore()
|
||||
{
|
||||
if (!_resolved) return;
|
||||
_component.blurRadius.value = _initialBlurRadius;
|
||||
_component.radialCenterX.value = _initialCenterX;
|
||||
_component.radialCenterY.value = _initialCenterY;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 569f4a4691c95ba448cf7309993ca9a9
|
||||
@@ -0,0 +1,190 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.Rendering.PostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 黑白闪震动事件。
|
||||
/// </summary>
|
||||
public struct StrobeFlashShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
float duration,
|
||||
bool modifyExtra,
|
||||
FloatCurveChannel frequencyCurve,
|
||||
ColorCurveChannel colorHigh,
|
||||
ColorCurveChannel colorLow,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
float duration,
|
||||
bool modifyExtra = false,
|
||||
FloatCurveChannel frequencyCurve = default,
|
||||
ColorCurveChannel colorHigh = default,
|
||||
ColorCurveChannel colorLow = default,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, duration, modifyExtra, frequencyCurve, colorHigh, colorLow, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 黑白闪震动实例。
|
||||
/// </summary>
|
||||
public class StrobeFlashShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public readonly bool modifyExtra;
|
||||
public readonly FloatCurveChannel frequencyCurve;
|
||||
public readonly ColorCurveChannel colorHigh;
|
||||
public readonly ColorCurveChannel colorLow;
|
||||
|
||||
public StrobeFlashShakeInstance(
|
||||
FeedbackContext feedbackContext,
|
||||
bool modifyExtra,
|
||||
FloatCurveChannel frequencyCurve,
|
||||
ColorCurveChannel colorHigh,
|
||||
ColorCurveChannel colorLow)
|
||||
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
|
||||
{
|
||||
this.modifyExtra = modifyExtra;
|
||||
this.frequencyCurve = frequencyCurve;
|
||||
this.colorHigh = colorHigh;
|
||||
this.colorLow = colorLow;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// StrobeFlash 的震动聚合器。
|
||||
/// </summary>
|
||||
[AddComponentMenu("SLS Utilities/Feedback Shakers/Strobe Flash Shaker")]
|
||||
public class StrobeFlashShaker : MonoBehaviour
|
||||
{
|
||||
private StrobeFlash _component;
|
||||
private float _initialFrequency;
|
||||
private Color _initialColorHigh;
|
||||
private Color _initialColorLow;
|
||||
private bool _resolved;
|
||||
|
||||
private readonly List<StrobeFlashShakeInstance> _activeShakes = new List<StrobeFlashShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_resolved = TryResolve();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
StrobeFlashShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
StrobeFlashShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_resolved || _activeShakes.Count == 0) return;
|
||||
|
||||
float latestFrequency = _initialFrequency;
|
||||
Color latestColorHigh = _initialColorHigh;
|
||||
Color latestColorLow = _initialColorLow;
|
||||
bool hasExtra = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
StrobeFlashShakeInstance shake = _activeShakes[i];
|
||||
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
|
||||
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
if (shake.modifyExtra && !shake.IsFinished)
|
||||
{
|
||||
latestFrequency = shake.frequencyCurve.Evaluate(normalizedTime);
|
||||
latestColorHigh = shake.colorHigh.Evaluate(normalizedTime);
|
||||
latestColorLow = shake.colorLow.Evaluate(normalizedTime);
|
||||
hasExtra = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
_component.enableEffect.value = true;
|
||||
_component.autoFlash.value = true;
|
||||
|
||||
if (hasExtra)
|
||||
{
|
||||
_component.frequency.value = latestFrequency;
|
||||
_component.colorHigh.value = latestColorHigh;
|
||||
_component.colorLow.value = latestColorLow;
|
||||
}
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
float duration,
|
||||
bool modifyExtra,
|
||||
FloatCurveChannel frequencyCurve,
|
||||
ColorCurveChannel colorHigh,
|
||||
ColorCurveChannel colorLow,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
if (!_resolved) _resolved = TryResolve();
|
||||
if (!_resolved) return;
|
||||
|
||||
var instance = new StrobeFlashShakeInstance(
|
||||
feedbackContext, modifyExtra, frequencyCurve, colorHigh, colorLow
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private bool TryResolve()
|
||||
{
|
||||
if (_component != null) return true;
|
||||
if (PostProcessingManager.Instance == null) return false;
|
||||
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
|
||||
|
||||
_initialFrequency = _component.frequency.value;
|
||||
_initialColorHigh = _component.colorHigh.value;
|
||||
_initialColorLow = _component.colorLow.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Restore()
|
||||
{
|
||||
if (!_resolved) return;
|
||||
_component.frequency.value = _initialFrequency;
|
||||
_component.colorHigh.value = _initialColorHigh;
|
||||
_component.colorLow.value = _initialColorLow;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 588e6dd88aea55748adfb8b9aa48a518
|
||||
@@ -0,0 +1,252 @@
|
||||
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();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 33bd71b84e3b907468514ad946229fbc
|
||||
@@ -0,0 +1,269 @@
|
||||
using System.Collections.Generic;
|
||||
using SLSUtilities.Feedback;
|
||||
using SLSUtilities.Rendering.PostProcessing;
|
||||
using UnityEngine;
|
||||
|
||||
namespace Cielonos.MainGame.Effects.Feedback
|
||||
{
|
||||
/// <summary>
|
||||
/// 暗角震动事件。
|
||||
/// </summary>
|
||||
public struct VignetteShakeEvent
|
||||
{
|
||||
private static event ShakeDelegate OnEvent;
|
||||
|
||||
[RuntimeInitializeOnLoadMethod(RuntimeInitializeLoadType.SubsystemRegistration)]
|
||||
private static void RuntimeInitialization() { OnEvent = null; }
|
||||
|
||||
public delegate void ShakeDelegate(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2 center,
|
||||
bool modifyColors,
|
||||
ColorCurveChannel colorOuter,
|
||||
ColorCurveChannel colorInner,
|
||||
bool modifyShape,
|
||||
FloatCurveChannel smoothnessCurve,
|
||||
FloatCurveChannel roundnessCurve,
|
||||
bool stop
|
||||
);
|
||||
|
||||
public static void Register(ShakeDelegate callback) { OnEvent += callback; }
|
||||
public static void Unregister(ShakeDelegate callback) { OnEvent -= callback; }
|
||||
|
||||
public static void Trigger(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter = false,
|
||||
Vector2 center = default,
|
||||
bool modifyColors = false,
|
||||
ColorCurveChannel colorOuter = default,
|
||||
ColorCurveChannel colorInner = default,
|
||||
bool modifyShape = false,
|
||||
FloatCurveChannel smoothnessCurve = default,
|
||||
FloatCurveChannel roundnessCurve = default,
|
||||
bool stop = false)
|
||||
{
|
||||
OnEvent?.Invoke(feedbackContext, intensityCurve, modifyCenter, center,
|
||||
modifyColors, colorOuter, colorInner, modifyShape,
|
||||
smoothnessCurve, roundnessCurve, stop);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 暗角震动实例。
|
||||
/// </summary>
|
||||
public class VignetteShakeInstance : ShakeInstanceBase
|
||||
{
|
||||
public readonly FloatCurveChannel intensityCurve;
|
||||
public readonly bool modifyCenter;
|
||||
public readonly Vector2 center;
|
||||
public readonly bool modifyColors;
|
||||
public readonly ColorCurveChannel colorOuter;
|
||||
public readonly ColorCurveChannel colorInner;
|
||||
public readonly bool modifyShape;
|
||||
public readonly FloatCurveChannel smoothnessCurve;
|
||||
public readonly FloatCurveChannel roundnessCurve;
|
||||
|
||||
public VignetteShakeInstance(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2 center,
|
||||
bool modifyColors,
|
||||
ColorCurveChannel colorOuter,
|
||||
ColorCurveChannel colorInner,
|
||||
bool modifyShape,
|
||||
FloatCurveChannel smoothnessCurve,
|
||||
FloatCurveChannel roundnessCurve)
|
||||
: base(feedbackContext.timeSettings, feedbackContext.player.TimeProvider, feedbackContext.duration)
|
||||
{
|
||||
this.intensityCurve = intensityCurve;
|
||||
this.modifyCenter = modifyCenter;
|
||||
this.center = center;
|
||||
this.modifyColors = modifyColors;
|
||||
this.colorOuter = colorOuter;
|
||||
this.colorInner = colorInner;
|
||||
this.modifyShape = modifyShape;
|
||||
this.smoothnessCurve = smoothnessCurve;
|
||||
this.roundnessCurve = roundnessCurve;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// AdvancedVignette 的震动聚合器。
|
||||
/// </summary>
|
||||
[AddComponentMenu("SLS Utilities/Feedback Shakers/Vignette Shaker")]
|
||||
public class VignetteShaker : MonoBehaviour
|
||||
{
|
||||
private AdvancedVignette _component;
|
||||
private float _initialIntensity;
|
||||
private Vector2 _initialCenter;
|
||||
private Color _initialColorOuter;
|
||||
private Color _initialColorInner;
|
||||
private float _initialSmoothness;
|
||||
private float _initialRoundness;
|
||||
private bool _resolved;
|
||||
|
||||
private readonly List<VignetteShakeInstance> _activeShakes = new List<VignetteShakeInstance>();
|
||||
|
||||
private void Awake()
|
||||
{
|
||||
_resolved = TryResolve();
|
||||
}
|
||||
|
||||
private void OnEnable()
|
||||
{
|
||||
VignetteShakeEvent.Register(OnShakeEvent);
|
||||
}
|
||||
|
||||
private void OnDisable()
|
||||
{
|
||||
VignetteShakeEvent.Unregister(OnShakeEvent);
|
||||
StopAll();
|
||||
}
|
||||
|
||||
private void Update()
|
||||
{
|
||||
if (!_resolved || _activeShakes.Count == 0) return;
|
||||
|
||||
float additiveIntensity = 0f;
|
||||
float absoluteIntensity = 0f;
|
||||
bool hasAbsolute = false;
|
||||
|
||||
Vector2 latestCenter = _initialCenter;
|
||||
Color latestColorOuter = _initialColorOuter;
|
||||
Color latestColorInner = _initialColorInner;
|
||||
float latestSmoothness = _initialSmoothness;
|
||||
float latestRoundness = _initialRoundness;
|
||||
bool hasCenter = false;
|
||||
bool hasColors = false;
|
||||
bool hasShape = false;
|
||||
|
||||
for (int i = _activeShakes.Count - 1; i >= 0; i--)
|
||||
{
|
||||
VignetteShakeInstance shake = _activeShakes[i];
|
||||
shake.timer += shake.timeProvider.GetDeltaTime(shake.timeSettings);
|
||||
|
||||
float normalizedTime = shake.timer / shake.duration;
|
||||
|
||||
if (shake.intensityCurve.active)
|
||||
{
|
||||
float curveValue = shake.intensityCurve.Evaluate(normalizedTime);
|
||||
if (shake.intensityCurve.relativeToInitial)
|
||||
{
|
||||
additiveIntensity += curveValue;
|
||||
}
|
||||
else
|
||||
{
|
||||
absoluteIntensity = curveValue;
|
||||
hasAbsolute = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (shake.modifyCenter)
|
||||
{
|
||||
latestCenter = shake.center;
|
||||
hasCenter = true;
|
||||
}
|
||||
|
||||
if (shake.modifyColors)
|
||||
{
|
||||
latestColorOuter = shake.colorOuter.Evaluate(normalizedTime);
|
||||
latestColorInner = shake.colorInner.Evaluate(normalizedTime);
|
||||
hasColors = true;
|
||||
}
|
||||
|
||||
if (shake.modifyShape)
|
||||
{
|
||||
latestSmoothness = shake.smoothnessCurve.Evaluate(normalizedTime);
|
||||
latestRoundness = shake.roundnessCurve.Evaluate(normalizedTime);
|
||||
hasShape = true;
|
||||
}
|
||||
|
||||
if (shake.IsFinished)
|
||||
{
|
||||
_activeShakes.RemoveAt(i);
|
||||
}
|
||||
}
|
||||
|
||||
float finalIntensity = hasAbsolute ? absoluteIntensity : _initialIntensity + additiveIntensity;
|
||||
_component.intensity.value = finalIntensity;
|
||||
|
||||
if (hasCenter) _component.center.value = latestCenter;
|
||||
if (hasColors)
|
||||
{
|
||||
_component.colorOuter.value = latestColorOuter;
|
||||
_component.colorInner.value = latestColorInner;
|
||||
}
|
||||
if (hasShape)
|
||||
{
|
||||
_component.smoothness.value = latestSmoothness;
|
||||
_component.roundness.value = latestRoundness;
|
||||
}
|
||||
|
||||
if (_activeShakes.Count == 0)
|
||||
{
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
|
||||
private void OnShakeEvent(
|
||||
FeedbackContext feedbackContext,
|
||||
FloatCurveChannel intensityCurve,
|
||||
bool modifyCenter,
|
||||
Vector2 center,
|
||||
bool modifyColors,
|
||||
ColorCurveChannel colorOuter,
|
||||
ColorCurveChannel colorInner,
|
||||
bool modifyShape,
|
||||
FloatCurveChannel smoothnessCurve,
|
||||
FloatCurveChannel roundnessCurve,
|
||||
bool stop)
|
||||
{
|
||||
if (stop) { StopAll(); return; }
|
||||
if (!_resolved) _resolved = TryResolve();
|
||||
if (!_resolved) return;
|
||||
|
||||
var instance = new VignetteShakeInstance(
|
||||
feedbackContext, intensityCurve, modifyCenter, center,
|
||||
modifyColors, colorOuter, colorInner, modifyShape,
|
||||
smoothnessCurve, roundnessCurve
|
||||
);
|
||||
_activeShakes.Add(instance);
|
||||
}
|
||||
|
||||
private bool TryResolve()
|
||||
{
|
||||
if (_component != null) return true;
|
||||
if (PostProcessingManager.Instance == null) return false;
|
||||
if (!PostProcessingManager.Instance.GetVolumeComponent(out _component)) return false;
|
||||
|
||||
_initialIntensity = _component.intensity.value;
|
||||
_initialCenter = _component.center.value;
|
||||
_initialColorOuter = _component.colorOuter.value;
|
||||
_initialColorInner = _component.colorInner.value;
|
||||
_initialSmoothness = _component.smoothness.value;
|
||||
_initialRoundness = _component.roundness.value;
|
||||
return true;
|
||||
}
|
||||
|
||||
private void Restore()
|
||||
{
|
||||
if (!_resolved) return;
|
||||
_component.intensity.value = _initialIntensity;
|
||||
_component.center.value = _initialCenter;
|
||||
_component.colorOuter.value = _initialColorOuter;
|
||||
_component.colorInner.value = _initialColorInner;
|
||||
_component.smoothness.value = _initialSmoothness;
|
||||
_component.roundness.value = _initialRoundness;
|
||||
}
|
||||
|
||||
private void StopAll()
|
||||
{
|
||||
_activeShakes.Clear();
|
||||
Restore();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
fileFormatVersion: 2
|
||||
guid: 129b8b0330a4e5741a1f3121f9d01d22
|
||||
Reference in New Issue
Block a user