狗屎Minimax坏我代码

This commit is contained in:
SoulliesOfficial
2026-04-18 13:57:19 -04:00
parent 41140a2017
commit 7379583165
473 changed files with 34480 additions and 8069 deletions

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 8b6df685fb7fcb144a2f10822632a9f5
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,43 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// Cinemachine摄像机震动Action的基类。
/// 封装了统一的触发逻辑和参数定义。
/// </summary>
[Serializable]
public abstract class CinemachineActionBase : FeedbackActionBase
{
public override void OnStart(FeedbackContext context)
{
TriggerEvent(context);
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
}
public override void OnEnd(FeedbackContext context)
{
}
public override void OnInterrupt(FeedbackContext context)
{
StopEvent(context);
}
/// <summary>
/// 触发震动事件(由子类实现)。
/// </summary>
protected abstract void TriggerEvent(FeedbackContext context);
/// <summary>
/// 停止震动事件(由子类实现)。
/// </summary>
protected abstract void StopEvent(FeedbackContext context);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: bfcba53df80e9ad468aa639ef88e9c57

View File

@@ -0,0 +1,181 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 曲线通道模块,用于定义一个可复用的曲线震动参数。
/// 包含激活状态、曲线定义、重映射范围。
/// </summary>
[Serializable]
public struct FloatCurveChannel
{
/// <summary>
/// 是否启用此通道。
/// </summary>
public bool active;
/// <summary>
/// 震动曲线X轴为归一化时间[0,1]Y轴为强度[0,1]。
/// </summary>
[ShowIf("active")]
[ShakeCurvePreset]
public AnimationCurve curve;
/// <summary>
/// 曲线值0对应的实际数值。
/// </summary>
[ShowIf("active")]
[LabelText("Remap Min")]
public float remapMin;
/// <summary>
/// 曲线值1对应的实际数值。
/// </summary>
[ShowIf("active")]
[LabelText("Remap Max")]
public float remapMax;
/// <summary>
/// 是否相对初始值叠加。
/// </summary>
[Tooltip("开启时,结果叠加在初始值上;关闭时,结果为绝对值")]
public bool relativeToInitial;
/// <summary>
/// 创建默认的曲线通道。
/// </summary>
public static FloatCurveChannel CreateDefault(bool active = true, float remapMin = 0f, float remapMax = 1f, bool relativeToInitial = true)
{
return new FloatCurveChannel
{
active = active,
curve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
new Keyframe(1f, 0f)
),
remapMin = remapMin,
remapMax = remapMax,
relativeToInitial = relativeToInitial
};
}
/// <summary>
/// 根据归一化时间计算当前值。
/// </summary>
public readonly float Evaluate(float normalizedTime)
{
if (!active || curve == null) return 0f;
float t = Mathf.Clamp01(normalizedTime);
float curveValue = curve.Evaluate(t);
return Mathf.LerpUnclamped(remapMin, remapMax, curveValue);
}
}
/// <summary>
/// 带颜色选项的曲线通道。
/// </summary>
[Serializable]
public struct ColorCurveChannel
{
/// <summary>
/// 是否启用此通道。
/// </summary>
public bool active;
/// <summary>
/// 颜色渐变。
/// </summary>
[ShowIf("active")]
[LabelText("颜色渐变")]
public Gradient gradient;
/// <summary>
/// 创建默认的颜色曲线通道。
/// </summary>
public static ColorCurveChannel CreateDefault()
{
return new ColorCurveChannel
{
active = true,
gradient = new Gradient()
};
}
/// <summary>
/// 根据归一化时间获取颜色。
/// </summary>
public Color Evaluate(float normalizedTime)
{
if (!active || gradient == null) return Color.white;
return gradient.Evaluate(Mathf.Clamp01(normalizedTime));
}
}
/// <summary>
/// 带Vector2选项的曲线通道用于中心点等
/// </summary>
[Serializable]
public struct Vector2CurveChannel
{
public bool active;
[ShowIf("active")]
[LabelText("曲线 X")]
[ShakeCurvePreset]
public AnimationCurve curveX;
[ShowIf("active")]
[LabelText("曲线 Y")]
[ShakeCurvePreset]
public AnimationCurve curveY;
[ShowIf("active")]
[LabelText("Remap Min")]
public Vector2 remapMin;
[ShowIf("active")]
[LabelText("Remap Max")]
public Vector2 remapMax;
/// <summary>
/// 是否相对初始值叠加。
/// </summary>
[TitleGroup("高级设置")]
[LabelText("相对初始值")]
[Tooltip("开启时,结果叠加在初始值上;关闭时,结果为绝对值")]
public bool relativeToInitial;
public static Vector2CurveChannel CreateDefault()
{
return new Vector2CurveChannel
{
active = true,
curveX = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)),
curveY = new AnimationCurve(new Keyframe(0f, 0f), new Keyframe(1f, 1f)),
remapMin = Vector2.zero,
remapMax = Vector2.one
};
}
/// <summary>
/// 根据归一化时间计算Vector2值。
/// </summary>
public Vector2 Evaluate(float normalizedTime, Vector2 initialValue)
{
if (!active) return Vector2.zero;
float t = Mathf.Clamp01(normalizedTime);
float x = curveX?.Evaluate(t) ?? 0f;
float y = curveY?.Evaluate(t) ?? 0f;
Vector2 remappedValue = new Vector2(
Mathf.LerpUnclamped(remapMin.x, remapMax.x, x),
Mathf.LerpUnclamped(remapMin.y, remapMax.y, y)
);
return relativeToInitial ? initialValue + remappedValue : remappedValue;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: e453f5e1c6b44764f8284800f9f46479

View File

@@ -0,0 +1,44 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 后处理震动Action的基类。
/// 封装了统一的曲线参数定义和生命周期管理。
/// 子类需要实现TriggerEvent和StopEvent抽象方法。
/// </summary>
[Serializable]
public abstract class PostprocessingActionBase : FeedbackActionBase
{
public override void OnStart(FeedbackContext context)
{
TriggerEvent(context);
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
}
public override void OnEnd(FeedbackContext context)
{
}
public override void OnInterrupt(FeedbackContext context)
{
StopEvent(context);
}
/// <summary>
/// 触发震动事件(由子类实现)。
/// </summary>
protected abstract void TriggerEvent(FeedbackContext context);
/// <summary>
/// 停止震动事件(由子类实现)。
/// </summary>
protected abstract void StopEvent(FeedbackContext context);
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: a4a098e68b58d5e4487a805af4437aea

View File

@@ -1,102 +0,0 @@
using System;
using MoreMountains.Feedbacks;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 摄像机位移震动反馈,通过 MMCinemachinePositionShakeEvent 触发现有的 Shaker。
/// Shaker 负责处理多个震动的叠加混合。
/// </summary>
[Serializable]
public class CameraPositionShakeAction : FeedbackActionBase
{
public override string DisplayName => "Camera Position Shake";
/// <summary>
/// 震动曲线,定义震动强度随时间的变化。
/// </summary>
[Title("Position Shake")]
[LabelText("Shake Curve")]
public AnimationCurve shakeCurve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.2f, 1f),
new Keyframe(1f, 0f)
);
/// <summary>
/// 最大位移振幅(本地空间)。
/// </summary>
[LabelText("Amplitude")]
public Vector3 positionAmplitude = new Vector3(0.5f, 0.5f, 0f);
/// <summary>
/// 方向影响设置。
/// </summary>
[Title("Direction")]
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
/// <summary>
/// 距离衰减:根据摄像机与 owner 的距离衰减震动强度。
/// </summary>
[Title("Distance Attenuation")]
[LabelText("Use Attenuation")]
public bool useAttenuation;
/// <summary>
/// 全强度的最大距离。
/// </summary>
[ShowIf("useAttenuation")]
[LabelText("Attenuation Range")]
public float attenuationRange = 50f;
/// <summary>
/// 距离-强度衰减曲线0=近处/全强度1=远处/无强度)。
/// </summary>
[ShowIf("useAttenuation")]
[LabelText("Attenuation Curve")]
public AnimationCurve attenuationCurve = new AnimationCurve(
new Keyframe(0f, 1f),
new Keyframe(1f, 0f)
);
public override void OnStart(FeedbackContext context)
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(positionAmplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
MMCinemachinePositionShakeEvent.Trigger(
null,
shakeCurve,
context.duration,
finalAmplitude,
intensityMultiplier
);
}
public override void OnInterrupt(FeedbackContext context)
{
MMCinemachinePositionShakeEvent.Trigger(
null, shakeCurve, 0f, Vector3.zero, 0f,
stop: true
);
}
/// <summary>
/// 计算距离衰减系数。
/// </summary>
private float ComputeAttenuation(FeedbackContext context)
{
if (!useAttenuation || context.owner == null) return 1f;
Camera mainCamera = Camera.main;
if (mainCamera == null) return 1f;
float distance = Vector3.Distance(context.owner.position, mainCamera.transform.position);
float normalizedDistance = Mathf.Clamp01(distance / attenuationRange);
return attenuationCurve.Evaluate(normalizedDistance);
}
}
}

View File

@@ -1,98 +0,0 @@
using System;
using MoreMountains.FeedbacksForThirdParty;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 摄像机旋转震动反馈,通过 MMCinemachineRotationShakeEvent 触发现有的 Shaker。
/// X/Y 作用于 FollowTarget 旋转Z 作用于 Dutch 倾斜。
/// </summary>
[Serializable]
public class CameraRotationShakeAction : FeedbackActionBase
{
public override string DisplayName => "Camera Rotation Shake";
/// <summary>
/// 震动曲线,定义震动强度随时间的变化。
/// </summary>
[Title("Rotation Shake")]
[LabelText("Shake Curve")]
public AnimationCurve shakeCurve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.2f, 1f),
new Keyframe(1f, 0f)
);
/// <summary>
/// 最大旋转角度振幅。X/Y -> FollowTarget, Z -> Dutch。
/// </summary>
[LabelText("Rotation Amplitude")]
public Vector3 rotationAmplitude = new Vector3(2f, 2f, 5f);
/// <summary>
/// 方向影响设置。
/// </summary>
[Title("Direction")]
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
/// <summary>
/// 距离衰减。
/// </summary>
[Title("Distance Attenuation")]
[LabelText("Use Attenuation")]
public bool useAttenuation;
[ShowIf("useAttenuation")]
[LabelText("Attenuation Range")]
public float attenuationRange = 50f;
[ShowIf("useAttenuation")]
[LabelText("Attenuation Curve")]
public AnimationCurve attenuationCurve = new AnimationCurve(
new Keyframe(0f, 1f),
new Keyframe(1f, 0f)
);
public override void OnStart(FeedbackContext context)
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(rotationAmplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
MMCinemachineRotationShakeEvent.Trigger(
null,
shakeCurve,
context.duration,
finalAmplitude,
0f, 1f, false,
intensityMultiplier
);
}
public override void OnInterrupt(FeedbackContext context)
{
MMCinemachineRotationShakeEvent.Trigger(
null, shakeCurve, 0f, Vector3.zero,
0f, 1f, false,
stop: true
);
}
/// <summary>
/// 计算距离衰减系数。
/// </summary>
private float ComputeAttenuation(FeedbackContext context)
{
if (!useAttenuation || context.owner == null) return 1f;
Camera mainCamera = Camera.main;
if (mainCamera == null) return 1f;
float distance = Vector3.Distance(context.owner.position, mainCamera.transform.position);
float normalizedDistance = Mathf.Clamp01(distance / attenuationRange);
return attenuationCurve.Evaluate(normalizedDistance);
}
}
}

View File

@@ -1,126 +0,0 @@
using System;
using Cielonos;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 高级色散反馈动作,通过 PostProcessingManager 驱动 AdvancedChromaticAberration Volume 参数。
/// </summary>
[Serializable]
public class ChromaticAberrationAction : CurveShakeAction
{
public override string DisplayName => "Chromatic Aberration";
/// <summary>
/// 是否同时修改中心点。
/// </summary>
[Title("Chromatic Aberration Settings")]
[LabelText("Modify Center")]
public bool modifyCenter;
[ShowIf("modifyCenter")]
[LabelText("Center")]
public Vector2 center = new Vector2(0.5f, 0.5f);
/// <summary>
/// 是否同时修改抖动强度。
/// </summary>
[LabelText("Modify Jitter")]
public bool modifyJitter;
[ShowIf("modifyJitter")]
[LabelText("Jitter Curve")]
public AnimationCurve jitterCurve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
new Keyframe(1f, 0f)
);
[ShowIf("modifyJitter")]
[LabelText("Jitter Remap Min")]
public float jitterRemapMin;
[ShowIf("modifyJitter")]
[LabelText("Jitter Remap Max")]
public float jitterRemapMax = 0.5f;
[NonSerialized] private AdvancedChromaticAberration _aca;
[NonSerialized] private float _initialIntensity;
[NonSerialized] private Vector2 _initialCenter;
[NonSerialized] private float _initialJitter;
[NonSerialized] private bool _resolved;
public override void OnStart(FeedbackContext context)
{
_resolved = TryResolveComponent();
if (!_resolved) return;
_initialIntensity = _aca.intensity.value;
_initialCenter = _aca.center.value;
_initialJitter = _aca.jitterIntensity.value;
if (modifyCenter)
{
_aca.center.value = center;
}
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
if (!_resolved) return;
float newIntensity = EvaluateShake(normalizedTime, _initialIntensity);
_aca.intensity.value = newIntensity;
if (modifyJitter)
{
float jitterValue = jitterCurve.Evaluate(normalizedTime);
float mappedJitter = Mathf.LerpUnclamped(jitterRemapMin, jitterRemapMax, jitterValue);
_aca.jitterIntensity.value = relativeToInitial ? _initialJitter + mappedJitter : mappedJitter;
}
}
public override void OnEnd(FeedbackContext context)
{
RestoreValues();
}
public override void OnInterrupt(FeedbackContext context)
{
RestoreValues();
}
private bool TryResolveComponent()
{
if (_aca != null) return true;
if (PostProcessingManager.Instance == null)
{
Debug.LogWarning("[ChromaticAberrationAction] PostProcessingManager instance not found.");
return false;
}
if (!PostProcessingManager.Instance.GetVolumeComponent(out _aca))
{
Debug.LogWarning("[ChromaticAberrationAction] AdvancedChromaticAberration not found in Volume Profile.");
return false;
}
return true;
}
private void RestoreValues()
{
if (!_resolved) return;
_aca.intensity.value = _initialIntensity;
_aca.center.value = _initialCenter;
_aca.jitterIntensity.value = _initialJitter;
_resolved = false;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 5b5dc09ccaea7ab41847c4a59492bc44
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -51,7 +51,7 @@ namespace Cielonos.MainGame.Effects.Feedback
if (affectedByCameraDirection)
{
Camera mainCamera = Camera.main;
Camera mainCamera = MainGameManager.Instance.player.viewSc.playerCamera;
if (mainCamera != null)
{
return mainCamera.transform.TransformDirection(localAmplitude);

View File

@@ -0,0 +1,42 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 摄像机视野角(FOV)反馈动作,通过 CameraFovShakeEvent 触发 CameraFovShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.2f, 0.8f, 0.9f)]
public class CameraFieldOfViewAction : CinemachineActionBase
{
public override string DisplayName => "Camera Field of View";
[TitleGroup("FOV设置")]
[LabelText("FOV曲线")]
public FloatCurveChannel fovCurve = FloatCurveChannel.CreateDefault(remapMax: 10f);
protected override void TriggerEvent(FeedbackContext context)
{
CameraFovShakeEvent.Trigger(context, fovCurve);
}
protected override void StopEvent(FeedbackContext context)
{
CameraFovShakeEvent.Trigger(context, fovCurve, true);
}
public override bool Validate(out string error)
{
if (!fovCurve.active)
{
error = "FOV curve is not enabled.";
return false;
}
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 6b01df3a292fc2748ab56454e289de8f

View File

@@ -0,0 +1,78 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 摄像机位移震动反馈,通过 CameraPositionShakeEvent 触发 CinemachinePositionShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.4f, 0.8f, 0.4f)]
public class CameraPositionShakeAction : CinemachineActionBase
{
public override string DisplayName => "Camera Position Shake";
[TitleGroup("位移震动设置")]
[LabelText("震动曲线")]
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault();
[TitleGroup("位移震动设置")]
[LabelText("振幅")]
public Vector3 amplitude = new Vector3(0.5f, 0.5f, 0f);
[TitleGroup("方向设置")]
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
[TitleGroup("距离衰减")]
[LabelText("启用衰减")]
public bool useAttenuation;
[ShowIf("useAttenuation")]
[LabelText("衰减范围")]
public float attenuationRange = 50f;
[ShowIf("useAttenuation")]
[LabelText("衰减曲线")]
public AnimationCurve attenuationCurve = new AnimationCurve(
new Keyframe(0f, 1f),
new Keyframe(1f, 0f)
);
protected override void TriggerEvent(FeedbackContext context)
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(amplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
CameraRotationShakeEvent.Trigger(context, intensityCurve, finalAmplitude * intensityMultiplier);
}
protected override void StopEvent(FeedbackContext context)
{
CameraPositionShakeEvent.Trigger(context, intensityCurve, Vector3.zero, true);
}
private float ComputeAttenuation(FeedbackContext context)
{
if (!useAttenuation || context.owner == null) return 1f;
Camera mainCamera = Camera.main;
if (mainCamera == null) return 1f;
float distance = Vector3.Distance(context.owner.position, mainCamera.transform.position);
float normalizedDistance = Mathf.Clamp01(distance / attenuationRange);
return attenuationCurve.Evaluate(normalizedDistance);
}
public override bool Validate(out string error)
{
if (!intensityCurve.active)
{
error = "Intensity curve is not enabled.";
return false;
}
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,70 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 摄像机旋转震动反馈,通过 CameraRotationShakeEvent 触发 CinemachineRotationShaker。
/// X/Y 作用于 FollowTarget 旋转Z 作用于 Dutch 倾斜。
/// </summary>
[Serializable]
[FeedbackActionColor(0.3f, 0.7f, 0.3f)]
public class CameraRotationShakeAction : CinemachineActionBase
{
public override string DisplayName => "Camera Rotation Shake";
public Vector3 amplitude;
[LabelText("X轴曲线")]
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 5f);
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
[TitleGroup("距离衰减")]
[LabelText("启用衰减")]
public bool useAttenuation;
[ShowIf("useAttenuation")]
[LabelText("衰减范围")]
public float attenuationRange = 50f;
[ShowIf("useAttenuation")]
[LabelText("衰减曲线")]
public AnimationCurve attenuationCurve = new AnimationCurve(
new Keyframe(0f, 1f),
new Keyframe(1f, 0f)
);
protected override void TriggerEvent(FeedbackContext context)
{
Vector3 finalAmplitude = directionSettings.TransformAmplitude(amplitude, context.owner);
float intensityMultiplier = ComputeAttenuation(context);
CameraRotationShakeEvent.Trigger(context, intensityCurve, finalAmplitude * intensityMultiplier);
}
protected override void StopEvent(FeedbackContext context)
{
CameraRotationShakeEvent.Trigger(context, intensityCurve, Vector3.zero, true);
}
private float ComputeAttenuation(FeedbackContext context)
{
if (!useAttenuation || context.owner == null) return 1f;
Camera mainCamera = Camera.main;
if (mainCamera == null) return 1f;
float distance = Vector3.Distance(context.owner.position, mainCamera.transform.position);
float normalizedDistance = Mathf.Clamp01(distance / attenuationRange);
return attenuationCurve.Evaluate(normalizedDistance);
}
public override bool Validate(out string error)
{
error = null;
return true;
}
}
}

View File

@@ -1,59 +0,0 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using Unity.Cinemachine;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// Cinemachine Impulse 反馈,通过 CinemachineImpulseDefinition 直接创建脉冲事件。
/// 需要场景中 Cinemachine Camera 上有 CinemachineImpulseListener 组件。
/// </summary>
[Serializable]
public class CinemachineImpulseAction : FeedbackActionBase
{
public override string DisplayName => "Cinemachine Impulse";
/// <summary>
/// Impulse 定义,包含信号形状、衰减模式、持续时间等。
/// </summary>
[Title("Impulse Settings")]
public CinemachineImpulseDefinition impulseDefinition = new CinemachineImpulseDefinition();
/// <summary>
/// 脉冲速度向量。
/// </summary>
[LabelText("Velocity")]
public Vector3 velocity = new Vector3(5f, 5f, 5f);
/// <summary>
/// Stop 时是否清除所有 impulse。
/// </summary>
[LabelText("Clear Impulse on Stop")]
public bool clearImpulseOnStop;
/// <summary>
/// 方向影响设置。
/// </summary>
[Title("Direction")]
public CameraDirectionSettings directionSettings = new CameraDirectionSettings();
public override void OnStart(FeedbackContext context)
{
Vector3 finalVelocity = directionSettings.TransformAmplitude(velocity, context.owner);
Vector3 position = context.owner != null ? context.owner.position : Vector3.zero;
CinemachineImpulseManager.Instance.IgnoreTimeScale = true;
impulseDefinition.CreateEvent(position, finalVelocity);
}
public override void OnInterrupt(FeedbackContext context)
{
if (clearImpulseOnStop)
{
CinemachineImpulseManager.Instance.Clear();
}
}
}
}

View File

@@ -1,2 +0,0 @@
fileFormatVersion: 2
guid: 4e30410247dced6409fff042f9c8828a

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: 84b87c262bd85754b9840ceb6830c8ef
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -0,0 +1,85 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// Anime ACES 反馈动作,通过 AnimeACESShakeEvent 触发 AnimeACESShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.9f, 0.4f, 0.2f)]
public class AnimeACESAction : PostprocessingActionBase
{
public override string DisplayName => "Anime ACES Tone";
[TitleGroup("曝光度")]
[LabelText("修改曝光度")]
public bool modifyExposure;
[ShowIf("modifyExposure")]
[LabelText("曝光度曲线")]
public FloatCurveChannel exposureChannel = FloatCurveChannel.CreateDefault();
[TitleGroup("对比度")]
[LabelText("修改对比度")]
public bool modifyContrast;
[ShowIf("modifyContrast")]
[LabelText("对比度曲线")]
public FloatCurveChannel contrastChannel = FloatCurveChannel.CreateDefault();
[TitleGroup("饱和度")]
[LabelText("修改饱和度")]
public bool modifySaturation;
[ShowIf("modifySaturation")]
[LabelText("饱和度曲线")]
public FloatCurveChannel saturationChannel = FloatCurveChannel.CreateDefault();
[TitleGroup("色相")]
[LabelText("修改色相")]
public bool modifyHue;
[ShowIf("modifyHue")]
[LabelText("色相曲线")]
public FloatCurveChannel hueChannel = FloatCurveChannel.CreateDefault();
[TitleGroup("颜色滤镜")]
[LabelText("修改颜色滤镜")]
public bool modifyColorFilter;
[ShowIf("modifyColorFilter")]
[LabelText("颜色滤镜渐变")]
public ColorCurveChannel colorFilterChannel = ColorCurveChannel.CreateDefault();
protected override void TriggerEvent(FeedbackContext context)
{
AnimeACESShakeEvent.Trigger(
context,
exposureChannel,
contrastChannel,
saturationChannel,
hueChannel,
colorFilterChannel
);
}
protected override void StopEvent(FeedbackContext context)
{
AnimeACESShakeEvent.Trigger(context, stop: true);
}
public override bool Validate(out string error)
{
if (!modifyExposure && !modifyContrast && !modifySaturation && !modifyHue && !modifyColorFilter)
{
error = "No channel is enabled. Enable at least one channel.";
return false;
}
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 81ca349cbc0a67e40bb42b89d4702a3c

View File

@@ -0,0 +1,10 @@
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
public class BloomAction : CurveShakeAction
{
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: f30cb59f5ebeee44e99100865ced4e94

View File

@@ -0,0 +1,56 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 高级色散反馈动作,通过 ChromaticAberrationShakeEvent 触发 ChromaticAberrationShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.8f, 0.4f, 0.8f)]
public class ChromaticAberrationAction : PostprocessingActionBase
{
public override string DisplayName => "Chromatic Aberration";
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
/// <summary>
/// 是否同时修改中心点。
/// </summary>
[LabelText("修改中心点")]
public bool modifyCenter;
[ShowIf("modifyCenter")]
[LabelText("中心点曲线")]
public Vector2CurveChannel centerCurve = Vector2CurveChannel.CreateDefault();
/// <summary>
/// 是否同时修改抖动强度。
/// </summary>
[LabelText("修改抖动")]
public bool modifyJitter;
[ShowIf("modifyJitter")]
[LabelText("抖动曲线")]
public FloatCurveChannel jitterCurve = FloatCurveChannel.CreateDefault(remapMax: 0.5f);
protected override void TriggerEvent(FeedbackContext context)
{
ChromaticAberrationShakeEvent.Trigger(
context,
intensityCurve,
modifyCenter,
centerCurve,
modifyJitter,
jitterCurve
);
}
protected override void StopEvent(FeedbackContext context)
{
ChromaticAberrationShakeEvent.Trigger(context, intensityCurve, stop: true);
}
}
}

View File

@@ -0,0 +1,48 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 径向模糊反馈动作,通过 RadialBlurShakeEvent 触发 RadialBlurShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.6f, 0.4f, 0.9f)]
public class RadialBlurAction : PostprocessingActionBase
{
public override string DisplayName => "Radial Blur";
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
/// <summary>
/// 是否修改模糊中心点。关闭时保持 Volume 当前设置。
/// </summary>
[LabelText("修改中心点")]
public bool modifyCenter;
/// <summary>
/// 模糊中心的屏幕坐标 (0-1)。(0.5, 0.5) 为屏幕正中心。
/// </summary>
[ShowIf("modifyCenter")]
[LabelText("中心点")]
public Vector2 center = new Vector2(0.5f, 0.5f);
protected override void TriggerEvent(FeedbackContext context)
{
RadialBlurShakeEvent.Trigger(context, intensityCurve, modifyCenter, center);
}
protected override void StopEvent(FeedbackContext context)
{
RadialBlurShakeEvent.Trigger(context, default, stop: true);
}
public override bool Validate(out string error)
{
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,59 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 黑白闪反馈动作,通过 StrobeFlashShakeEvent 触发 StrobeFlashShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(1.0f, 0.9f, 0.3f)]
public class StrobeFlashAction : PostprocessingActionBase
{
public override string DisplayName => "Strobe Flash";
/// <summary>
/// 是否修改频率和颜色参数。
/// </summary>
[TitleGroup("闪烁设置")]
[LabelText("修改额外参数")]
public bool modifyExtra;
[ShowIf("modifyExtra")]
[LabelText("频率曲线")]
public FloatCurveChannel frequencyCurve = FloatCurveChannel.CreateDefault(remapMax: 15f);
[ShowIf("modifyExtra")]
[LabelText("高颜色")]
public ColorCurveChannel colorHigh = ColorCurveChannel.CreateDefault();
[ShowIf("modifyExtra")]
[LabelText("低颜色")]
public ColorCurveChannel colorLow = ColorCurveChannel.CreateDefault();
protected override void TriggerEvent(FeedbackContext context)
{
StrobeFlashShakeEvent.Trigger(
context,
context.duration,
modifyExtra,
frequencyCurve,
colorHigh,
colorLow
);
}
protected override void StopEvent(FeedbackContext context)
{
StrobeFlashShakeEvent.Trigger(context, 0f, stop: true);
}
public override bool Validate(out string error)
{
error = null;
return true;
}
}
}

View File

@@ -0,0 +1,115 @@
using System;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 高级暗角反馈动作,通过 VignetteShakeEvent 触发 VignetteShaker。
/// </summary>
[Serializable]
[FeedbackActionColor(0.9f, 0.5f, 0.3f)]
public class VignetteAction : PostprocessingActionBase
{
public override string DisplayName => "Vignette";
public FloatCurveChannel intensityCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
/// <summary>
/// 是否修改暗角中心点。
/// </summary>
[LabelText("修改中心点")]
public bool modifyCenter;
/// <summary>
/// 模糊中心的屏幕坐标 (0-1)。(0.5, 0.5) 为屏幕正中心。
/// </summary>
[HideIf("modifyCenter")]
[LabelText("中心点")]
public Vector2 center = new Vector2(0.5f, 0.5f);
[ShowIf("modifyCenter")]
public Vector2CurveChannel centerCurve = Vector2CurveChannel.CreateDefault();
/// <summary>
/// 是否修改颜色。
/// </summary>
[LabelText("修改颜色")]
public bool modifyColors;
[HideIf("modifyColors")]
public Color outColor;
[HideIf("modifyColors")]
public Color innerColor;
/// <summary>
/// 外圈颜色。
/// </summary>
[ShowIf("modifyColors")]
[LabelText("外圈颜色")]
public ColorCurveChannel outerColorCurve = ColorCurveChannel.CreateDefault();
/// <summary>
/// 内圈颜色。
/// </summary>
[ShowIf("modifyColors")]
[LabelText("内圈颜色")]
public ColorCurveChannel innerColorCurve = ColorCurveChannel.CreateDefault();
/// <summary>
/// 是否修改形状。
/// </summary>
[LabelText("修改形状")]
public bool modifyShape;
[HideIf("modifyShape")]
public float smoothness;
[HideIf("modifyShape")]
public float roundness;
/// <summary>
/// 柔和度曲线。
/// </summary>
[ShowIf("modifyShape")]
[LabelText("柔和度曲线")]
public FloatCurveChannel smoothnessCurve = FloatCurveChannel.CreateDefault(remapMax: 0.5f);
/// <summary>
/// 圆度曲线。
/// </summary>
[ShowIf("modifyShape")]
[LabelText("圆度曲线")]
public FloatCurveChannel roundnessCurve = FloatCurveChannel.CreateDefault(remapMax: 1f);
protected override void TriggerEvent(FeedbackContext context)
{
VignetteShakeEvent.Trigger(
context,
intensityCurve,
modifyCenter,
center,
modifyColors,
outerColorCurve,
innerColorCurve,
modifyShape,
smoothnessCurve,
roundnessCurve
);
}
protected override void StopEvent(FeedbackContext context)
{
VignetteShakeEvent.Trigger(context, intensityCurve, stop: true);
}
public override bool Validate(out string error)
{
error = null;
return true;
}
}
}

View File

@@ -1,123 +0,0 @@
using System;
using Cielonos;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 径向模糊反馈动作,通过 PostProcessingManager 驱动 RadialBlur Volume 参数。
/// 继承 CurveShakeAction 获得曲线采样和初始值管理能力。
/// </summary>
[Serializable]
public class RadialBlurAction : CurveShakeAction
{
public override string DisplayName => "Radial Blur";
/// <summary>
/// 是否修改模糊中心点。关闭时保持 Volume 当前设置(通常为 0.5, 0.5)。
/// </summary>
[Title("Radial Blur Settings")]
[LabelText("Modify Center")]
public bool modifyCenter;
/// <summary>
/// 模糊中心的屏幕坐标 (0-1)。(0.5, 0.5) 为屏幕正中心。
/// </summary>
[ShowIf("modifyCenter")]
[LabelText("Center")]
public Vector2 center = new Vector2(0.5f, 0.5f);
// 运行时缓存
[NonSerialized] private RadialBlur _radialBlur;
[NonSerialized] private float _initialBlurRadius;
[NonSerialized] private float _initialCenterX;
[NonSerialized] private float _initialCenterY;
[NonSerialized] private bool _resolved;
public override void OnStart(FeedbackContext context)
{
_resolved = TryResolveComponent();
if (!_resolved) return;
// 记录初始值用于复位
_initialBlurRadius = _radialBlur.blurRadius.value;
_initialCenterX = _radialBlur.radialCenterX.value;
_initialCenterY = _radialBlur.radialCenterY.value;
// 设置中心点(整个 Clip 期间保持不变)
if (modifyCenter)
{
_radialBlur.radialCenterX.value = center.x;
_radialBlur.radialCenterY.value = center.y;
}
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
if (!_resolved) return;
float newRadius = EvaluateShake(normalizedTime, _initialBlurRadius);
_radialBlur.blurRadius.value = newRadius;
}
public override void OnEnd(FeedbackContext context)
{
RestoreValues();
}
public override void OnInterrupt(FeedbackContext context)
{
RestoreValues();
}
public override bool Validate(out string error)
{
if (PostProcessingManager.Instance == null)
{
error = "PostProcessingManager instance not found in scene.";
return false;
}
error = null;
return true;
}
/// <summary>
/// 尝试从 PostProcessingManager 获取 RadialBlur Volume 组件。
/// </summary>
private bool TryResolveComponent()
{
if (_radialBlur != null) return true;
if (PostProcessingManager.Instance == null)
{
Debug.LogWarning("[RadialBlurAction] PostProcessingManager instance not found.");
return false;
}
if (!PostProcessingManager.Instance.GetVolumeComponent(out _radialBlur))
{
Debug.LogWarning("[RadialBlurAction] RadialBlur component not found in Volume Profile.");
return false;
}
return true;
}
/// <summary>
/// 恢复到 OnStart 时记录的初始值。
/// </summary>
private void RestoreValues()
{
if (!_resolved) return;
_radialBlur.blurRadius.value = _initialBlurRadius;
_radialBlur.radialCenterX.value = _initialCenterX;
_radialBlur.radialCenterY.value = _initialCenterY;
_resolved = false;
}
}
}

View File

@@ -1,115 +0,0 @@
using System;
using Cielonos;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 黑白闪反馈动作,在 Clip 持续时间内开启 StrobeFlash 的 AutoFlash
/// Clip 结束或被打断时自动关闭。
/// </summary>
[Serializable]
public class StrobeFlashAction : FeedbackActionBase
{
public override string DisplayName => "Strobe Flash";
/// <summary>
/// 是否修改频率和颜色参数。
/// </summary>
[Title("Strobe Settings")]
[LabelText("Modify Extra")]
public bool modifyExtra;
[ShowIf("modifyExtra")]
[LabelText("Frequency")]
public float frequency = 15f;
[ShowIf("modifyExtra")]
[LabelText("Color High")]
public Color colorHigh = Color.white;
[ShowIf("modifyExtra")]
[LabelText("Color Low")]
public Color colorLow = Color.black;
[NonSerialized] private StrobeFlash _strobeFlash;
[NonSerialized] private bool _initialEnable;
[NonSerialized] private bool _initialAutoFlash;
[NonSerialized] private float _initialFrequency;
[NonSerialized] private Color _initialColorHigh;
[NonSerialized] private Color _initialColorLow;
[NonSerialized] private bool _resolved;
public override void OnStart(FeedbackContext context)
{
_resolved = TryResolveComponent();
if (!_resolved) return;
_initialEnable = _strobeFlash.enableEffect.value;
_initialAutoFlash = _strobeFlash.autoFlash.value;
_initialFrequency = _strobeFlash.frequency.value;
_initialColorHigh = _strobeFlash.colorHigh.value;
_initialColorLow = _strobeFlash.colorLow.value;
_strobeFlash.enableEffect.value = true;
_strobeFlash.autoFlash.value = true;
if (modifyExtra)
{
_strobeFlash.frequency.value = frequency;
_strobeFlash.colorHigh.value = colorHigh;
_strobeFlash.colorLow.value = colorLow;
}
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
// StrobeFlash 由 Shader 内部的 _Time 驱动自动闪烁,
// Action 只负责开关控制,不需要每帧更新。
}
public override void OnEnd(FeedbackContext context)
{
RestoreValues();
}
public override void OnInterrupt(FeedbackContext context)
{
RestoreValues();
}
private bool TryResolveComponent()
{
if (_strobeFlash != null) return true;
if (PostProcessingManager.Instance == null)
{
Debug.LogWarning("[StrobeFlashAction] PostProcessingManager instance not found.");
return false;
}
if (!PostProcessingManager.Instance.GetVolumeComponent(out _strobeFlash))
{
Debug.LogWarning("[StrobeFlashAction] StrobeFlash not found in Volume Profile.");
return false;
}
return true;
}
private void RestoreValues()
{
if (!_resolved) return;
_strobeFlash.enableEffect.value = _initialEnable;
_strobeFlash.autoFlash.value = _initialAutoFlash;
_strobeFlash.frequency.value = _initialFrequency;
_strobeFlash.colorHigh.value = _initialColorHigh;
_strobeFlash.colorLow.value = _initialColorLow;
_resolved = false;
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: e33332fa07bdfcd459b3bf3350c11299
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -1,5 +1,4 @@
using System;
using Cielonos.MainGame;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using UnityEngine;
@@ -31,12 +30,14 @@ namespace Cielonos.MainGame.Effects.Feedback
/// <summary>
/// 是否激活此通道。
/// </summary>
[HorizontalGroup("Channel")]
public bool active;
/// <summary>
/// 通道工作模式。
/// </summary>
[ShowIf("active")]
[HorizontalGroup("Channel")]
public TimeScaleMode mode = TimeScaleMode.Fixed;
/// <summary>
@@ -51,6 +52,7 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Curve")]
[ShakeCurvePreset]
public AnimationCurve curve = new AnimationCurve(
new Keyframe(0f, 0f),
new Keyframe(0.5f, 1f),
@@ -62,6 +64,7 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Remap Zero")]
[HorizontalGroup("Ramp")]
public float remapZero;
/// <summary>
@@ -69,11 +72,25 @@ namespace Cielonos.MainGame.Effects.Feedback
/// </summary>
[ShowIf("@active && mode == TimeScaleMode.Dynamic")]
[LabelText("Remap One")]
[HorizontalGroup("Ramp")]
public float remapOne = 1f;
/// <summary>
/// 根据归一化进度计算当前通道的时间缩放值
/// 将此通道的配置转换为事件传输用的 TimeScaleChannelData
/// </summary>
public TimeScaleChannelData ToChannelData()
{
return new TimeScaleChannelData
{
active = active,
mode = mode,
fixedValue = fixedValue,
curve = curve,
remapZero = remapZero,
remapOne = remapOne
};
}
public float Evaluate(float normalizedTime)
{
if (!active) return 1f;
@@ -83,94 +100,114 @@ namespace Cielonos.MainGame.Effects.Feedback
return fixedValue;
}
float curveValue = curve.Evaluate(normalizedTime);
float curveValue = curve?.Evaluate(normalizedTime) ?? 0f;
return Mathf.LerpUnclamped(remapZero, remapOne, curveValue);
}
}
/// <summary>
/// 时间缩放修改器反馈,直接驱动 TimeManager 的各个通道
/// 时间缩放修改器反馈,通过 TimeScaleShakeEvent 触发 TimeScaleShaker
/// Shaker 负责管理多个并发时间缩放实例的叠加混合和初始值恢复。
///
/// 重要:此 Action 只应使用游戏的 unscaledDeltaTime 驱动。
/// 重要:此 Action 会忽略时间缩放,使用未缩放的 deltaTime 驱动。
/// 当 Time.timeScale == 0 时,此 Action 也会暂停。
/// 不要在包含此 Action 的 Clip 上启用自定义 overrideTimeSettings
/// FeedbackData 的 defaultTimeSettings.useTimeScale 也应保持为 false。
/// 我们的自定义时间参数绝不能影响时间缩放修改器本身。
/// </summary>
[Serializable]
[FeedbackActionColor(0.3f, 0.7f, 1.0f)]
public class TimeScaleModifierAction : FeedbackActionBase
{
public override string DisplayName => "Time Scale Modifier";
/// <summary>
/// 忽略时间缩放,使用未缩放的 deltaTime。
/// </summary>
public override bool IgnoreTimeScale => true;
public TimeScaleChannel globalChannel = new TimeScaleChannel { active = true, fixedValue = 0.1f };
[Title("Global Time Scale")]
public TimeScaleChannel globalChannel = new TimeScaleChannel { active = true, fixedValue = 0f };
[Title("Player Time Scale")]
public bool advancedSettings = false;
[ShowIf("advancedSettings")]
public TimeScaleChannel playerChannel = new TimeScaleChannel();
[Title("Enemy Time Scale")]
[ShowIf("advancedSettings")]
public TimeScaleChannel enemyChannel = new TimeScaleChannel();
[Title("Allied Time Scale")]
[ShowIf("advancedSettings")]
public TimeScaleChannel alliedChannel = new TimeScaleChannel();
[Title("Non-Player Time Scale")]
[ShowIf("advancedSettings")]
public TimeScaleChannel nonPlayerChannel = new TimeScaleChannel();
[NonSerialized] private float _initialGlobal;
[NonSerialized] private float _initialPlayer;
[NonSerialized] private float _initialEnemy;
[NonSerialized] private float _initialAllied;
[NonSerialized] private float _initialNonPlayer;
public override void OnStart(FeedbackContext context)
{
if (TimeManager.Instance == null)
// 通过事件触发让TimeScaleShaker注册这个实例
TimeScaleShakeEvent.Trigger(
duration: context.duration,
global: globalChannel.ToChannelData(),
player: playerChannel.ToChannelData(),
enemy: enemyChannel.ToChannelData(),
allied: alliedChannel.ToChannelData(),
nonPlayer: nonPlayerChannel.ToChannelData()
);
// 立即执行一次TimeScaleShaker的更新
// 这样在同一帧内TimeScaleModifierAction修改的globalTimeScale就能立即生效
ImmediateApplyTimeScale();
}
/// <summary>
/// 立即应用时间缩放,确保在同一帧内立即生效
/// </summary>
private void ImmediateApplyTimeScale()
{
if (TimeManager.Instance == null) return;
if (globalChannel.active)
{
Debug.LogWarning("[TimeScaleModifierAction] TimeManager instance not found.");
return;
TimeManager.Instance.globalTimeScale.Value = globalChannel.Evaluate(0);
}
if (playerChannel.active)
{
TimeManager.Instance.playerTimeScale.Value = playerChannel.Evaluate(0);
}
if (enemyChannel.active)
{
TimeManager.Instance.enemyTimeScale.Value = enemyChannel.Evaluate(0);
}
if (alliedChannel.active)
{
TimeManager.Instance.alliedMinionTimeScale.Value = alliedChannel.Evaluate(0);
}
if (nonPlayerChannel.active)
{
TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerChannel.Evaluate(0);
}
_initialGlobal = TimeManager.Instance.globalTimeScale.Value;
_initialPlayer = TimeManager.Instance.playerTimeScale.Value;
_initialEnemy = TimeManager.Instance.enemyTimeScale.Value;
_initialAllied = TimeManager.Instance.alliedMinionTimeScale.Value;
_initialNonPlayer = TimeManager.Instance.nonPlayerTimeScale.Value;
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
if (TimeManager.Instance == null) return;
if (globalChannel.active)
TimeManager.Instance.globalTimeScale.Value = globalChannel.Evaluate(normalizedTime);
if (playerChannel.active)
TimeManager.Instance.playerTimeScale.Value = playerChannel.Evaluate(normalizedTime);
if (enemyChannel.active)
TimeManager.Instance.enemyTimeScale.Value = enemyChannel.Evaluate(normalizedTime);
if (alliedChannel.active)
TimeManager.Instance.alliedMinionTimeScale.Value = alliedChannel.Evaluate(normalizedTime);
if (nonPlayerChannel.active)
TimeManager.Instance.nonPlayerTimeScale.Value = nonPlayerChannel.Evaluate(normalizedTime);
// Shaker 自行每帧驱动所有活跃实例。
}
public override void OnEnd(FeedbackContext context)
{
RestoreValues();
// Shaker 自动管理实例生命周期和初始值恢复。
}
public override void OnInterrupt(FeedbackContext context)
{
RestoreValues();
TimeScaleShakeEvent.Trigger(0f, stop: true);
}
public override bool Validate(out string error)
{
// 防呆检查:时间缩放修改器不应受自定义时间缩放影响
// 此检查在 Editor 中调用,完整的 Inspector 防呆将在后续版本中添加
bool anyActive = globalChannel.active || playerChannel.active ||
enemyChannel.active || alliedChannel.active ||
nonPlayerChannel.active;
@@ -184,25 +221,5 @@ namespace Cielonos.MainGame.Effects.Feedback
error = null;
return true;
}
private void RestoreValues()
{
if (TimeManager.Instance == null) return;
if (globalChannel.active)
TimeManager.Instance.globalTimeScale.Value = _initialGlobal;
if (playerChannel.active)
TimeManager.Instance.playerTimeScale.Value = _initialPlayer;
if (enemyChannel.active)
TimeManager.Instance.enemyTimeScale.Value = _initialEnemy;
if (alliedChannel.active)
TimeManager.Instance.alliedMinionTimeScale.Value = _initialAllied;
if (nonPlayerChannel.active)
TimeManager.Instance.nonPlayerTimeScale.Value = _initialNonPlayer;
}
}
}

View File

@@ -1,147 +0,0 @@
using System;
using Cielonos;
using Sirenix.OdinInspector;
using SLSUtilities.Feedback;
using SLSUtilities.Rendering.PostProcessing;
using UnityEngine;
namespace Cielonos.MainGame.Effects.Feedback
{
/// <summary>
/// 高级暗角反馈动作,通过 PostProcessingManager 驱动 AdvancedVignette Volume 参数。
/// 可用于受击暗角、环境压抑等效果。
/// </summary>
[Serializable]
public class VignetteAction : CurveShakeAction
{
public override string DisplayName => "Vignette";
/// <summary>
/// 是否修改暗角中心点。
/// </summary>
[Title("Vignette Settings")]
[LabelText("Modify Center")]
public bool modifyCenter;
[ShowIf("modifyCenter")]
[LabelText("Center")]
public Vector2 center = new Vector2(0.5f, 0.5f);
/// <summary>
/// 是否修改颜色。
/// </summary>
[LabelText("Modify Colors")]
public bool modifyColors;
[ShowIf("modifyColors")]
[LabelText("Color Outer")]
public Color colorOuter = Color.black;
[ShowIf("modifyColors")]
[LabelText("Color Inner")]
public Color colorInner = Color.black;
/// <summary>
/// 是否修改柔和度和圆度。
/// </summary>
[LabelText("Modify Shape")]
public bool modifyShape;
[ShowIf("modifyShape")]
[LabelText("Smoothness")]
[Range(0.01f, 1f)]
public float smoothness = 0.5f;
[ShowIf("modifyShape")]
[LabelText("Roundness")]
[Range(0f, 1f)]
public float roundness = 1f;
[NonSerialized] private AdvancedVignette _vignette;
[NonSerialized] private float _initialIntensity;
[NonSerialized] private Vector2 _initialCenter;
[NonSerialized] private Color _initialColorOuter;
[NonSerialized] private Color _initialColorInner;
[NonSerialized] private float _initialSmoothness;
[NonSerialized] private float _initialRoundness;
[NonSerialized] private bool _resolved;
public override void OnStart(FeedbackContext context)
{
_resolved = TryResolveComponent();
if (!_resolved) return;
_initialIntensity = _vignette.intensity.value;
_initialCenter = _vignette.center.value;
_initialColorOuter = _vignette.colorOuter.value;
_initialColorInner = _vignette.colorInner.value;
_initialSmoothness = _vignette.smoothness.value;
_initialRoundness = _vignette.roundness.value;
if (modifyCenter)
_vignette.center.value = center;
if (modifyColors)
{
_vignette.colorOuter.value = colorOuter;
_vignette.colorInner.value = colorInner;
}
if (modifyShape)
{
_vignette.smoothness.value = smoothness;
_vignette.roundness.value = roundness;
}
}
public override void OnUpdate(FeedbackContext context, float normalizedTime)
{
if (!_resolved) return;
float newIntensity = EvaluateShake(normalizedTime, _initialIntensity);
_vignette.intensity.value = newIntensity;
}
public override void OnEnd(FeedbackContext context)
{
RestoreValues();
}
public override void OnInterrupt(FeedbackContext context)
{
RestoreValues();
}
private bool TryResolveComponent()
{
if (_vignette != null) return true;
if (PostProcessingManager.Instance == null)
{
Debug.LogWarning("[VignetteAction] PostProcessingManager instance not found.");
return false;
}
if (!PostProcessingManager.Instance.GetVolumeComponent(out _vignette))
{
Debug.LogWarning("[VignetteAction] AdvancedVignette not found in Volume Profile.");
return false;
}
return true;
}
private void RestoreValues()
{
if (!_resolved) return;
_vignette.intensity.value = _initialIntensity;
_vignette.center.value = _initialCenter;
_vignette.colorOuter.value = _initialColorOuter;
_vignette.colorInner.value = _initialColorInner;
_vignette.smoothness.value = _initialSmoothness;
_vignette.roundness.value = _initialRoundness;
_resolved = false;
}
}
}

View File

@@ -20,8 +20,7 @@ namespace Cielonos.MainGame.Effects.Feedback
/// <summary>
/// 全局时间缩放TimeManager.globalTimeScale
/// </summary>
public float GlobalTimeScale =>
TimeManager.Instance != null ? TimeManager.Instance.globalTimeScale.Value : 1f;
public float GlobalTimeScale => TimeManager.Instance != null ? TimeManager.Instance.globalTimeScale.Value : 1f;
/// <summary>
/// 分组时间缩放,根据角色 Fraction 返回对应的 TimeManager 通道值。
@@ -48,28 +47,31 @@ namespace Cielonos.MainGame.Effects.Feedback
/// <summary>
/// 角色本地时间缩放SelfTimeSubmodule.localTimeScale
/// </summary>
public float LocalTimeScale =>
_character?.selfTimeSm?.localTimeScale?.Value ?? 1f;
public float LocalTimeScale => _character?.selfTimeSm?.localTimeScale?.Value ?? 1f;
/// <summary>
/// 根据 FeedbackTimeSettings 组合各层级缩放计算实际 deltaTime。
/// 根据 FeedbackTimeSettings 计算综合时间缩放系数(不含 deltaTime
/// 返回 1.0 表示正常速度。
/// </summary>
public float GetTimeScale(FeedbackTimeSettings settings)
{
if (settings == null || settings.timeScaleType == FeedbackTimeSettings.TimeScaleType.Unscaled) return 1f;
float scale = 1f;
if (settings.timeScaleType == FeedbackTimeSettings.TimeScaleType.Global) scale *= GlobalTimeScale;
if (settings.timeScaleType == FeedbackTimeSettings.TimeScaleType.Group) scale *= GroupTimeScale;
if (settings.timeScaleType == FeedbackTimeSettings.TimeScaleType.Local) scale *= LocalTimeScale;
return scale;
}
/// <summary>
/// 根据 FeedbackTimeSettings 组合各层级缩放计算实际 deltaTime
/// </summary>
public float GetDeltaTime(FeedbackTimeSettings settings)
{
if (settings == null || !settings.useTimeScale) return Time.unscaledDeltaTime;
float dt = Time.unscaledDeltaTime;
if (settings.affectedByGlobalTimeScale)
dt *= GlobalTimeScale;
if (settings.affectedByGroupTimeScale)
dt *= GroupTimeScale;
if (settings.affectedByLocalTimeScale)
dt *= LocalTimeScale;
return dt;
return Time.unscaledDeltaTime * GetTimeScale(settings);
}
}
}

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: b533c73b5ef2bdf4a8044636a5d25fed
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: fc3cd16e5556b7a4eb66e5385e4f4e60

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: c15f7649d43e18b448c0acb1f9163f2c

View File

@@ -0,0 +1,8 @@
fileFormatVersion: 2
guid: d812b009316d71f4fbfc8abb6e76bc18
folderAsset: yes
DefaultImporter:
externalObjects: {}
userData:
assetBundleName:
assetBundleVariant:

View File

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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: dbbfef1d7cc479c47837ff154e4a7c5e

View File

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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 08a7d10a0525af448902b60235ecf4fc

View File

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

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 5c54de88d6cf4304180fbd641401b1b6

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 569f4a4691c95ba448cf7309993ca9a9

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 588e6dd88aea55748adfb8b9aa48a518

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 33bd71b84e3b907468514ad946229fbc

View File

@@ -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();
}
}
}

View File

@@ -0,0 +1,2 @@
fileFormatVersion: 2
guid: 129b8b0330a4e5741a1f3121f9d01d22