Files
Cielonos/Assets/Scripts/SLSUtilities/Feedback/Base/FeedbackActionBase.cs
SoulliesOfficial 39b43680a9 爆更
2026-07-18 03:16:20 -04:00

319 lines
10 KiB
C#
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
using System;
using System.Collections;
using System.Collections.Generic;
using System.Reflection;
using Sirenix.OdinInspector;
using UnityEngine;
namespace SLSUtilities.Feedback
{
/// <summary>
/// 反馈播放上下文,传递给每个 FeedbackAction 的生命周期回调。
/// </summary>
public struct FeedbackContext
{
/// <summary>
/// 当前播放器实例。
/// </summary>
public FeedbackPlayer player;
public int runtimeFeedbackId;
public int trackIndex;
public int clipIndex;
/// <summary>
/// 触发者的 Transform可选
/// </summary>
public Transform owner;
/// <summary>
/// 当前帧经过时间缩放处理后的 deltaTime。
/// </summary>
public float deltaTime;
/// <summary>
/// Clip 已播放时间(秒)。
/// </summary>
public float elapsedTime;
/// <summary>
/// Clip 总时长(秒)。
/// </summary>
public float duration;
/// <summary>
/// 当前 Clip 的综合时间缩放系数(含 Global/Group/Local由 FeedbackPlayer 每帧动态计算。
/// </summary>
public float timeScale;
/// <summary>
/// 当前 Clip 是否动态获取当前的时间缩放
/// </summary>
public FeedbackTimeSettings timeSettings;
}
/// <summary>
/// 所有反馈动作的抽象基类,定义生命周期回调。
/// 使用 Odin 的序列化路径实现多态序列化SerializedScriptableObject
/// Odin 会自动为此抽象类型的字段显示多态类型选择器。
/// </summary>
[Serializable]
public abstract class FeedbackActionBase
{
/// <summary>
/// Inspector 中显示的名称。
/// </summary>
public virtual string DisplayName => GetType().Name;
/// <summary>
/// 是否忽略时间缩放。如果为true此Action将使用原始deltaTime不受TimeScale影响。
/// </summary>
public virtual bool IgnoreTimeScale => false;
/// <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() { }
public virtual FeedbackActionBase CreateRuntimeCopy()
{
return FeedbackRuntimeCloneUtility.CloneSerialized(this);
}
/// <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;
}
}
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));
}
}
}