From 67edbc6ca1f7d5309eac5fb5ebce6bcd6744befc Mon Sep 17 00:00:00 2001 From: Patrik Holmberg Date: Fri, 25 Apr 2025 11:08:21 +0200 Subject: [PATCH 1/2] Restructured the project into a package --- LICENSE => LICENSE.md | 0 LICENSE.md.meta | 7 ++ Packages/manifest.json | 67 ------------------- README.md.meta | 7 ++ Runtime.meta | 8 +++ .../AnimationEvent.cs | 0 .../AnimationEvent.cs.meta | 0 .../AnimationEventReceiver.cs | 0 .../AnimationEventReceiver.cs.meta | 0 .../AnimationEventStateBehaviour.cs | 0 .../AnimationEventStateBehaviour.cs.meta | 0 .../AnimationEvents => Runtime}/Editor.meta | 2 +- .../Editor/AnimationEventDrawer.cs | 0 .../Editor/AnimationEventDrawer.cs.meta | 0 .../AnimationEventStateBehaviourEditor.cs | 0 ...AnimationEventStateBehaviourEditor.cs.meta | 0 ...ImprovedUnityAnimationEvents.Editor.asmdef | 18 +++++ ...vedUnityAnimationEvents.Editor.asmdef.meta | 7 ++ Runtime/ImprovedUnityAnimationEvents.asmdef | 14 ++++ .../ImprovedUnityAnimationEvents.asmdef.meta | 7 ++ package.json | 11 +++ package.json.meta | 7 ++ 22 files changed, 87 insertions(+), 68 deletions(-) rename LICENSE => LICENSE.md (100%) create mode 100644 LICENSE.md.meta delete mode 100644 Packages/manifest.json create mode 100644 README.md.meta create mode 100644 Runtime.meta rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEvent.cs (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEvent.cs.meta (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEventReceiver.cs (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEventReceiver.cs.meta (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEventStateBehaviour.cs (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/AnimationEventStateBehaviour.cs.meta (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/Editor.meta (77%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/Editor/AnimationEventDrawer.cs (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/Editor/AnimationEventDrawer.cs.meta (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/Editor/AnimationEventStateBehaviourEditor.cs (100%) rename {Assets/_Project/Scripts/AnimationEvents => Runtime}/Editor/AnimationEventStateBehaviourEditor.cs.meta (100%) create mode 100644 Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef create mode 100644 Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef.meta create mode 100644 Runtime/ImprovedUnityAnimationEvents.asmdef create mode 100644 Runtime/ImprovedUnityAnimationEvents.asmdef.meta create mode 100644 package.json create mode 100644 package.json.meta diff --git a/LICENSE b/LICENSE.md similarity index 100% rename from LICENSE rename to LICENSE.md diff --git a/LICENSE.md.meta b/LICENSE.md.meta new file mode 100644 index 0000000..91fdb1f --- /dev/null +++ b/LICENSE.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 5865749fb2f477441a10b65d50d016e2 +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Packages/manifest.json b/Packages/manifest.json deleted file mode 100644 index 197e54b..0000000 --- a/Packages/manifest.json +++ /dev/null @@ -1,67 +0,0 @@ -{ - "dependencies": { - "com.gitamend.improvedtimers": "git+https://github.com/adammyhre/Unity-Improved-Timers.git", - "com.gitamend.unityutils": "git+https://github.com/adammyhre/Unity-Utils.git", - "com.unity.ai.navigation": "2.0.4", - "com.unity.burst": "1.8.18", - "com.unity.cinemachine": "2.10.1", - "com.unity.collab-proxy": "2.5.2", - "com.unity.collections": "2.5.1", - "com.unity.dt.app-ui": "1.1.0", - "com.unity.editorcoroutines": "1.0.0", - "com.unity.ext.nunit": "2.0.5", - "com.unity.ide.rider": "3.0.31", - "com.unity.ide.visualstudio": "2.0.22", - "com.unity.inputsystem": "1.11.1", - "com.unity.mathematics": "1.3.2", - "com.unity.multiplayer.center": "1.0.0", - "com.unity.nuget.mono-cecil": "1.11.4", - "com.unity.postprocessing": "3.4.0", - "com.unity.recorder": "5.1.1", - "com.unity.render-pipelines.core": "17.0.3", - "com.unity.render-pipelines.universal": "17.0.3", - "com.unity.render-pipelines.universal-config": "17.0.3", - "com.unity.rendering.light-transport": "1.0.1", - "com.unity.searcher": "4.9.2", - "com.unity.shadergraph": "17.0.3", - "com.unity.test-framework": "1.4.5", - "com.unity.test-framework.performance": "3.0.3", - "com.unity.timeline": "1.8.7", - "com.unity.ugui": "2.0.0", - "com.unity.visualscripting": "1.9.4", - "com.unity.modules.accessibility": "1.0.0", - "com.unity.modules.ai": "1.0.0", - "com.unity.modules.androidjni": "1.0.0", - "com.unity.modules.animation": "1.0.0", - "com.unity.modules.assetbundle": "1.0.0", - "com.unity.modules.audio": "1.0.0", - "com.unity.modules.cloth": "1.0.0", - "com.unity.modules.director": "1.0.0", - "com.unity.modules.hierarchycore": "1.0.0", - "com.unity.modules.imageconversion": "1.0.0", - "com.unity.modules.imgui": "1.0.0", - "com.unity.modules.jsonserialize": "1.0.0", - "com.unity.modules.particlesystem": "1.0.0", - "com.unity.modules.physics": "1.0.0", - "com.unity.modules.physics2d": "1.0.0", - "com.unity.modules.screencapture": "1.0.0", - "com.unity.modules.subsystems": "1.0.0", - "com.unity.modules.terrain": "1.0.0", - "com.unity.modules.terrainphysics": "1.0.0", - "com.unity.modules.tilemap": "1.0.0", - "com.unity.modules.ui": "1.0.0", - "com.unity.modules.uielements": "1.0.0", - "com.unity.modules.umbra": "1.0.0", - "com.unity.modules.unityanalytics": "1.0.0", - "com.unity.modules.unitywebrequest": "1.0.0", - "com.unity.modules.unitywebrequestassetbundle": "1.0.0", - "com.unity.modules.unitywebrequestaudio": "1.0.0", - "com.unity.modules.unitywebrequesttexture": "1.0.0", - "com.unity.modules.unitywebrequestwww": "1.0.0", - "com.unity.modules.vehicles": "1.0.0", - "com.unity.modules.video": "1.0.0", - "com.unity.modules.vr": "1.0.0", - "com.unity.modules.wind": "1.0.0", - "com.unity.modules.xr": "1.0.0" - } -} diff --git a/README.md.meta b/README.md.meta new file mode 100644 index 0000000..fe7dbb4 --- /dev/null +++ b/README.md.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 256daaa39158b6b49ad920bb089e9b04 +TextScriptImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime.meta b/Runtime.meta new file mode 100644 index 0000000..91e93c7 --- /dev/null +++ b/Runtime.meta @@ -0,0 +1,8 @@ +fileFormatVersion: 2 +guid: edcbdc38ddc746242a69b286a3ddcf22 +folderAsset: yes +DefaultImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs b/Runtime/AnimationEvent.cs similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs rename to Runtime/AnimationEvent.cs diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs.meta b/Runtime/AnimationEvent.cs.meta similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs.meta rename to Runtime/AnimationEvent.cs.meta diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs b/Runtime/AnimationEventReceiver.cs similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs rename to Runtime/AnimationEventReceiver.cs diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs.meta b/Runtime/AnimationEventReceiver.cs.meta similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs.meta rename to Runtime/AnimationEventReceiver.cs.meta diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs b/Runtime/AnimationEventStateBehaviour.cs similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs rename to Runtime/AnimationEventStateBehaviour.cs diff --git a/Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs.meta b/Runtime/AnimationEventStateBehaviour.cs.meta similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs.meta rename to Runtime/AnimationEventStateBehaviour.cs.meta diff --git a/Assets/_Project/Scripts/AnimationEvents/Editor.meta b/Runtime/Editor.meta similarity index 77% rename from Assets/_Project/Scripts/AnimationEvents/Editor.meta rename to Runtime/Editor.meta index 8e79b30..718c153 100644 --- a/Assets/_Project/Scripts/AnimationEvents/Editor.meta +++ b/Runtime/Editor.meta @@ -1,5 +1,5 @@ fileFormatVersion: 2 -guid: a76d967b7753ac84487d62daad0326ce +guid: 0a9476d8535844b45ae4d726bcdef8f9 folderAsset: yes DefaultImporter: externalObjects: {} diff --git a/Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs b/Runtime/Editor/AnimationEventDrawer.cs similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs rename to Runtime/Editor/AnimationEventDrawer.cs diff --git a/Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs.meta b/Runtime/Editor/AnimationEventDrawer.cs.meta similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs.meta rename to Runtime/Editor/AnimationEventDrawer.cs.meta diff --git a/Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs b/Runtime/Editor/AnimationEventStateBehaviourEditor.cs similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs rename to Runtime/Editor/AnimationEventStateBehaviourEditor.cs diff --git a/Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs.meta b/Runtime/Editor/AnimationEventStateBehaviourEditor.cs.meta similarity index 100% rename from Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs.meta rename to Runtime/Editor/AnimationEventStateBehaviourEditor.cs.meta diff --git a/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef b/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef new file mode 100644 index 0000000..a78ab33 --- /dev/null +++ b/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef @@ -0,0 +1,18 @@ +{ + "name": "ImprovedUnityAnimationEvents.Editor", + "rootNamespace": "ImprovedUnityAnimationEvents", + "references": [ + "GUID:919e0586bef85cc4e95c0f6c823c98d4" + ], + "includePlatforms": [ + "Editor" + ], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef.meta b/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef.meta new file mode 100644 index 0000000..8575e75 --- /dev/null +++ b/Runtime/Editor/ImprovedUnityAnimationEvents.Editor.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: d09e765e7609ba14bbcb267d0a7068f2 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Runtime/ImprovedUnityAnimationEvents.asmdef b/Runtime/ImprovedUnityAnimationEvents.asmdef new file mode 100644 index 0000000..242abf0 --- /dev/null +++ b/Runtime/ImprovedUnityAnimationEvents.asmdef @@ -0,0 +1,14 @@ +{ + "name": "ImprovedUnityAnimationEvents", + "rootNamespace": "ImprovedUnityAnimationEvents", + "references": [], + "includePlatforms": [], + "excludePlatforms": [], + "allowUnsafeCode": false, + "overrideReferences": false, + "precompiledReferences": [], + "autoReferenced": true, + "defineConstraints": [], + "versionDefines": [], + "noEngineReferences": false +} \ No newline at end of file diff --git a/Runtime/ImprovedUnityAnimationEvents.asmdef.meta b/Runtime/ImprovedUnityAnimationEvents.asmdef.meta new file mode 100644 index 0000000..5d96b28 --- /dev/null +++ b/Runtime/ImprovedUnityAnimationEvents.asmdef.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 919e0586bef85cc4e95c0f6c823c98d4 +AssemblyDefinitionImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: diff --git a/package.json b/package.json new file mode 100644 index 0000000..b98e599 --- /dev/null +++ b/package.json @@ -0,0 +1,11 @@ +{ + "name": "com.gitamend.improved-unity-animation-events", + "version": "1.0.0", + "displayName": "Improved Unity Animation Events", + "description": "This system offers enhanced flexibility and control over Unity\u2019s Animation Event system, particularly valuable for managing complex animation events in both runtime and editor-time workflows.", + "unity": "2022.1", + "author": { + "name": "Git Amend", + "url": "https://www.youtube.com/@git-amend" + } +} \ No newline at end of file diff --git a/package.json.meta b/package.json.meta new file mode 100644 index 0000000..5af8815 --- /dev/null +++ b/package.json.meta @@ -0,0 +1,7 @@ +fileFormatVersion: 2 +guid: 1a8b013e07bae574ab5c824c238a295e +PackageManifestImporter: + externalObjects: {} + userData: + assetBundleName: + assetBundleVariant: From b5cca3ae2e52b986fa39d6949788d0ec1b58e3ab Mon Sep 17 00:00:00 2001 From: Patrik Holmberg Date: Fri, 25 Apr 2025 11:41:08 +0200 Subject: [PATCH 2/2] Added namespace --- Runtime/AnimationEvent.cs | 12 +- Runtime/AnimationEventReceiver.cs | 14 +- Runtime/AnimationEventStateBehaviour.cs | 40 +- Runtime/Editor/AnimationEventDrawer.cs | 42 +- .../AnimationEventStateBehaviourEditor.cs | 402 +++++++++--------- 5 files changed, 260 insertions(+), 250 deletions(-) diff --git a/Runtime/AnimationEvent.cs b/Runtime/AnimationEvent.cs index cdebffe..68418fa 100644 --- a/Runtime/AnimationEvent.cs +++ b/Runtime/AnimationEvent.cs @@ -1,8 +1,10 @@ using System; using UnityEngine.Events; -[Serializable] -public class AnimationEvent { - public string eventName; - public UnityEvent OnAnimationEvent; -} \ No newline at end of file +namespace ImprovedUnityAnimationEvents { + [Serializable] + public class AnimationEvent { + public string eventName; + public UnityEvent OnAnimationEvent; + } +} diff --git a/Runtime/AnimationEventReceiver.cs b/Runtime/AnimationEventReceiver.cs index 827325f..05abe02 100644 --- a/Runtime/AnimationEventReceiver.cs +++ b/Runtime/AnimationEventReceiver.cs @@ -1,11 +1,13 @@ using UnityEngine; using System.Collections.Generic; -public class AnimationEventReceiver : MonoBehaviour { - [SerializeField] List animationEvents = new(); +namespace ImprovedUnityAnimationEvents { + public class AnimationEventReceiver : MonoBehaviour { + [SerializeField] List animationEvents = new(); - public void OnAnimationEventTriggered(string eventName) { - AnimationEvent matchingEvent = animationEvents.Find(se => se.eventName == eventName); - matchingEvent?.OnAnimationEvent?.Invoke(); + public void OnAnimationEventTriggered(string eventName) { + AnimationEvent matchingEvent = animationEvents.Find(se => se.eventName == eventName); + matchingEvent?.OnAnimationEvent?.Invoke(); + } } -} \ No newline at end of file +} diff --git a/Runtime/AnimationEventStateBehaviour.cs b/Runtime/AnimationEventStateBehaviour.cs index f3e4951..4e50ea1 100644 --- a/Runtime/AnimationEventStateBehaviour.cs +++ b/Runtime/AnimationEventStateBehaviour.cs @@ -1,30 +1,32 @@ using UnityEngine; using UnityEngine.Events; -public class AnimationEventStateBehaviour : StateMachineBehaviour { - public string eventName; - [Range(0f, 1f)] public float triggerTime; - - bool hasTriggered; - AnimationEventReceiver receiver; +namespace ImprovedUnityAnimationEvents { + public class AnimationEventStateBehaviour : StateMachineBehaviour { + public string eventName; + [Range(0f, 1f)] public float triggerTime; + + bool hasTriggered; + AnimationEventReceiver receiver; - public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { - hasTriggered = false; - receiver = animator.GetComponent(); - } + public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { + hasTriggered = false; + receiver = animator.GetComponent(); + } - public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { - float currentTime = stateInfo.normalizedTime % 1f; + public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { + float currentTime = stateInfo.normalizedTime % 1f; - if (!hasTriggered && currentTime >= triggerTime) { - NotifyReceiver(animator); - hasTriggered = true; + if (!hasTriggered && currentTime >= triggerTime) { + NotifyReceiver(animator); + hasTriggered = true; + } } - } - void NotifyReceiver(Animator animator) { - if (receiver != null) { - receiver.OnAnimationEventTriggered(eventName); + void NotifyReceiver(Animator animator) { + if (receiver != null) { + receiver.OnAnimationEventTriggered(eventName); + } } } } diff --git a/Runtime/Editor/AnimationEventDrawer.cs b/Runtime/Editor/AnimationEventDrawer.cs index 671575f..617b0c8 100644 --- a/Runtime/Editor/AnimationEventDrawer.cs +++ b/Runtime/Editor/AnimationEventDrawer.cs @@ -2,27 +2,29 @@ using UnityEditor; using UnityEngine; -[CustomPropertyDrawer(typeof(AnimationEvent))] -public class AnimationEventDrawer : PropertyDrawer { - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { - EditorGUI.BeginProperty(position, label, property); - - SerializedProperty stateNameProperty = property.FindPropertyRelative("eventName"); - SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); - - Rect stateNameRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); - Rect stateEventRect = new(position.x, position.y + EditorGUIUtility.singleLineHeight + 2, position.width, - EditorGUI.GetPropertyHeight(stateEventProperty)); - - EditorGUI.PropertyField(stateNameRect, stateNameProperty); - EditorGUI.PropertyField(stateEventRect, stateEventProperty, true); - - EditorGUI.EndProperty(); - } +namespace ImprovedUnityAnimationEvents { + [CustomPropertyDrawer(typeof(AnimationEvent))] + public class AnimationEventDrawer : PropertyDrawer { + public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { + EditorGUI.BeginProperty(position, label, property); + + SerializedProperty stateNameProperty = property.FindPropertyRelative("eventName"); + SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); + + Rect stateNameRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); + Rect stateEventRect = new(position.x, position.y + EditorGUIUtility.singleLineHeight + 2, position.width, + EditorGUI.GetPropertyHeight(stateEventProperty)); + + EditorGUI.PropertyField(stateNameRect, stateNameProperty); + EditorGUI.PropertyField(stateEventRect, stateEventProperty, true); + + EditorGUI.EndProperty(); + } - public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { - SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); - return EditorGUIUtility.singleLineHeight + EditorGUI.GetPropertyHeight(stateEventProperty) + 4; + public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { + SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); + return EditorGUIUtility.singleLineHeight + EditorGUI.GetPropertyHeight(stateEventProperty) + 4; + } } } #endif diff --git a/Runtime/Editor/AnimationEventStateBehaviourEditor.cs b/Runtime/Editor/AnimationEventStateBehaviourEditor.cs index 286788b..91b6be5 100644 --- a/Runtime/Editor/AnimationEventStateBehaviourEditor.cs +++ b/Runtime/Editor/AnimationEventStateBehaviourEditor.cs @@ -13,267 +13,269 @@ /// and handling animation events within the Unity editor. Enables users to preview animations and manage /// animation events directly in the editor. /// -[UnityEditor.CustomEditor(typeof(AnimationEventStateBehaviour))] -public class AnimationEventStateBehaviourEditor : Editor { - Motion previewClip; - float previewTime; - bool isPreviewing; - - PlayableGraph playableGraph; - AnimationMixerPlayable mixer; - - public override void OnInspectorGUI() { - DrawDefaultInspector(); - - AnimationEventStateBehaviour stateBehaviour = (AnimationEventStateBehaviour) target; - - if (Validate(stateBehaviour, out string errorMessage)) { - GUILayout.Space(10); - - if (isPreviewing) { - if (GUILayout.Button("Stop Preview")) { - EnforceTPose(); - isPreviewing = false; - AnimationMode.StopAnimationMode(); - playableGraph.Destroy(); - } else { - PreviewAnimationClip(stateBehaviour); +namespace ImprovedUnityAnimationEvents { + [UnityEditor.CustomEditor(typeof(AnimationEventStateBehaviour))] + public class AnimationEventStateBehaviourEditor : Editor { + Motion previewClip; + float previewTime; + bool isPreviewing; + + PlayableGraph playableGraph; + AnimationMixerPlayable mixer; + + public override void OnInspectorGUI() { + DrawDefaultInspector(); + + AnimationEventStateBehaviour stateBehaviour = (AnimationEventStateBehaviour) target; + + if (Validate(stateBehaviour, out string errorMessage)) { + GUILayout.Space(10); + + if (isPreviewing) { + if (GUILayout.Button("Stop Preview")) { + EnforceTPose(); + isPreviewing = false; + AnimationMode.StopAnimationMode(); + playableGraph.Destroy(); + } else { + PreviewAnimationClip(stateBehaviour); + } + } else if (GUILayout.Button("Preview")) { + isPreviewing = true; + AnimationMode.StartAnimationMode(); } - } else if (GUILayout.Button("Preview")) { - isPreviewing = true; - AnimationMode.StartAnimationMode(); - } - GUILayout.Label($"Previewing at {previewTime:F2}s", EditorStyles.helpBox); - } else { - EditorGUILayout.HelpBox(errorMessage, MessageType.Info); + GUILayout.Label($"Previewing at {previewTime:F2}s", EditorStyles.helpBox); + } else { + EditorGUILayout.HelpBox(errorMessage, MessageType.Info); + } } - } - void PreviewAnimationClip(AnimationEventStateBehaviour stateBehaviour) { - AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); - if (animatorController == null) return; + void PreviewAnimationClip(AnimationEventStateBehaviour stateBehaviour) { + AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); + if (animatorController == null) return; - ChildAnimatorState matchingState = animatorController.layers - .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) - .FirstOrDefault(state => state.state != null); + ChildAnimatorState matchingState = animatorController.layers + .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) + .FirstOrDefault(state => state.state != null); - if (matchingState.state == null) return; + if (matchingState.state == null) return; - Motion motion = matchingState.state.motion; - - // Handle BlendTree logic - if (motion is BlendTree blendTree) { - SampleBlendTreeAnimation(stateBehaviour, stateBehaviour.triggerTime); - return; - } - - // If it's a simple AnimationClip, sample it directly - if (motion is AnimationClip clip) { - previewTime = stateBehaviour.triggerTime * clip.length; - AnimationMode.SampleAnimationClip(Selection.activeGameObject, clip, previewTime); - } - } + Motion motion = matchingState.state.motion; - void SampleBlendTreeAnimation(AnimationEventStateBehaviour stateBehaviour, float normalizedTime) { - Animator animator = Selection.activeGameObject.GetComponent(); + // Handle BlendTree logic + if (motion is BlendTree blendTree) { + SampleBlendTreeAnimation(stateBehaviour, stateBehaviour.triggerTime); + return; + } - if (playableGraph.IsValid()) { - playableGraph.Destroy(); + // If it's a simple AnimationClip, sample it directly + if (motion is AnimationClip clip) { + previewTime = stateBehaviour.triggerTime * clip.length; + AnimationMode.SampleAnimationClip(Selection.activeGameObject, clip, previewTime); + } } - playableGraph = PlayableGraph.Create("BlendTreePreviewGraph"); - mixer = AnimationMixerPlayable.Create(playableGraph, 1, true); + void SampleBlendTreeAnimation(AnimationEventStateBehaviour stateBehaviour, float normalizedTime) { + Animator animator = Selection.activeGameObject.GetComponent(); - var output = AnimationPlayableOutput.Create(playableGraph, "Animation", animator); - output.SetSourcePlayable(mixer); + if (playableGraph.IsValid()) { + playableGraph.Destroy(); + } - AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); - if (animatorController == null) return; + playableGraph = PlayableGraph.Create("BlendTreePreviewGraph"); + mixer = AnimationMixerPlayable.Create(playableGraph, 1, true); - ChildAnimatorState matchingState = animatorController.layers - .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) - .FirstOrDefault(state => state.state != null); + var output = AnimationPlayableOutput.Create(playableGraph, "Animation", animator); + output.SetSourcePlayable(mixer); - // If the matching state is not a BlendTree, bail out - if (matchingState.state.motion is not BlendTree blendTree) return; - - // Determine the maximum threshold value in the blend tree - float maxThreshold = blendTree.children.Max(child => child.threshold); + AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); + if (animatorController == null) return; - AnimationClipPlayable[] clipPlayables = new AnimationClipPlayable[blendTree.children.Length]; - float[] weights = new float[blendTree.children.Length]; - float totalWeight = 0f; + ChildAnimatorState matchingState = animatorController.layers + .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) + .FirstOrDefault(state => state.state != null); - // Scale target weight according to max threshold - float targetWeight = Mathf.Clamp(normalizedTime * maxThreshold, blendTree.minThreshold, maxThreshold); + // If the matching state is not a BlendTree, bail out + if (matchingState.state.motion is not BlendTree blendTree) return; + + // Determine the maximum threshold value in the blend tree + float maxThreshold = blendTree.children.Max(child => child.threshold); - for (int i = 0; i < blendTree.children.Length; i++) { - ChildMotion child = blendTree.children[i]; - float weight = CalculateWeightForChild(blendTree, child, targetWeight); - weights[i] = weight; - totalWeight += weight; + AnimationClipPlayable[] clipPlayables = new AnimationClipPlayable[blendTree.children.Length]; + float[] weights = new float[blendTree.children.Length]; + float totalWeight = 0f; - AnimationClip clip = GetAnimationClipFromMotion(child.motion); - clipPlayables[i] = AnimationClipPlayable.Create(playableGraph, clip); - } + // Scale target weight according to max threshold + float targetWeight = Mathf.Clamp(normalizedTime * maxThreshold, blendTree.minThreshold, maxThreshold); - // Normalize weights so they sum to 1 - for (int i = 0; i < weights.Length; i++) { - weights[i] /= totalWeight; - } + for (int i = 0; i < blendTree.children.Length; i++) { + ChildMotion child = blendTree.children[i]; + float weight = CalculateWeightForChild(blendTree, child, targetWeight); + weights[i] = weight; + totalWeight += weight; - mixer.SetInputCount(clipPlayables.Length); - for (int i = 0; i < clipPlayables.Length; i++) { - mixer.ConnectInput(i, clipPlayables[i], 0); - mixer.SetInputWeight(i, weights[i]); - } + AnimationClip clip = GetAnimationClipFromMotion(child.motion); + clipPlayables[i] = AnimationClipPlayable.Create(playableGraph, clip); + } - AnimationMode.SamplePlayableGraph(playableGraph, 0, normalizedTime); - } + // Normalize weights so they sum to 1 + for (int i = 0; i < weights.Length; i++) { + weights[i] /= totalWeight; + } - - float CalculateWeightForChild(BlendTree blendTree, ChildMotion child, float targetWeight) { - float weight = 0f; + mixer.SetInputCount(clipPlayables.Length); + for (int i = 0; i < clipPlayables.Length; i++) { + mixer.ConnectInput(i, clipPlayables[i], 0); + mixer.SetInputWeight(i, weights[i]); + } - if (blendTree.blendType == BlendTreeType.Simple1D) { - // Find the neighbors around the target weight - ChildMotion? lowerNeighbor = null; - ChildMotion? upperNeighbor = null; + AnimationMode.SamplePlayableGraph(playableGraph, 0, normalizedTime); + } - foreach (var motion in blendTree.children) { - if (motion.threshold <= targetWeight && (lowerNeighbor == null || motion.threshold > lowerNeighbor.Value.threshold)) { - lowerNeighbor = motion; + + float CalculateWeightForChild(BlendTree blendTree, ChildMotion child, float targetWeight) { + float weight = 0f; + + if (blendTree.blendType == BlendTreeType.Simple1D) { + // Find the neighbors around the target weight + ChildMotion? lowerNeighbor = null; + ChildMotion? upperNeighbor = null; + + foreach (var motion in blendTree.children) { + if (motion.threshold <= targetWeight && (lowerNeighbor == null || motion.threshold > lowerNeighbor.Value.threshold)) { + lowerNeighbor = motion; + } + + if (motion.threshold >= targetWeight && (upperNeighbor == null || motion.threshold < upperNeighbor.Value.threshold)) { + upperNeighbor = motion; + } } - if (motion.threshold >= targetWeight && (upperNeighbor == null || motion.threshold < upperNeighbor.Value.threshold)) { - upperNeighbor = motion; + if (lowerNeighbor.HasValue && upperNeighbor.HasValue) { + if (Mathf.Approximately(child.threshold, lowerNeighbor.Value.threshold)) { + weight = 1.0f - Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); + } else if (Mathf.Approximately(child.threshold, upperNeighbor.Value.threshold)) { + weight = Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); + } + } else { + // Handle edge cases where there is no valid interpolation range + weight = Mathf.Approximately(targetWeight, child.threshold) ? 1f : 0f; } + } else if (blendTree.blendType == BlendTreeType.FreeformCartesian2D || blendTree.blendType == BlendTreeType.FreeformDirectional2D) { + Vector2 targetPos = new( + GetBlendParameterValue(blendTree, blendTree.blendParameter), + GetBlendParameterValue(blendTree, blendTree.blendParameterY) + ); + float distance = Vector2.Distance(targetPos, child.position); + weight = Mathf.Clamp01(1.0f / (distance + 0.001f)); } - if (lowerNeighbor.HasValue && upperNeighbor.HasValue) { - if (Mathf.Approximately(child.threshold, lowerNeighbor.Value.threshold)) { - weight = 1.0f - Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); - } else if (Mathf.Approximately(child.threshold, upperNeighbor.Value.threshold)) { - weight = Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); - } - } else { - // Handle edge cases where there is no valid interpolation range - weight = Mathf.Approximately(targetWeight, child.threshold) ? 1f : 0f; - } - } else if (blendTree.blendType == BlendTreeType.FreeformCartesian2D || blendTree.blendType == BlendTreeType.FreeformDirectional2D) { - Vector2 targetPos = new( - GetBlendParameterValue(blendTree, blendTree.blendParameter), - GetBlendParameterValue(blendTree, blendTree.blendParameterY) - ); - float distance = Vector2.Distance(targetPos, child.position); - weight = Mathf.Clamp01(1.0f / (distance + 0.001f)); + return weight; } - return weight; - } + float GetBlendParameterValue(BlendTree blendTree, string parameterName) { + var methodInfo = typeof(BlendTree).GetMethod("GetInputBlendValue", BindingFlags.NonPublic | BindingFlags.Instance); + if (methodInfo == null) { + Debug.LogError("Failed to find GetInputBlendValue method via reflection."); + return 0f; + } - float GetBlendParameterValue(BlendTree blendTree, string parameterName) { - var methodInfo = typeof(BlendTree).GetMethod("GetInputBlendValue", BindingFlags.NonPublic | BindingFlags.Instance); - if (methodInfo == null) { - Debug.LogError("Failed to find GetInputBlendValue method via reflection."); - return 0f; + return (float) methodInfo.Invoke(blendTree, new object[] { parameterName }); } - return (float) methodInfo.Invoke(blendTree, new object[] { parameterName }); - } - - ChildAnimatorState FindMatchingState(AnimatorStateMachine stateMachine, AnimationEventStateBehaviour stateBehaviour) { - foreach (var state in stateMachine.states) { - if (state.state.behaviours.Contains(stateBehaviour)) { - return state; + ChildAnimatorState FindMatchingState(AnimatorStateMachine stateMachine, AnimationEventStateBehaviour stateBehaviour) { + foreach (var state in stateMachine.states) { + if (state.state.behaviours.Contains(stateBehaviour)) { + return state; + } } - } - foreach (var subStateMachine in stateMachine.stateMachines) { - var matchingState = FindMatchingState(subStateMachine.stateMachine, stateBehaviour); - if (matchingState.state != null) { - return matchingState; + foreach (var subStateMachine in stateMachine.stateMachines) { + var matchingState = FindMatchingState(subStateMachine.stateMachine, stateBehaviour); + if (matchingState.state != null) { + return matchingState; + } } + + return default; } - return default; - } + bool Validate(AnimationEventStateBehaviour stateBehaviour, out string errorMessage) { + AnimatorController animatorController = GetValidAnimatorController(out errorMessage); + if (animatorController == null) return false; - bool Validate(AnimationEventStateBehaviour stateBehaviour, out string errorMessage) { - AnimatorController animatorController = GetValidAnimatorController(out errorMessage); - if (animatorController == null) return false; + ChildAnimatorState matchingState = animatorController.layers + .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) + .FirstOrDefault(state => state.state != null); - ChildAnimatorState matchingState = animatorController.layers - .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) - .FirstOrDefault(state => state.state != null); + previewClip = GetAnimationClipFromMotion(matchingState.state?.motion); + if (previewClip == null) { + errorMessage = "No valid AnimationClip found for the current state."; + return false; + } - previewClip = GetAnimationClipFromMotion(matchingState.state?.motion); - if (previewClip == null) { - errorMessage = "No valid AnimationClip found for the current state."; - return false; + return true; } - return true; - } + AnimationClip GetAnimationClipFromMotion(Motion motion) { + if (motion is AnimationClip clip) { + return clip; + } - AnimationClip GetAnimationClipFromMotion(Motion motion) { - if (motion is AnimationClip clip) { - return clip; - } + if (motion is BlendTree blendTree) { + return blendTree.children + .Select(child => GetAnimationClipFromMotion(child.motion)) + .FirstOrDefault(childClip => childClip != null); + } - if (motion is BlendTree blendTree) { - return blendTree.children - .Select(child => GetAnimationClipFromMotion(child.motion)) - .FirstOrDefault(childClip => childClip != null); + return null; } - return null; - } + AnimatorController GetValidAnimatorController(out string errorMessage) { + errorMessage = string.Empty; - AnimatorController GetValidAnimatorController(out string errorMessage) { - errorMessage = string.Empty; + GameObject targetGameObject = Selection.activeGameObject; + if (targetGameObject == null) { + errorMessage = "Please select a GameObject with an Animator to preview."; + return null; + } - GameObject targetGameObject = Selection.activeGameObject; - if (targetGameObject == null) { - errorMessage = "Please select a GameObject with an Animator to preview."; - return null; - } + Animator animator = targetGameObject.GetComponent(); + if (animator == null) { + errorMessage = "The selected GameObject does not have an Animator component."; + return null; + } - Animator animator = targetGameObject.GetComponent(); - if (animator == null) { - errorMessage = "The selected GameObject does not have an Animator component."; - return null; - } + AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController; + if (animatorController == null) { + errorMessage = "The selected Animator does not have a valid AnimatorController."; + return null; + } - AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController; - if (animatorController == null) { - errorMessage = "The selected Animator does not have a valid AnimatorController."; - return null; + return animatorController; } - return animatorController; - } - - [MenuItem("GameObject/Enforce T-Pose", false, 0)] - static void EnforceTPose() { - GameObject selected = Selection.activeGameObject; - if (!selected || !selected.TryGetComponent(out Animator animator) || !animator.avatar) return; + [MenuItem("GameObject/Enforce T-Pose", false, 0)] + static void EnforceTPose() { + GameObject selected = Selection.activeGameObject; + if (!selected || !selected.TryGetComponent(out Animator animator) || !animator.avatar) return; - SkeletonBone[] skeletonBones = animator.avatar.humanDescription.skeleton; + SkeletonBone[] skeletonBones = animator.avatar.humanDescription.skeleton; - foreach (HumanBodyBones hbb in Enum.GetValues(typeof(HumanBodyBones))) { - if (hbb == HumanBodyBones.LastBone) continue; + foreach (HumanBodyBones hbb in Enum.GetValues(typeof(HumanBodyBones))) { + if (hbb == HumanBodyBones.LastBone) continue; - Transform boneTransform = animator.GetBoneTransform(hbb); - if (!boneTransform) continue; + Transform boneTransform = animator.GetBoneTransform(hbb); + if (!boneTransform) continue; - SkeletonBone skeletonBone = skeletonBones.FirstOrDefault(sb => sb.name == boneTransform.name); - if (skeletonBone.name == null) continue; + SkeletonBone skeletonBone = skeletonBones.FirstOrDefault(sb => sb.name == boneTransform.name); + if (skeletonBone.name == null) continue; - if (hbb == HumanBodyBones.Hips) boneTransform.localPosition = skeletonBone.position; - boneTransform.localRotation = skeletonBone.rotation; + if (hbb == HumanBodyBones.Hips) boneTransform.localPosition = skeletonBone.position; + boneTransform.localRotation = skeletonBone.rotation; + } } } }