├── ControllerInitialStateContext.cs ├── README.md ├── ReverseAnimationContext.cs └── UnloopAnimationContext.cs /ControllerInitialStateContext.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | using UnityEngine; 4 | using UnityEditor; 5 | using UnityEditor.Animations; 6 | 7 | public static class ControllerInitialState 8 | { 9 | [MenuItem("Assets/Set Initial State", false, 16)] 10 | private static void SetInitialStates() 11 | { 12 | List conts = GetSelectedControllers(); 13 | if (conts != null && conts.Count > 0) 14 | { 15 | foreach (AnimatorController con in conts) 16 | { 17 | SetInitialState(con); 18 | } 19 | } 20 | } 21 | 22 | public static List GetSelectedControllers() 23 | { 24 | var conts = Selection.GetFiltered(typeof(AnimatorController), SelectionMode.Assets); 25 | List animConts = new List(); 26 | if (conts.Length > 0) 27 | { 28 | foreach (var cont in conts) 29 | { 30 | animConts.Add(cont as AnimatorController); 31 | } 32 | return animConts; 33 | } 34 | return null; 35 | } 36 | 37 | private static void SetInitialState(AnimatorController cont) 38 | { 39 | AnimatorStateMachine asm = cont.layers[0].stateMachine; 40 | AnimatorState newState = asm.AddState("Default State"); 41 | asm.defaultState = newState; 42 | } 43 | 44 | [MenuItem("Assets/Set Initial State", true)] 45 | static bool SetInitialStateValidation() 46 | { 47 | return Selection.activeObject && Selection.activeObject.GetType() == typeof(AnimatorController); 48 | } 49 | 50 | } 51 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # unity-editor-tools 2 | Some simple quality of life tools for Unity. 3 | 4 | Simply drop these scripts in your project and the context menu options will appear when you right-click on applicable files. 5 | Make sure these scripts are within an "Editor"-folder in Unity to be excluded in builds. 6 | 7 | All context scripts work with shift-clicking or control-clicking to affect multiple files at once. 8 | 9 | ------------------------------------- 10 | 11 | ReverseAnimationContext.cs allows you to easily reverse animation clips. Based on the discussion here (https://forum.unity.com/threads/how-can-i-play-an-animation-backwards.498287/) and Bunny83's answer here (https://answers.unity.com/questions/476819/reverse-animation-help.html) 12 | 13 | ![Reverse Animations](https://i.imgur.com/NCcCKn2.gif) 14 | 15 | ControllerInitialStateContext.cs allows you to set a new, empty state as the default state to an AnimationController. 16 | 17 | ![Set Default State](https://i.imgur.com/foLtUcT.gif) 18 | 19 | UnloopAnimationContext.cs allows you to easily set animation clips to not loop. 20 | 21 | ![Unloop Animations](https://i.imgur.com/QIBHvaB.gif) 22 | 23 | 24 | -------------------------------------------------------------------------------- /ReverseAnimationContext.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using UnityEditor.Animations; 7 | 8 | public static class ReverseAnimationContext 9 | { 10 | [MenuItem("Assets/Create Reversed Clip", false, 14)] 11 | private static void ReverseClips() 12 | { 13 | var animators = Object.FindObjectsByType(FindObjectsSortMode.None); 14 | AssetDatabase.FindAssets("t:AnimatorController"); 15 | List clips = GetSelectedClips(); 16 | 17 | if (clips is not { Count: > 0 }) 18 | return; 19 | 20 | foreach (AnimationClip clip in clips) 21 | { 22 | ReverseClip(clip, animators); 23 | } 24 | 25 | Debug.Log("All selected clips reversed"); 26 | } 27 | 28 | private static List GetSelectedClips() 29 | { 30 | var clips = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Assets); 31 | 32 | if (clips.Length <= 0) 33 | return null; 34 | 35 | return clips.Select(clip => clip as AnimationClip).ToList(); 36 | } 37 | 38 | private static void ReverseClip(AnimationClip clip, Animator[] animators) 39 | { 40 | AnimationClip originalClip = clip; 41 | string directoryPath = Path.GetDirectoryName(AssetDatabase.GetAssetPath(clip)); 42 | string fileName = Path.GetFileName(AssetDatabase.GetAssetPath(clip)); 43 | string fileExtension = Path.GetExtension(AssetDatabase.GetAssetPath(clip)); 44 | fileName = fileName.Split('.')[0]; 45 | string copiedFilePath = directoryPath + Path.DirectorySeparatorChar + fileName + "_Reversed" + fileExtension; 46 | 47 | AssetDatabase.CopyAsset(AssetDatabase.GetAssetPath(clip), copiedFilePath); 48 | 49 | clip = (AnimationClip)AssetDatabase.LoadAssetAtPath(copiedFilePath, typeof(AnimationClip)); 50 | 51 | if (clip == null) 52 | return; 53 | 54 | float clipLength = clip.length; 55 | var curves = AnimationUtility.GetCurveBindings(clip); 56 | 57 | foreach (EditorCurveBinding binding in curves) 58 | { 59 | var animCurve = AnimationUtility.GetEditorCurve(clip, binding); 60 | var keys = animCurve.keys; 61 | int keyCount = keys.Length; 62 | 63 | for (int i = 0; i < keyCount; i++) 64 | { 65 | Keyframe kf = keys[i]; 66 | kf.time = clipLength - kf.time; 67 | var tmp = -kf.inTangent; 68 | kf.inTangent = -kf.outTangent; 69 | kf.outTangent = tmp; 70 | keys[i] = kf; 71 | } 72 | 73 | animCurve.keys = keys; 74 | clip.SetCurve(binding.path, binding.type, binding.propertyName, animCurve); 75 | } 76 | 77 | var events = AnimationUtility.GetAnimationEvents(clip); 78 | if (events.Length > 0) 79 | { 80 | foreach (var e in events) 81 | { 82 | e.time = clipLength - e.time; 83 | } 84 | 85 | AnimationUtility.SetAnimationEvents(clip, events); 86 | } 87 | 88 | var objectReferenceCurves = AnimationUtility.GetObjectReferenceCurveBindings(clip); 89 | foreach (EditorCurveBinding binding in objectReferenceCurves) 90 | { 91 | ObjectReferenceKeyframe[] objectReferenceKeyframes = 92 | AnimationUtility.GetObjectReferenceCurve(clip, binding); 93 | for (int i = 0; i < objectReferenceKeyframes.Length; i++) 94 | { 95 | ObjectReferenceKeyframe kf = objectReferenceKeyframes[i]; 96 | //K.time = clipLength - K.time - (1 / clip.frameRate); //Reversed sprite clips may be offset by 1 frame time 97 | kf.time = clipLength - kf.time; 98 | objectReferenceKeyframes[i] = kf; 99 | } 100 | 101 | AnimationUtility.SetObjectReferenceCurve(clip, binding, objectReferenceKeyframes); 102 | } 103 | 104 | foreach (Animator anim in animators) 105 | { 106 | AnimationClip[] clips = AnimationUtility.GetAnimationClips(anim.gameObject); 107 | 108 | if (clips.All(c => c != originalClip)) 109 | continue; 110 | 111 | Debug.Log("Found the animator containing the original clip that was reversed, adding new clip to its state machine..."); 112 | AnimatorController controller = AssetDatabase.LoadAssetAtPath(AssetDatabase.GetAssetPath(anim.runtimeAnimatorController)); 113 | AnimatorStateMachine asm = controller.layers[0].stateMachine; 114 | AnimatorState animState = asm.AddState(clip.name); 115 | animState.motion = clip; 116 | break; 117 | } 118 | } 119 | 120 | [MenuItem("Assets/Create Reversed Clip", true)] 121 | private static bool ReverseClipValidation() 122 | { 123 | return Selection.activeObject && Selection.activeObject is AnimationClip; 124 | } 125 | 126 | public static AnimationClip GetSelectedClip() 127 | { 128 | var clips = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Assets); 129 | if (clips.Length > 0) 130 | { 131 | return clips[0] as AnimationClip; 132 | } 133 | 134 | return null; 135 | } 136 | } 137 | -------------------------------------------------------------------------------- /UnloopAnimationContext.cs: -------------------------------------------------------------------------------- 1 | using UnityEditor; 2 | using UnityEngine; 3 | using System.IO; 4 | using System.Collections.Generic; 5 | 6 | public static class UnloopAnimationContext 7 | { 8 | [MenuItem("Assets/Unloop Animation", false, 15)] 9 | private static void UnloopClips() 10 | { 11 | List clips = GetSelectedClips(); 12 | if (clips != null && clips.Count > 0) 13 | { 14 | foreach (AnimationClip clip in clips) 15 | { 16 | UnLoopClip(clip); 17 | } 18 | } 19 | } 20 | public static List GetSelectedClips() 21 | { 22 | var clips = Selection.GetFiltered(typeof(AnimationClip), SelectionMode.Assets); 23 | List animClips = new List(); 24 | if (clips.Length > 0) 25 | { 26 | foreach (var clip in clips) 27 | { 28 | animClips.Add(clip as AnimationClip); 29 | } 30 | return animClips; 31 | } 32 | return null; 33 | } 34 | private static void UnLoopClip(AnimationClip clip) 35 | { 36 | AnimationClipSettings settings = AnimationUtility.GetAnimationClipSettings(clip); 37 | settings.loopTime = false; 38 | AnimationUtility.SetAnimationClipSettings(clip, settings); 39 | } 40 | [MenuItem("Assets/Unloop Animation", true)] 41 | static bool UnLoopValidation() 42 | { 43 | return Selection.activeObject && Selection.activeObject.GetType() == typeof(AnimationClip); 44 | } 45 | 46 | } 47 | --------------------------------------------------------------------------------