using System; using System.Collections.Generic; using System.Linq; using Sirenix.OdinInspector; using SLSUtilities.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; [ReadOnly, ShowInInspector] public FuncAnimDataCollection parentCollection; [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(), DisruptionType.NormalAction, 1.0f, 0, true); [Title("技能区间")] [ListDrawerSettings( ListElementLabelName = "@this.GetIntervalLabel()", AddCopiesLastElement = true)] public List intervals = new List() { new FuncAnimInterval(IntervalType.ExternalDisruption), new FuncAnimInterval(IntervalType.Active), new FuncAnimInterval(IntervalType.Preinput), new FuncAnimInterval(IntervalType.ActionDisruption), new FuncAnimInterval(IntervalType.MovementDisruption), new FuncAnimInterval(IntervalType.RootMotion) }; [Title("交互处理")] [Tooltip("技能交互标签,用于标记技能与其他系统的交互关系,例如可以用来标记哪些技能可以被特定状态打断等")] public Dictionary> interactions = new Dictionary>(); [Title("动画事件")] public EventCollection eventCollection = new EventCollection(); [Title("变量存储")] public VariableCollection variableCollection = new VariableCollection(); // (新增) 核心方法:将父级引用传递给子级 // 这让子对象 (Event/Interval) 能够访问 animationClip 来计算帧 [OnInspectorInit("UpdateChildReferences")] [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 animEvent in eventCollection.animEvents) { if (animEvent is { payload: not null }) animEvent.payload.parentData = this; } foreach (var payload in eventCollection.startEvents) { if (payload != null) payload.parentData = this; } foreach (var payload in eventCollection.disruptionEvents) { if (payload != null) payload.parentData = this; } foreach (var payload in eventCollection.updateEvents) { if (payload != null) payload.parentData = this; } foreach (var payload in eventCollection.updateUntilEvents) { if (payload != null) payload.parentData = this; } } } private void RequestRepaint() { #if UNITY_EDITOR EditorWantsRepaint = true; #endif } public FuncAnimInterval Interval(IntervalType type, string name = "", int index = 0) { List 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]; } public T Variable(string key, T defaultValue = default) { if (variableCollection!= null && variableCollection.variables!= null && variableCollection.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; } } } namespace SLSUtilities.FunctionalAnimation { public enum IntervalType { [InspectorName("前摇 (Startup)")] Startup = 10, [InspectorName("外部打断窗口 (External Disruption)")] ExternalDisruption = 11, [InspectorName("强制外部打断窗口 (Forced External Disruption),如果没有此区间,默认整个动作都可被强制打断")] ForcedExternalDisruption = 12, [InspectorName("攻击判定 (Active)")] Active = 20, [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 float Duration => Mathf.Max(0, timeRange.y - timeRange.x); public FuncAnimInterval(IntervalType intervalType) { this.parentData = null; this.intervalType = intervalType; this.intervalName = ""; this.timeRange = Vector2.zero; } } } namespace SLSUtilities.FunctionalAnimation { [Serializable] public class EventCollection : ICloneable { [TitleGroup("时间轴事件")] [ListDrawerSettings(ShowFoldout = true, DraggableItems = true)] public List animEvents = new List(); [TitleGroup("时间轴事件")] [DisableInEditorMode] [ShowInInspector] [Tooltip("在动作被打断时触发的事件列表")] public List endEvents => animEvents.FindAll(evt => evt.isEnd); [TitleGroup("非时间轴事件")] [Tooltip("在技能开始播放动画 *之前* 立即执行")] [ListDrawerSettings(ShowFoldout = true, DraggableItems = false)] [SerializeReference] public List startEvents = new List(); [TitleGroup("非时间轴事件")] [Tooltip("在技能被(StopSkill) *打断* 时执行")] [ListDrawerSettings(ShowFoldout = true, DraggableItems = false)] [SerializeReference] public List disruptionEvents = new List(); [TitleGroup("非时间轴事件")] [Tooltip("技能播放期间每帧执行")] [ListDrawerSettings(ShowFoldout = true, DraggableItems = false)] [SerializeReference] public List updateEvents = new List(); [TitleGroup("非时间轴事件")] [Tooltip("技能播放期间每帧执行")] [ListDrawerSettings(ShowFoldout = true, DraggableItems = false)] [SerializeReference] public List> updateUntilEvents = new List>(); public EventCollection Clone() { EventCollection clone = new EventCollection(); clone.animEvents = this.animEvents.Select(evt => evt.DeepClone()).ToList(); clone.startEvents = this.startEvents.Select(p => p?.DeepClone()).ToList(); clone.disruptionEvents = this.disruptionEvents.Select(p => p?.DeepClone()).ToList(); clone.updateEvents = this.updateEvents.Select(p => p?.DeepClone()).ToList(); clone.updateUntilEvents = this.updateUntilEvents .Select(p => (FuncAnimPayloadBase)p?.DeepClone()).ToList(); return clone; } } [Serializable] public class FuncAnimEvent { // (新增) 非序列化的父级引用 private FuncAnimData parentData => payload?.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)] [HideReferenceObjectPicker] [LabelText("@NameForInspector")] public FuncAnimPayloadBase payload; public string NameForInspector => payload != null ? payload.NameForInspector + (payload.mute ? " (Muted)" : "") : "NULL"; public FuncAnimEvent(float triggerTime, FuncAnimPayloadBase payload, bool isEnd) { this.triggerTime = triggerTime; this.isEnd = isEnd; this.payload = payload; } /// /// 创建此事件的独立副本,包括深拷贝 payload。 /// public FuncAnimEvent DeepClone() { return new FuncAnimEvent(triggerTime, payload?.DeepClone(), isEnd); } public void Invoke() { payload?.Invoke(); } } public class VariableCollection { [InfoBox("RootMoveZ 计算方法:Active Interval的结束时Z坐标减去开始时Z坐标。")] public Dictionary variables = new Dictionary(); public T GetVariable(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(this.variables); return clone; } } public static class FuncAnimExtensions { public static void Invoke(this IList events) { if (events == null) return; foreach (FuncAnimEvent evt in events) { evt.Invoke(); } } public static void Invoke(this IList events) { if (events == null) return; foreach (FuncAnimPayloadBase evt in events) { evt.Invoke(); } } } }