├── .gitignore ├── Assets └── _Project │ └── Scripts │ └── AnimationEvents │ ├── AnimationEvent.cs │ ├── AnimationEvent.cs.meta │ ├── AnimationEventReceiver.cs │ ├── AnimationEventReceiver.cs.meta │ ├── AnimationEventStateBehaviour.cs │ ├── AnimationEventStateBehaviour.cs.meta │ ├── Editor.meta │ └── Editor │ ├── AnimationEventDrawer.cs │ ├── AnimationEventDrawer.cs.meta │ ├── AnimationEventStateBehaviourEditor.cs │ └── AnimationEventStateBehaviourEditor.cs.meta ├── LICENSE ├── Packages └── manifest.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # This .gitignore file should be placed at the root of your Unity project directory 2 | # 3 | # Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore 4 | # 5 | /[Ll]ibrary/ 6 | /[Tt]emp/ 7 | /[Oo]bj/ 8 | /[Bb]uild/ 9 | /[Bb]uildFullScreen/ 10 | /[Bb]uilds/ 11 | /[Ll]ogs/ 12 | /[Uu]ser[Ss]ettings/ 13 | /CCDBuildData/ 14 | 15 | # Ignore everything under Assets except the _Project folder 16 | Assets/* 17 | !Assets/_Project/ 18 | 19 | # MemoryCaptures can get excessive in size. 20 | # They also could contain extremely sensitive data 21 | /[Mm]emoryCaptures/ 22 | 23 | # Recordings can get excessive in size 24 | /[Rr]ecordings/ 25 | 26 | # Uncomment this line if you wish to ignore the asset store tools plugin 27 | # /[Aa]ssets/AssetStoreTools* 28 | 29 | # Autogenerated Jetbrains Rider plugin 30 | /[Aa]ssets/Plugins/Editor/JetBrains* 31 | 32 | # Visual Studio cache directory 33 | .vs/ 34 | 35 | # Gradle cache directory 36 | .gradle/ 37 | 38 | # Autogenerated VS/MD/Consulo solution and project files 39 | ExportedObj/ 40 | .consulo/ 41 | *.csproj 42 | *.unityproj 43 | *.sln 44 | *.suo 45 | *.tmp 46 | *.user 47 | *.userprefs 48 | *.pidb 49 | *.booproj 50 | *.svd 51 | *.pdb 52 | *.mdb 53 | *.opendb 54 | *.VC.db 55 | 56 | # Unity3D generated meta files 57 | *.pidb.meta 58 | *.pdb.meta 59 | *.mdb.meta 60 | 61 | # Unity3D generated file on crash reports 62 | sysinfo.txt 63 | 64 | # Builds 65 | *.apk 66 | *.aab 67 | *.unitypackage 68 | *.app 69 | 70 | # Crashlytics generated file 71 | crashlytics-build.properties 72 | 73 | # Packed Addressables 74 | /[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* 75 | 76 | # Temporary auto-generated Android Assets 77 | /[Aa]ssets/[Ss]treamingAssets/aa.meta 78 | /[Aa]ssets/[Ss]treamingAssets/aa/* 79 | 80 | # Custom 81 | Assets/SceneDependencyCache* 82 | Assets/NetCodeGenerated* 83 | .idea/ 84 | .DS_Store 85 | RiderScriptEditorPersistedState.asset 86 | Packages/packages-lock.json 87 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using UnityEngine.Events; 3 | 4 | [Serializable] 5 | public class AnimationEvent { 6 | public string eventName; 7 | public UnityEvent OnAnimationEvent; 8 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEvent.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 2c8930a89d408fb4ebb0cdf05b4f3a16 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using System.Collections.Generic; 3 | 4 | public class AnimationEventReceiver : MonoBehaviour { 5 | [SerializeField] List animationEvents = new(); 6 | 7 | public void OnAnimationEventTriggered(string eventName) { 8 | AnimationEvent matchingEvent = animationEvents.Find(se => se.eventName == eventName); 9 | matchingEvent?.OnAnimationEvent?.Invoke(); 10 | } 11 | } -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEventReceiver.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: cbe80126baae4cf0ba8194133e415a29 3 | timeCreated: 1729274533 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs: -------------------------------------------------------------------------------- 1 | using UnityEngine; 2 | using UnityEngine.Events; 3 | 4 | public class AnimationEventStateBehaviour : StateMachineBehaviour { 5 | public string eventName; 6 | [Range(0f, 1f)] public float triggerTime; 7 | 8 | bool hasTriggered; 9 | AnimationEventReceiver receiver; 10 | 11 | public override void OnStateEnter(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { 12 | hasTriggered = false; 13 | receiver = animator.GetComponent(); 14 | } 15 | 16 | public override void OnStateUpdate(Animator animator, AnimatorStateInfo stateInfo, int layerIndex) { 17 | float currentTime = stateInfo.normalizedTime % 1f; 18 | 19 | if (!hasTriggered && currentTime >= triggerTime) { 20 | NotifyReceiver(animator); 21 | hasTriggered = true; 22 | } 23 | } 24 | 25 | void NotifyReceiver(Animator animator) { 26 | if (receiver != null) { 27 | receiver.OnAnimationEventTriggered(eventName); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/AnimationEventStateBehaviour.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 89ae7ffbd33b42658b02ec41fecc0797 3 | timeCreated: 1729271187 -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/Editor.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: a76d967b7753ac84487d62daad0326ce 3 | folderAsset: yes 4 | DefaultImporter: 5 | externalObjects: {} 6 | userData: 7 | assetBundleName: 8 | assetBundleVariant: 9 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs: -------------------------------------------------------------------------------- 1 | #if UNITY_EDITOR 2 | using UnityEditor; 3 | using UnityEngine; 4 | 5 | [CustomPropertyDrawer(typeof(AnimationEvent))] 6 | public class AnimationEventDrawer : PropertyDrawer { 7 | public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { 8 | EditorGUI.BeginProperty(position, label, property); 9 | 10 | SerializedProperty stateNameProperty = property.FindPropertyRelative("eventName"); 11 | SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); 12 | 13 | Rect stateNameRect = new(position.x, position.y, position.width, EditorGUIUtility.singleLineHeight); 14 | Rect stateEventRect = new(position.x, position.y + EditorGUIUtility.singleLineHeight + 2, position.width, 15 | EditorGUI.GetPropertyHeight(stateEventProperty)); 16 | 17 | EditorGUI.PropertyField(stateNameRect, stateNameProperty); 18 | EditorGUI.PropertyField(stateEventRect, stateEventProperty, true); 19 | 20 | EditorGUI.EndProperty(); 21 | } 22 | 23 | public override float GetPropertyHeight(SerializedProperty property, GUIContent label) { 24 | SerializedProperty stateEventProperty = property.FindPropertyRelative("OnAnimationEvent"); 25 | return EditorGUIUtility.singleLineHeight + EditorGUI.GetPropertyHeight(stateEventProperty) + 4; 26 | } 27 | } 28 | #endif 29 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventDrawer.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 53a288335284eb7479e329737f0d60eb -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Reflection; 4 | using UnityEngine; 5 | using UnityEngine.Animations; 6 | using UnityEngine.Playables; 7 | 8 | #if UNITY_EDITOR 9 | using UnityEditor; 10 | using UnityEditor.Animations; 11 | /// 12 | /// Custom editor for the AnimationEventStateBehaviour class, providing a GUI for previewing animation states 13 | /// and handling animation events within the Unity editor. Enables users to preview animations and manage 14 | /// animation events directly in the editor. 15 | /// 16 | [UnityEditor.CustomEditor(typeof(AnimationEventStateBehaviour))] 17 | public class AnimationEventStateBehaviourEditor : Editor { 18 | Motion previewClip; 19 | float previewTime; 20 | bool isPreviewing; 21 | 22 | PlayableGraph playableGraph; 23 | AnimationMixerPlayable mixer; 24 | 25 | public override void OnInspectorGUI() { 26 | DrawDefaultInspector(); 27 | 28 | AnimationEventStateBehaviour stateBehaviour = (AnimationEventStateBehaviour) target; 29 | 30 | if (Validate(stateBehaviour, out string errorMessage)) { 31 | GUILayout.Space(10); 32 | 33 | if (isPreviewing) { 34 | if (GUILayout.Button("Stop Preview")) { 35 | EnforceTPose(); 36 | isPreviewing = false; 37 | AnimationMode.StopAnimationMode(); 38 | playableGraph.Destroy(); 39 | } else { 40 | PreviewAnimationClip(stateBehaviour); 41 | } 42 | } else if (GUILayout.Button("Preview")) { 43 | isPreviewing = true; 44 | AnimationMode.StartAnimationMode(); 45 | } 46 | 47 | GUILayout.Label($"Previewing at {previewTime:F2}s", EditorStyles.helpBox); 48 | } else { 49 | EditorGUILayout.HelpBox(errorMessage, MessageType.Info); 50 | } 51 | } 52 | 53 | void PreviewAnimationClip(AnimationEventStateBehaviour stateBehaviour) { 54 | AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); 55 | if (animatorController == null) return; 56 | 57 | ChildAnimatorState matchingState = animatorController.layers 58 | .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) 59 | .FirstOrDefault(state => state.state != null); 60 | 61 | if (matchingState.state == null) return; 62 | 63 | Motion motion = matchingState.state.motion; 64 | 65 | // Handle BlendTree logic 66 | if (motion is BlendTree blendTree) { 67 | SampleBlendTreeAnimation(stateBehaviour, stateBehaviour.triggerTime); 68 | return; 69 | } 70 | 71 | // If it's a simple AnimationClip, sample it directly 72 | if (motion is AnimationClip clip) { 73 | previewTime = stateBehaviour.triggerTime * clip.length; 74 | AnimationMode.SampleAnimationClip(Selection.activeGameObject, clip, previewTime); 75 | } 76 | } 77 | 78 | void SampleBlendTreeAnimation(AnimationEventStateBehaviour stateBehaviour, float normalizedTime) { 79 | Animator animator = Selection.activeGameObject.GetComponent(); 80 | 81 | if (playableGraph.IsValid()) { 82 | playableGraph.Destroy(); 83 | } 84 | 85 | playableGraph = PlayableGraph.Create("BlendTreePreviewGraph"); 86 | mixer = AnimationMixerPlayable.Create(playableGraph, 1, true); 87 | 88 | var output = AnimationPlayableOutput.Create(playableGraph, "Animation", animator); 89 | output.SetSourcePlayable(mixer); 90 | 91 | AnimatorController animatorController = GetValidAnimatorController(out string errorMessage); 92 | if (animatorController == null) return; 93 | 94 | ChildAnimatorState matchingState = animatorController.layers 95 | .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) 96 | .FirstOrDefault(state => state.state != null); 97 | 98 | // If the matching state is not a BlendTree, bail out 99 | if (matchingState.state.motion is not BlendTree blendTree) return; 100 | 101 | // Determine the maximum threshold value in the blend tree 102 | float maxThreshold = blendTree.children.Max(child => child.threshold); 103 | 104 | AnimationClipPlayable[] clipPlayables = new AnimationClipPlayable[blendTree.children.Length]; 105 | float[] weights = new float[blendTree.children.Length]; 106 | float totalWeight = 0f; 107 | 108 | // Scale target weight according to max threshold 109 | float targetWeight = Mathf.Clamp(normalizedTime * maxThreshold, blendTree.minThreshold, maxThreshold); 110 | 111 | for (int i = 0; i < blendTree.children.Length; i++) { 112 | ChildMotion child = blendTree.children[i]; 113 | float weight = CalculateWeightForChild(blendTree, child, targetWeight); 114 | weights[i] = weight; 115 | totalWeight += weight; 116 | 117 | AnimationClip clip = GetAnimationClipFromMotion(child.motion); 118 | clipPlayables[i] = AnimationClipPlayable.Create(playableGraph, clip); 119 | } 120 | 121 | // Normalize weights so they sum to 1 122 | for (int i = 0; i < weights.Length; i++) { 123 | weights[i] /= totalWeight; 124 | } 125 | 126 | mixer.SetInputCount(clipPlayables.Length); 127 | for (int i = 0; i < clipPlayables.Length; i++) { 128 | mixer.ConnectInput(i, clipPlayables[i], 0); 129 | mixer.SetInputWeight(i, weights[i]); 130 | } 131 | 132 | AnimationMode.SamplePlayableGraph(playableGraph, 0, normalizedTime); 133 | } 134 | 135 | 136 | float CalculateWeightForChild(BlendTree blendTree, ChildMotion child, float targetWeight) { 137 | float weight = 0f; 138 | 139 | if (blendTree.blendType == BlendTreeType.Simple1D) { 140 | // Find the neighbors around the target weight 141 | ChildMotion? lowerNeighbor = null; 142 | ChildMotion? upperNeighbor = null; 143 | 144 | foreach (var motion in blendTree.children) { 145 | if (motion.threshold <= targetWeight && (lowerNeighbor == null || motion.threshold > lowerNeighbor.Value.threshold)) { 146 | lowerNeighbor = motion; 147 | } 148 | 149 | if (motion.threshold >= targetWeight && (upperNeighbor == null || motion.threshold < upperNeighbor.Value.threshold)) { 150 | upperNeighbor = motion; 151 | } 152 | } 153 | 154 | if (lowerNeighbor.HasValue && upperNeighbor.HasValue) { 155 | if (Mathf.Approximately(child.threshold, lowerNeighbor.Value.threshold)) { 156 | weight = 1.0f - Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); 157 | } else if (Mathf.Approximately(child.threshold, upperNeighbor.Value.threshold)) { 158 | weight = Mathf.InverseLerp(lowerNeighbor.Value.threshold, upperNeighbor.Value.threshold, targetWeight); 159 | } 160 | } else { 161 | // Handle edge cases where there is no valid interpolation range 162 | weight = Mathf.Approximately(targetWeight, child.threshold) ? 1f : 0f; 163 | } 164 | } else if (blendTree.blendType == BlendTreeType.FreeformCartesian2D || blendTree.blendType == BlendTreeType.FreeformDirectional2D) { 165 | Vector2 targetPos = new( 166 | GetBlendParameterValue(blendTree, blendTree.blendParameter), 167 | GetBlendParameterValue(blendTree, blendTree.blendParameterY) 168 | ); 169 | float distance = Vector2.Distance(targetPos, child.position); 170 | weight = Mathf.Clamp01(1.0f / (distance + 0.001f)); 171 | } 172 | 173 | return weight; 174 | } 175 | 176 | 177 | float GetBlendParameterValue(BlendTree blendTree, string parameterName) { 178 | var methodInfo = typeof(BlendTree).GetMethod("GetInputBlendValue", BindingFlags.NonPublic | BindingFlags.Instance); 179 | if (methodInfo == null) { 180 | Debug.LogError("Failed to find GetInputBlendValue method via reflection."); 181 | return 0f; 182 | } 183 | 184 | return (float) methodInfo.Invoke(blendTree, new object[] { parameterName }); 185 | } 186 | 187 | ChildAnimatorState FindMatchingState(AnimatorStateMachine stateMachine, AnimationEventStateBehaviour stateBehaviour) { 188 | foreach (var state in stateMachine.states) { 189 | if (state.state.behaviours.Contains(stateBehaviour)) { 190 | return state; 191 | } 192 | } 193 | 194 | foreach (var subStateMachine in stateMachine.stateMachines) { 195 | var matchingState = FindMatchingState(subStateMachine.stateMachine, stateBehaviour); 196 | if (matchingState.state != null) { 197 | return matchingState; 198 | } 199 | } 200 | 201 | return default; 202 | } 203 | 204 | bool Validate(AnimationEventStateBehaviour stateBehaviour, out string errorMessage) { 205 | AnimatorController animatorController = GetValidAnimatorController(out errorMessage); 206 | if (animatorController == null) return false; 207 | 208 | ChildAnimatorState matchingState = animatorController.layers 209 | .Select(layer => FindMatchingState(layer.stateMachine, stateBehaviour)) 210 | .FirstOrDefault(state => state.state != null); 211 | 212 | previewClip = GetAnimationClipFromMotion(matchingState.state?.motion); 213 | if (previewClip == null) { 214 | errorMessage = "No valid AnimationClip found for the current state."; 215 | return false; 216 | } 217 | 218 | return true; 219 | } 220 | 221 | AnimationClip GetAnimationClipFromMotion(Motion motion) { 222 | if (motion is AnimationClip clip) { 223 | return clip; 224 | } 225 | 226 | if (motion is BlendTree blendTree) { 227 | return blendTree.children 228 | .Select(child => GetAnimationClipFromMotion(child.motion)) 229 | .FirstOrDefault(childClip => childClip != null); 230 | } 231 | 232 | return null; 233 | } 234 | 235 | AnimatorController GetValidAnimatorController(out string errorMessage) { 236 | errorMessage = string.Empty; 237 | 238 | GameObject targetGameObject = Selection.activeGameObject; 239 | if (targetGameObject == null) { 240 | errorMessage = "Please select a GameObject with an Animator to preview."; 241 | return null; 242 | } 243 | 244 | Animator animator = targetGameObject.GetComponent(); 245 | if (animator == null) { 246 | errorMessage = "The selected GameObject does not have an Animator component."; 247 | return null; 248 | } 249 | 250 | AnimatorController animatorController = animator.runtimeAnimatorController as AnimatorController; 251 | if (animatorController == null) { 252 | errorMessage = "The selected Animator does not have a valid AnimatorController."; 253 | return null; 254 | } 255 | 256 | return animatorController; 257 | } 258 | 259 | [MenuItem("GameObject/Enforce T-Pose", false, 0)] 260 | static void EnforceTPose() { 261 | GameObject selected = Selection.activeGameObject; 262 | if (!selected || !selected.TryGetComponent(out Animator animator) || !animator.avatar) return; 263 | 264 | SkeletonBone[] skeletonBones = animator.avatar.humanDescription.skeleton; 265 | 266 | foreach (HumanBodyBones hbb in Enum.GetValues(typeof(HumanBodyBones))) { 267 | if (hbb == HumanBodyBones.LastBone) continue; 268 | 269 | Transform boneTransform = animator.GetBoneTransform(hbb); 270 | if (!boneTransform) continue; 271 | 272 | SkeletonBone skeletonBone = skeletonBones.FirstOrDefault(sb => sb.name == boneTransform.name); 273 | if (skeletonBone.name == null) continue; 274 | 275 | if (hbb == HumanBodyBones.Hips) boneTransform.localPosition = skeletonBone.position; 276 | boneTransform.localRotation = skeletonBone.rotation; 277 | } 278 | } 279 | } 280 | 281 | #endif 282 | -------------------------------------------------------------------------------- /Assets/_Project/Scripts/AnimationEvents/Editor/AnimationEventStateBehaviourEditor.cs.meta: -------------------------------------------------------------------------------- 1 | fileFormatVersion: 2 2 | guid: 16ceb027015e98e45bd6b94dc90e0cf7 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | This is free and unencumbered software released into the public domain. 2 | 3 | Anyone is free to copy, modify, publish, use, compile, sell, or 4 | distribute this software, either in source code form or as a compiled 5 | binary, for any purpose, commercial or non-commercial, and by any 6 | means. 7 | 8 | In jurisdictions that recognize copyright laws, the author or authors 9 | of this software dedicate any and all copyright interest in the 10 | software to the public domain. We make this dedication for the benefit 11 | of the public at large and to the detriment of our heirs and 12 | successors. We intend this dedication to be an overt act of 13 | relinquishment in perpetuity of all present and future rights to this 14 | software under copyright law. 15 | 16 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, 17 | EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF 18 | MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. 19 | IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR 20 | OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, 21 | ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR 22 | OTHER DEALINGS IN THE SOFTWARE. 23 | 24 | For more information, please refer to 25 | -------------------------------------------------------------------------------- /Packages/manifest.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "com.gitamend.improvedtimers": "git+https://github.com/adammyhre/Unity-Improved-Timers.git", 4 | "com.gitamend.unityutils": "git+https://github.com/adammyhre/Unity-Utils.git", 5 | "com.unity.ai.navigation": "2.0.4", 6 | "com.unity.burst": "1.8.18", 7 | "com.unity.cinemachine": "2.10.1", 8 | "com.unity.collab-proxy": "2.5.2", 9 | "com.unity.collections": "2.5.1", 10 | "com.unity.dt.app-ui": "1.1.0", 11 | "com.unity.editorcoroutines": "1.0.0", 12 | "com.unity.ext.nunit": "2.0.5", 13 | "com.unity.ide.rider": "3.0.31", 14 | "com.unity.ide.visualstudio": "2.0.22", 15 | "com.unity.inputsystem": "1.11.1", 16 | "com.unity.mathematics": "1.3.2", 17 | "com.unity.multiplayer.center": "1.0.0", 18 | "com.unity.nuget.mono-cecil": "1.11.4", 19 | "com.unity.postprocessing": "3.4.0", 20 | "com.unity.recorder": "5.1.1", 21 | "com.unity.render-pipelines.core": "17.0.3", 22 | "com.unity.render-pipelines.universal": "17.0.3", 23 | "com.unity.render-pipelines.universal-config": "17.0.3", 24 | "com.unity.rendering.light-transport": "1.0.1", 25 | "com.unity.searcher": "4.9.2", 26 | "com.unity.shadergraph": "17.0.3", 27 | "com.unity.test-framework": "1.4.5", 28 | "com.unity.test-framework.performance": "3.0.3", 29 | "com.unity.timeline": "1.8.7", 30 | "com.unity.ugui": "2.0.0", 31 | "com.unity.visualscripting": "1.9.4", 32 | "com.unity.modules.accessibility": "1.0.0", 33 | "com.unity.modules.ai": "1.0.0", 34 | "com.unity.modules.androidjni": "1.0.0", 35 | "com.unity.modules.animation": "1.0.0", 36 | "com.unity.modules.assetbundle": "1.0.0", 37 | "com.unity.modules.audio": "1.0.0", 38 | "com.unity.modules.cloth": "1.0.0", 39 | "com.unity.modules.director": "1.0.0", 40 | "com.unity.modules.hierarchycore": "1.0.0", 41 | "com.unity.modules.imageconversion": "1.0.0", 42 | "com.unity.modules.imgui": "1.0.0", 43 | "com.unity.modules.jsonserialize": "1.0.0", 44 | "com.unity.modules.particlesystem": "1.0.0", 45 | "com.unity.modules.physics": "1.0.0", 46 | "com.unity.modules.physics2d": "1.0.0", 47 | "com.unity.modules.screencapture": "1.0.0", 48 | "com.unity.modules.subsystems": "1.0.0", 49 | "com.unity.modules.terrain": "1.0.0", 50 | "com.unity.modules.terrainphysics": "1.0.0", 51 | "com.unity.modules.tilemap": "1.0.0", 52 | "com.unity.modules.ui": "1.0.0", 53 | "com.unity.modules.uielements": "1.0.0", 54 | "com.unity.modules.umbra": "1.0.0", 55 | "com.unity.modules.unityanalytics": "1.0.0", 56 | "com.unity.modules.unitywebrequest": "1.0.0", 57 | "com.unity.modules.unitywebrequestassetbundle": "1.0.0", 58 | "com.unity.modules.unitywebrequestaudio": "1.0.0", 59 | "com.unity.modules.unitywebrequesttexture": "1.0.0", 60 | "com.unity.modules.unitywebrequestwww": "1.0.0", 61 | "com.unity.modules.vehicles": "1.0.0", 62 | "com.unity.modules.video": "1.0.0", 63 | "com.unity.modules.vr": "1.0.0", 64 | "com.unity.modules.wind": "1.0.0", 65 | "com.unity.modules.xr": "1.0.0" 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Improved Unity Animation Events 2 | 3 | ![AnimationEvents](https://github.com/user-attachments/assets/ab3b9e80-1533-454b-b551-78ff8d92169f) 4 | 5 | This repository offers a powerful, flexible system for managing **Animation Events** in Unity, with advanced capabilities for configuring, previewing, and controlling events during animation playback. It extends Unity's default animation workflow, giving developers greater control over animation events and their integration. 6 | 7 | With this system, events can be triggered from any state in your Animator Controller, including **Blend Trees**, allowing precise control over when events fire during animations. The system also features a customizable event receiver component that links animation events to UnityEvents, making event handling more flexible and maintainable. 8 | 9 | ## Key Features 10 | 11 | - **Custom Animation Events**: Configure and trigger animation events at specific, normalized times (0 to 1) within an animation state. 12 | - **Blend Tree Support**: Trigger events at the blend tree level, allowing precise event control when working with multiple animations. 13 | - **Event Receiver Component**: A reusable component that enables animation events to trigger UnityEvents, streamlining and maintaining event handling. 14 | - **Inspector Preview Mode (Scene View)**: Preview animations and blend trees *directly in the Scene view from the Unity Editor*, providing real-time visual feedback and dramatically improving workflow efficiency. 15 | - **T-Pose Reset**: Quickly reset characters to their default T-pose, aiding in animation rigging and debugging. 16 | - **StateMachineBehaviour Integration**: Uses `StateMachineBehaviour` to manage events tied to specific animation states. 17 | 18 | This system offers enhanced flexibility and control over Unity’s Animation Event system, particularly valuable for managing complex animation events in both runtime and editor-time workflows. 19 | 20 | ## YouTube 21 | 22 | [**Improved Animation Events in Unity**](https://youtu.be/XEDi7fUCQos?sub_confirmation=1) 23 | 24 | You can also check out my [YouTube channel](https://www.youtube.com/@git-amend?sub_confirmation=1) for more Unity content. --------------------------------------------------------------------------------