2026-04-12 02:11:15 -04:00
using System ;
2026-07-18 03:16:20 -04:00
using System.Collections ;
using System.Collections.Generic ;
using System.Reflection ;
2026-04-12 02:11:15 -04:00
using Sirenix.OdinInspector ;
using UnityEngine ;
namespace SLSUtilities.Feedback
{
/// <summary>
/// 反馈播放上下文,传递给每个 FeedbackAction 的生命周期回调。
/// </summary>
public struct FeedbackContext
{
/// <summary>
/// 当前播放器实例。
/// </summary>
public FeedbackPlayer player ;
2026-07-18 03:16:20 -04:00
public int runtimeFeedbackId ;
public int trackIndex ;
public int clipIndex ;
2026-04-12 02:11:15 -04:00
/// <summary>
/// 触发者的 Transform( 可选) 。
/// </summary>
public Transform owner ;
/// <summary>
/// 当前帧经过时间缩放处理后的 deltaTime。
/// </summary>
public float deltaTime ;
/// <summary>
/// Clip 已播放时间(秒)。
/// </summary>
public float elapsedTime ;
/// <summary>
/// Clip 总时长(秒)。
/// </summary>
public float duration ;
2026-04-18 13:57:19 -04:00
/// <summary>
/// 当前 Clip 的综合时间缩放系数(含 Global/Group/Local) , 由 FeedbackPlayer 每帧动态计算。
/// </summary>
public float timeScale ;
/// <summary>
/// 当前 Clip 是否动态获取当前的时间缩放
/// </summary>
public FeedbackTimeSettings timeSettings ;
2026-04-12 02:11:15 -04:00
}
/// <summary>
/// 所有反馈动作的抽象基类,定义生命周期回调。
/// 使用 Odin 的序列化路径实现多态序列化( SerializedScriptableObject) 。
/// Odin 会自动为此抽象类型的字段显示多态类型选择器。
/// </summary>
[Serializable]
public abstract class FeedbackActionBase
{
/// <summary>
/// Inspector 中显示的名称。
/// </summary>
public virtual string DisplayName = > GetType ( ) . Name ;
2026-04-18 13:57:19 -04:00
/// <summary>
/// 是否忽略时间缩放。如果为true, 此Action将使用原始deltaTime, 不受TimeScale影响。
/// </summary>
public virtual bool IgnoreTimeScale = > false ;
2026-04-12 02:11:15 -04:00
/// <summary>
/// 初始化, FeedbackPlayer 开始播放此 Clip 时调用。
/// </summary>
public virtual void OnStart ( FeedbackContext context ) { }
/// <summary>
/// 每帧更新, normalizedTime 为 Clip 内的归一化进度 [0,1]。
/// </summary>
public virtual void OnUpdate ( FeedbackContext context , float normalizedTime ) { }
/// <summary>
/// Clip 自然结束时调用。
/// </summary>
public virtual void OnEnd ( FeedbackContext context ) { }
/// <summary>
/// 被打断时调用,负责立即复位到初始状态。
/// </summary>
public virtual void OnInterrupt ( FeedbackContext context ) { }
/// <summary>
/// 用于验证配置是否正确( Editor 环境)。
/// </summary>
public virtual bool Validate ( out string error )
{
error = null ;
return true ;
}
/// <summary>
/// 用于 Editor 预览( Runtime 也可用)。
/// </summary>
public virtual void Preview ( ) { }
2026-07-18 03:16:20 -04:00
public virtual FeedbackActionBase CreateRuntimeCopy ( )
{
return FeedbackRuntimeCloneUtility . CloneSerialized ( this ) ;
}
2026-04-18 13:57:19 -04:00
/// <summary>
/// 根据归一化时间采样曲线并映射到实际值范围。
/// 如果 relativeToInitial 为 true, 结果会叠加在 initialValue 上。
/// </summary>
/// <param name="shakeCurve">震动曲线, X 轴为归一化时间 [0,1], Y 轴为震动强度 [0,1]。</param>
/// <param name="remapMin">曲线值 0 对应的实际数值。</param>
/// <param name="remapMax">曲线值 1 对应的实际数值。</param>
/// <param name="relativeToInitial">是否在初始值上叠加(而非替换)。</param>
/// <param name="normalizedTime">归一化时间 [0,1]</param>
/// <param name="initialValue">初始值( OnStart 时记录)</param>
/// <returns>映射后的最终数值</returns>
protected virtual float EvaluateShake ( AnimationCurve shakeCurve , float remapMin , float remapMax , bool relativeToInitial ,
float normalizedTime , float initialValue )
{
float curveValue = shakeCurve . Evaluate ( normalizedTime ) ;
float remappedValue = Mathf . LerpUnclamped ( remapMin , remapMax , curveValue ) ;
if ( relativeToInitial )
{
return initialValue + remappedValue ;
}
return remappedValue ;
}
2026-04-12 02:11:15 -04:00
}
2026-07-18 03:16:20 -04:00
internal static class FeedbackRuntimeCloneUtility
{
public static T CloneSerialized < T > ( T source )
{
return ( T ) CloneValue ( source ) ;
}
private static object CloneValue ( object source )
{
if ( source = = null ) return null ;
Type type = source . GetType ( ) ;
if ( IsDirectReference ( type ) )
{
return source ;
}
if ( source is AnimationCurve animationCurve )
{
return CloneAnimationCurve ( animationCurve ) ;
}
if ( source is Gradient gradient )
{
return CloneGradient ( gradient ) ;
}
if ( source is UnityEngine . Object )
{
return source ;
}
if ( type . IsArray )
{
return CloneArray ( ( Array ) source , type . GetElementType ( ) ) ;
}
if ( source is IList list )
{
return CloneList ( list , type ) ;
}
if ( type . IsValueType )
{
object boxed = source ;
CopySerializedFields ( source , boxed , type ) ;
return boxed ;
}
object clone ;
try
{
clone = Activator . CreateInstance ( type ) ;
}
catch ( Exception exception )
{
Debug . LogWarning ( $"[FeedbackRuntimeCloneUtility] Failed to clone '{type.Name}'. Runtime playback will reuse the original reference. {exception.Message}" ) ;
return source ;
}
CopySerializedFields ( source , clone , type ) ;
return clone ;
}
private static bool IsDirectReference ( Type type )
{
return type . IsPrimitive | |
type . IsEnum | |
type . Namespace ! = null & & type . Namespace . StartsWith ( "AK.Wwise" , StringComparison . Ordinal ) | |
type = = typeof ( string ) | |
type = = typeof ( decimal ) | |
type = = typeof ( Vector2 ) | |
type = = typeof ( Vector3 ) | |
type = = typeof ( Vector4 ) | |
type = = typeof ( Quaternion ) | |
type = = typeof ( Color ) | |
type = = typeof ( Color32 ) | |
type = = typeof ( Rect ) | |
type = = typeof ( Bounds ) | |
type = = typeof ( LayerMask ) ;
}
private static AnimationCurve CloneAnimationCurve ( AnimationCurve source )
{
AnimationCurve clone = new AnimationCurve ( source . keys )
{
preWrapMode = source . preWrapMode ,
postWrapMode = source . postWrapMode
} ;
return clone ;
}
private static Gradient CloneGradient ( Gradient source )
{
Gradient clone = new Gradient
{
mode = source . mode
} ;
clone . SetKeys ( source . colorKeys , source . alphaKeys ) ;
return clone ;
}
private static Array CloneArray ( Array source , Type elementType )
{
Array clone = Array . CreateInstance ( elementType , source . Length ) ;
for ( int i = 0 ; i < source . Length ; i + + )
{
clone . SetValue ( CloneValue ( source . GetValue ( i ) ) , i ) ;
}
return clone ;
}
private static object CloneList ( IList source , Type listType )
{
Type elementType = typeof ( object ) ;
if ( listType . IsGenericType )
{
elementType = listType . GetGenericArguments ( ) [ 0 ] ;
}
IList clone = CreateListInstance ( listType , elementType ) ;
foreach ( object value in source )
{
clone . Add ( CloneValue ( value ) ) ;
}
return clone ;
}
private static IList CreateListInstance ( Type listType , Type elementType )
{
if ( ! listType . IsInterface & & ! listType . IsAbstract )
{
try
{
return ( IList ) Activator . CreateInstance ( listType ) ;
}
catch
{
// Fall back to List<T> below.
}
}
Type concreteType = typeof ( List < > ) . MakeGenericType ( elementType ) ;
return ( IList ) Activator . CreateInstance ( concreteType ) ;
}
private static void CopySerializedFields ( object source , object target , Type type )
{
while ( type ! = null & & type ! = typeof ( object ) )
{
FieldInfo [ ] fields = type . GetFields ( BindingFlags . Instance | BindingFlags . Public | BindingFlags . NonPublic | BindingFlags . DeclaredOnly ) ;
for ( int i = 0 ; i < fields . Length ; i + + )
{
FieldInfo field = fields [ i ] ;
if ( ! ShouldCopyField ( field ) ) continue ;
object value = field . GetValue ( source ) ;
field . SetValue ( target , CloneValue ( value ) ) ;
}
type = type . BaseType ;
}
}
private static bool ShouldCopyField ( FieldInfo field )
{
if ( field . IsStatic ) return false ;
if ( field . IsInitOnly ) return false ;
if ( field . IsNotSerialized ) return false ;
return field . IsPublic | |
Attribute . IsDefined ( field , typeof ( SerializeField ) ) | |
Attribute . IsDefined ( field , typeof ( SerializeReference ) ) ;
}
}
2026-04-12 02:11:15 -04:00
}