Files
Cielonos/Assets/Scripts/SLSUtilities/FunctionalAnimation/FuncAnimData.cs
SoulliesOfficial 33b1795c1f 更新
2026-01-03 18:19:39 -05:00

407 lines
15 KiB
C#

using System;
using System.Collections.Generic;
using System.Linq;
using Cielonos.MainGame.Characters;
using Sirenix.OdinInspector;
using SLSFramework.General;
using UnityEngine;
#if UNITY_EDITOR
using UnityEditor;
using UnityEditor.Callbacks;
#endif
namespace SLSUtilities.FunctionalAnimation
{
public enum TimeDisplayMode
{
[InspectorName("秒 (s)")] Seconds,
[InspectorName("帧 (f)")] Frames
}
[CreateAssetMenu(fileName = "NewFuncAnimData", menuName = "Functional Animation/FuncAnimData", order = 1)]
public class FuncAnimData : SerializedScriptableObject
{
public static bool EditorWantsRepaint = false;
[Title("编辑器设置")]
[EnumToggleButtons]
[OnValueChanged("RequestRepaint")] // <-- 修改
public TimeDisplayMode timeMode;
[Title("核心动画")]
[Required("必须指定一个动画片段")]
[OnValueChanged("RequestRepaint")] // <-- 修改
public AnimationClip animationClip;
[Title("编辑时信息")]
public FuncAnimInfo animInfo = new FuncAnimInfo("AnimationName", "StateName", true, new List<string>(),
DisruptionType.NormalAction, 1.0f, 0, true, new Dictionary<string, List<string>>());
[Title("技能区间")]
[ListDrawerSettings(
ListElementLabelName = "@this.GetIntervalLabel()", // <-- 修改为调用方法
AddCopiesLastElement = true)]
public List<FuncAnimInterval> intervals = new List<FuncAnimInterval>()
{
new FuncAnimInterval(IntervalType.Cancellable),
new FuncAnimInterval(IntervalType.Startup),
new FuncAnimInterval(IntervalType.ExternalDisruption),
new FuncAnimInterval(IntervalType.Active),
new FuncAnimInterval(IntervalType.Invincible),
new FuncAnimInterval(IntervalType.Preinput),
new FuncAnimInterval(IntervalType.ActionDisruption),
new FuncAnimInterval(IntervalType.MovementDisruption),
new FuncAnimInterval(IntervalType.RootMotion)
};
[Title("动画事件")]
public EventCollection eventCollection;
[Title("变量存储")]
public VariableCollection variableCollection;
// (新增) 核心方法:将父级引用传递给子级
// 这让子对象 (Event/Interval) 能够访问 animationClip 来计算帧
[OnInspectorGUI("UpdateChildReferences")] // 在绘制其他所有内容之前运行
private void UpdateChildReferences()
{
if (intervals != null)
{
foreach (var interval in intervals)
{
if (interval != null) interval.parentData = this;
}
}
if (eventCollection.animEvents != null)
{
foreach (var evt in eventCollection.animEvents)
{
if (evt != null) evt.parentData = this;
}
}
}
private void RequestRepaint()
{
#if UNITY_EDITOR
EditorWantsRepaint = true;
#endif
}
public FuncAnimInterval Interval(IntervalType type, string name = "", int index = 0)
{
List<FuncAnimInterval> find = intervals.FindAll(interval => interval.intervalType == type);
if (find.Count == 1)
{
if (type != IntervalType.Custom)
{
return find[0];
}
if (!string.IsNullOrEmpty(name) && find[0].intervalName == name)
{
return find[0];
}
Debug.LogWarning($"[FuncAnimData.Interval] 找不到名为 '{name}' 的自定义区间。");
return null;
}
if (find.Count == 0)
{
Debug.LogWarning($"[FuncAnimData.Interval] 找不到类型为 '{type}' 的区间。");
return null;
}
if (index < 0 || index >= find.Count)
{
Debug.LogWarning($"[FuncAnimData.Interval] 类型为 '{type}' 的区间数量不足,无法获取索引 {index}。");
return null;
}
return find[index];
}
}
}
namespace SLSUtilities.FunctionalAnimation
{
public enum IntervalType
{
[InspectorName("可取消窗口 (Cancellable)")]
Cancellable = 0,
[InspectorName("前摇 (Startup)")]
Startup = 10,
[InspectorName("外部打断窗口 (External Disruption)")]
ExternalDisruption = 11,
[InspectorName("强制外部打断窗口 (Forced External Disruption),如果没有此区间,默认整个动作都可被强制打断")]
ForcedExternalDisruption = 12,
[InspectorName("攻击判定 (Active)")]
Active = 20,
[InspectorName("无敌帧 (Invincible)")]
Invincible = 21,
[InspectorName("预输入窗口 (Pre-Input)")]
Preinput = 22,
[InspectorName("新动作打断窗口 (Action Disruption)")]
ActionDisruption = 30,
[InspectorName("移动打断窗口 (Movement Disruption)")]
MovementDisruption = 31,
[InspectorName("强制动作打断窗口 (Forced Action Disruption) 如果没有此区间,默认整个动作都可被强制打断")]
ForcedActionDisruption = 32,
[InspectorName("Root Motion 作用区间,角色会使用动画中的位移")]
RootMotion = 40,
[InspectorName("自定义 (Custom)")]
Custom = 100
}
[Serializable]
public class FuncAnimInterval // <-- 关键改动: struct 变为 class
{
[NonSerialized, HideInInspector]
public FuncAnimData parentData;
// (新增) 辅助函数
private float GetClipDuration() => (parentData == null || parentData.animationClip == null)
? 10.0f : parentData.animationClip.length; // 默认10s
private bool ShowSeconds() => parentData == null || parentData.timeMode == TimeDisplayMode.Seconds || parentData.animationClip == null;
private bool ShowFrames() => parentData != null && parentData.timeMode == TimeDisplayMode.Frames && parentData.animationClip != null;
private int GetMaxFrames() => (parentData == null || parentData.animationClip == null || parentData.animationClip.frameRate == 0)
? 100 : Mathf.RoundToInt(parentData.animationClip.length * parentData.animationClip.frameRate);
public string GetIntervalLabel()
{
return intervalType == IntervalType.Custom ? (string.IsNullOrEmpty(intervalName) ? "Custom" : intervalName) : intervalType.ToString();
}
public IntervalType intervalType;
[ShowIf("intervalType", IntervalType.Custom)]
public string intervalName;
// (修改) 仅在 "Seconds" 模式下显示
[ShowIf("ShowSeconds")]
[MinMaxSlider(0f, "@GetClipDuration()", ShowFields = true)]
[LabelText("Time Range (s)")]
[SuffixLabel("秒")]
public Vector2 timeRange;
// (新增) 在 "Frames" 模式下显示的 "帧" 属性
[ShowInInspector, ShowIf("ShowFrames"), MinMaxSlider(0, "@GetMaxFrames()", ShowFields = true)]
[LabelText("Frame Range")]
public Vector2Int FrameRange
{
get
{
if (parentData == null || parentData.animationClip == null) return Vector2Int.zero;
float frameRate = parentData.animationClip.frameRate;
if (frameRate == 0) return Vector2Int.zero;
// (修改) 从 timeRange (秒) 计算
return new Vector2Int(
Mathf.RoundToInt(timeRange.x * frameRate),
Mathf.RoundToInt(timeRange.y * frameRate)
);
}
set
{
if (parentData == null || parentData.animationClip == null) return;
float frameRate = parentData.animationClip.frameRate;
if (frameRate == 0) return;
// (修改) 转换回 timeRange (秒)
timeRange.x = (float)value.x / frameRate;
timeRange.y = (float)value.y / frameRate;
}
}
// (修改) 属性现在直接返回 timeRange
public float StartTime => timeRange.x;
public float EndTime => timeRange.y;
public FuncAnimInterval(IntervalType intervalType)
{
this.parentData = null;
this.intervalType = intervalType;
this.intervalName = "";
this.timeRange = Vector2.zero;
}
}
}
namespace SLSUtilities.FunctionalAnimation
{
[Serializable]
public class EventCollection : ICloneable<EventCollection>
{
[TitleGroup("时间轴事件")]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = true)]
public List<FuncAnimEvent> animEvents = new List<FuncAnimEvent>();
[TitleGroup("时间轴事件")]
[DisableInEditorMode]
[ShowInInspector]
[Tooltip("在动作被打断时触发的事件列表")]
public List<FuncAnimEvent> endEvents => animEvents.FindAll(evt => evt.isEnd);
[TitleGroup("非时间轴事件")]
[Tooltip("在技能开始播放动画 *之前* 立即执行")]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = false)]
[SerializeReference]
public List<FuncAnimPayloadBase> startEvents = new List<FuncAnimPayloadBase>();
[TitleGroup("非时间轴事件")]
[Tooltip("在技能被(StopSkill) *打断* 时执行")]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = false)]
[SerializeReference]
public List<FuncAnimPayloadBase> disruptionEvents = new List<FuncAnimPayloadBase>();
[TitleGroup("非时间轴事件")]
[Tooltip("技能播放期间每帧执行")]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = false)]
[SerializeReference]
public List<FuncAnimPayloadBase> updateEvents = new List<FuncAnimPayloadBase>();
[TitleGroup("非时间轴事件")]
[Tooltip("技能播放期间每帧执行")]
[ListDrawerSettings(ShowFoldout = true, DraggableItems = false)]
[SerializeReference]
public List<FuncAnimPayloadBase<bool>> updateUntilEvents = new List<FuncAnimPayloadBase<bool>>();
public EventCollection Clone()
{
EventCollection clone = new EventCollection();
clone.animEvents = new List<FuncAnimEvent>(this.animEvents);
clone.startEvents = new List<FuncAnimPayloadBase>(this.startEvents);
clone.disruptionEvents = new List<FuncAnimPayloadBase>(this.disruptionEvents);
clone.updateEvents = new List<FuncAnimPayloadBase>(this.updateEvents);
clone.updateUntilEvents = new List<FuncAnimPayloadBase<bool>>(this.updateUntilEvents);
return clone;
}
}
[Serializable]
public class FuncAnimEvent
{
// (新增) 非序列化的父级引用
[NonSerialized]
public FuncAnimData parentData;
// (新增) 辅助方法
private bool ShowSeconds() => parentData == null || parentData.timeMode == TimeDisplayMode.Seconds || parentData.animationClip == null;
private bool ShowFrames() => parentData != null && parentData.timeMode == TimeDisplayMode.Frames && parentData.animationClip != null;
private int GetMaxFrames() => (parentData == null || parentData.animationClip == null || parentData.animationClip.frameRate == 0)
? 100 : Mathf.RoundToInt(parentData.animationClip.length * parentData.animationClip.frameRate);
// (修改) 仅在 "Seconds" 模式下显示
[HorizontalGroup("EventLine", Width = 0.15f)]
[Tooltip("事件触发时间 (秒)")] [SuffixLabel("秒", Overlay = true)]
[LabelText("时间")] [LabelWidth(40)]
[ShowIf("ShowSeconds")]
public float triggerTime;
// (新增字段)
[HorizontalGroup("EventLine", Width = 0.15f)]
[ShowInInspector, ShowIf("ShowFrames"), SuffixLabel("f", Overlay = true)]
[LabelText("帧")] [LabelWidth(40)]
[MinValue(0)] [MaxValue("@GetMaxFrames()")]
public int TriggerFrame
{
get
{
if (parentData == null || parentData.animationClip == null) return 0;
float frameRate = parentData.animationClip.frameRate;
return Mathf.RoundToInt(triggerTime * frameRate);
}
set
{
if (parentData == null || parentData.animationClip == null) return;
float frameRate = parentData.animationClip.frameRate;
if (frameRate > 0)
{
triggerTime = (float)value / frameRate;
}
}
}
// (修改) "Bool" 是第二列
[HorizontalGroup("EventLine", Width = 0.15f)]
[Tooltip("如果技能被打断,是否也触发此事件?(即'End'事件)")]
[LabelWidth(40)]
[PropertyOrder(1)]
public bool isEnd;
// (修改) "Payload" 是第三列
[HorizontalGroup("EventLine")]
[HideLabel]
[SerializeReference]
[PropertyOrder(2)]
public FuncAnimPayloadBase payload;
public FuncAnimEvent(FuncAnimData parentData, float triggerTime, FuncAnimPayloadBase payload, bool isEnd)
{
this.parentData = parentData;
this.triggerTime = triggerTime;
this.isEnd = isEnd;
this.payload = payload;
}
public void Invoke()
{
payload?.Invoke();
}
}
public class VariableCollection
{
public Dictionary<string, string> variables = new Dictionary<string, string>();
public T GetVariable<T>(string key, T defaultValue = default)
{
if (variables.TryGetValue(key, out string valueStr))
{
try
{
return (T)Convert.ChangeType(valueStr, typeof(T));
}
catch
{
Debug.LogWarning($"[FuncAnimData.VariableCollection] 无法将变量 '{key}' 的值转换为类型 {typeof(T)},返回默认值。");
return defaultValue;
}
}
return defaultValue;
}
public VariableCollection Clone()
{
VariableCollection clone = new VariableCollection();
clone.variables = new Dictionary<string, string>(this.variables);
return clone;
}
}
public static class FuncAnimExtensions
{
public static void Invoke(this IList<FuncAnimEvent> events)
{
if (events == null) return;
foreach (FuncAnimEvent evt in events)
{
evt.Invoke();
}
}
public static void Invoke(this IList<FuncAnimPayloadBase> events)
{
if (events == null) return;
foreach (FuncAnimPayloadBase evt in events)
{
evt.Invoke();
}
}
}
}