Files
SoulliesOfficial 649b7a5ddc 更新
2026-05-23 08:27:50 -04:00

458 lines
17 KiB
C#
Raw Permalink 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.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<string>(), DisruptionType.NormalAction, 1.0f, 0, true);
[Title("技能区间")]
[ListDrawerSettings(
ListElementLabelName = "@this.GetIntervalLabel()",
AddCopiesLastElement = true)]
public List<FuncAnimInterval> intervals = new List<FuncAnimInterval>()
{
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<string, List<string>> interactions = new Dictionary<string, List<string>>();
[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<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];
}
public T Variable<T>(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<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 = 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<bool>)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;
}
/// <summary>
/// 创建此事件的独立副本,包括深拷贝 payload。
/// </summary>
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<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();
}
}
}
}