using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Sirenix.OdinInspector;
using UnityEngine;
namespace SLSUtilities.Feedback
{
///
/// 反馈播放上下文,传递给每个 FeedbackAction 的生命周期回调。
///
public struct FeedbackContext
{
///
/// 当前播放器实例。
///
public FeedbackPlayer player;
public int runtimeFeedbackId;
public int trackIndex;
public int clipIndex;
///
/// 触发者的 Transform(可选)。
///
public Transform owner;
///
/// 当前帧经过时间缩放处理后的 deltaTime。
///
public float deltaTime;
///
/// Clip 已播放时间(秒)。
///
public float elapsedTime;
///
/// Clip 总时长(秒)。
///
public float duration;
///
/// 当前 Clip 的综合时间缩放系数(含 Global/Group/Local),由 FeedbackPlayer 每帧动态计算。
///
public float timeScale;
///
/// 当前 Clip 是否动态获取当前的时间缩放
///
public FeedbackTimeSettings timeSettings;
}
///
/// 所有反馈动作的抽象基类,定义生命周期回调。
/// 使用 Odin 的序列化路径实现多态序列化(SerializedScriptableObject)。
/// Odin 会自动为此抽象类型的字段显示多态类型选择器。
///
[Serializable]
public abstract class FeedbackActionBase
{
///
/// Inspector 中显示的名称。
///
public virtual string DisplayName => GetType().Name;
///
/// 是否忽略时间缩放。如果为true,此Action将使用原始deltaTime,不受TimeScale影响。
///
public virtual bool IgnoreTimeScale => false;
///
/// 初始化,FeedbackPlayer 开始播放此 Clip 时调用。
///
public virtual void OnStart(FeedbackContext context) { }
///
/// 每帧更新,normalizedTime 为 Clip 内的归一化进度 [0,1]。
///
public virtual void OnUpdate(FeedbackContext context, float normalizedTime) { }
///
/// Clip 自然结束时调用。
///
public virtual void OnEnd(FeedbackContext context) { }
///
/// 被打断时调用,负责立即复位到初始状态。
///
public virtual void OnInterrupt(FeedbackContext context) { }
///
/// 用于验证配置是否正确(Editor 环境)。
///
public virtual bool Validate(out string error)
{
error = null;
return true;
}
///
/// 用于 Editor 预览(Runtime 也可用)。
///
public virtual void Preview() { }
public virtual FeedbackActionBase CreateRuntimeCopy()
{
return FeedbackRuntimeCloneUtility.CloneSerialized(this);
}
///
/// 根据归一化时间采样曲线并映射到实际值范围。
/// 如果 relativeToInitial 为 true,结果会叠加在 initialValue 上。
///
/// 震动曲线,X 轴为归一化时间 [0,1],Y 轴为震动强度 [0,1]。
/// 曲线值 0 对应的实际数值。
/// 曲线值 1 对应的实际数值。
/// 是否在初始值上叠加(而非替换)。
/// 归一化时间 [0,1]
/// 初始值(OnStart 时记录)
/// 映射后的最终数值
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;
}
}
internal static class FeedbackRuntimeCloneUtility
{
public static T CloneSerialized(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 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));
}
}
}