Search Unity

  1. Engage, network and learn at Unite Austin 2017, Oct 3 - 5. Get your ticket today!
    Dismiss Notice
  2. Introducing the Unity Essentials Packs! Find out more.
    Dismiss Notice
  3. Check out all the fixes for 5.6 on the patch releases page.
    Dismiss Notice
  4. Unity 2017.1 is now released.
    Dismiss Notice
  5. Help us improve the editor usability and artist workflows. Join our discussion to provide your feedback.
    Dismiss Notice
  6. Unity 2017.2 beta is now available for download.
    Dismiss Notice

Animation Window - Preview Animation with specific Start and End frames

Discussion in 'Animation' started by v2-Ton-Studios, Apr 25, 2017.

  1. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Is it possible to set the Animation Window so it "plays" / "previews" (when I press the 'Play' button in the Animation Window) an animation from a specific Start frame to a specific End frame?

    Ex. I have a Run animation, which consists of RunStart, RunLoop and RunEnd. I want to preview just the 'RunLoop', by setting the Start and End frames of the animation window to the beginning and end of 'RunLoop'.

    This is pretty common functionality in programs like Blender or Max.

    Right now the Animation Window plays everything RunStart --> RunLoop --> RunEnd. Looping back to frame 0, RunStart, when it reaches the last frame of RunEnd.

    Assuming this isn't possible, is there a way to...

    1) Extend the Animation Window to include the feature?

    2) Control the Animation Window through a separate editor extension e.g. an inspector extension?

    TIA.
     
  2. Aedous

    Aedous

    Joined:
    Jun 20, 2009
    Posts:
    228
    Hey I'm not sure if it's actually possible to extend the Animation window, I've tried this myself and had no good results, however I did find out that you can use reflection to actually do some things with it externally. So I guess that answers your second question.

    I made a quick shortcut to allow me to toggle the record button on / off, as there wasn't one for it, because there's a new bug with Unity 5.5.2f1 that doesn't refresh the Sprite Renderer when you change anything on the animation window :(.

    Hopefully it could be useful to you in someway.

    Static class for reflection:
    Code (CSharp):
    1.  
    2. public class wAnimationWindowHelper
    3. {
    4.     static System.Type animationWindowType = null;
    5.  
    6.     static System.Type GetAnimationWindowType()
    7.     {
    8.         if (animationWindowType == null)
    9.         {
    10.             animationWindowType = System.Type.GetType("UnityEditor.AnimationWindow,UnityEditor");
    11.         }
    12.         return animationWindowType;
    13.     }
    14.  
    15.     static UnityEngine.Object GetOpenAnimationWindow()
    16.     {
    17.         UnityEngine.Object[] openAnimationWindows = Resources.FindObjectsOfTypeAll(GetAnimationWindowType());
    18.         if (openAnimationWindows.Length > 0)
    19.         {
    20.             return openAnimationWindows[0];
    21.         }
    22.         return null;
    23.     }
    24.  
    25.     static void RepaintOpenAnimationWindow()
    26.     {
    27.         UnityEngine.Object w = GetOpenAnimationWindow();
    28.         if (w != null)
    29.         {
    30.             (w as EditorWindow).Repaint();
    31.         }
    32.     }
    33.  
    34.     static void PrintMethods()
    35.     {
    36.         UnityEngine.Object w = GetOpenAnimationWindow();
    37.         if (w != null)
    38.         {
    39.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    40.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    41.  
    42.             Type animEditorType = animEditor.FieldType;
    43.             System.Object animEditorObject = animEditor.GetValue(w);
    44.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    45.             Type windowStateType = animWindowState.FieldType;
    46.  
    47.             Debug.Log("Methods");
    48.             MethodInfo[] methods = windowStateType.GetMethods(BindingFlags.Public | BindingFlags.Instance);
    49.             Debug.Log("Methods : " + methods.Length);
    50.             for (int i = 0; i < methods.Length; i++)
    51.             {
    52.                 MethodInfo currentInfo = methods[i];
    53.                 Debug.Log(currentInfo.ToString());
    54.             }
    55.         }
    56.     }
    57. }
    58.  
    Functions that I use to do stuff from the static class.
    Code (CSharp):
    1.  
    2.     static void FireStartRecord()
    3.     {
    4.         UnityEngine.Object w = GetOpenAnimationWindow();
    5.         if (w != null)
    6.         {
    7.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    8.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    9.  
    10.             Type animEditorType = animEditor.FieldType;
    11.             System.Object animEditorObject = animEditor.GetValue(w);
    12.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    13.             Type windowStateType = animWindowState.FieldType;
    14.  
    15.             windowStateType.InvokeMember("set_recording", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), new object[1] { true });
    16.         }
    17.     }
    18.  
    19.     static void FireStopRecord()
    20.     {
    21.         UnityEngine.Object w = GetOpenAnimationWindow();
    22.         if (w != null)
    23.         {
    24.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    25.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    26.  
    27.             Type animEditorType = animEditor.FieldType;
    28.             System.Object animEditorObject = animEditor.GetValue(w);
    29.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    30.             Type windowStateType = animWindowState.FieldType;
    31.  
    32.             windowStateType.InvokeMember("set_recording", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), new object[1] { false });
    33.         }
    34.     }
    35.  
    36. public static void QuickRecordButton()
    37.     {
    38.         //I need the m_State StartRecording / m_State StopRecording
    39.         if(AnimationMode.InAnimationMode()) //Unity 5.5.2f1 new function I think?
    40.         {
    41.             FireStopRecord();
    42.         }
    43.         else
    44.         {
    45.             FireStartRecord();
    46.         }
    47.     }
    48. }
    49.  

    Then my shortcut code which is on ctrl + x :
    Code (CSharp):
    1. [MenuItem("Custom Commands/Refresh Animation Window %x")]
    2.         static void ToggleRecord()
    3.         {
    4.             if (EditorApplication.isPlaying)
    5.                 return;
    6.  
    7.             wAnimationWindowHelper.QuickRecordButton();
    8.         }

    Here's some other functions that I use:
    Code (CSharp):
    1.  
    2. AnimationClip GetAnimationWindowCurrentClip()
    3.     {
    4.         UnityEngine.Object w = GetOpenAnimationWindow();
    5.         if (w != null)
    6.         {
    7.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    8.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    9.  
    10.             Type animEditorType = animEditor.FieldType;
    11.             System.Object animEditorObject = animEditor.GetValue(w);
    12.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    13.             Type windowStateType = animWindowState.FieldType;
    14.  
    15.             System.Object clip = windowStateType.InvokeMember("get_activeAnimationClip", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), null);
    16.  
    17.             return (AnimationClip)clip;
    18.         }
    19.  
    20.         return null;
    21.     }
    22.  
    23. //Not sure if this still works
    24. int GetAnimationWindowCurrentFrame()
    25.     {
    26.         UnityEngine.Object w = GetOpenAnimationWindow();
    27.         if (w != null)
    28.         {
    29.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    30.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    31.  
    32.             Type animEditorType = animEditor.FieldType;
    33.             System.Object animEditorObject = animEditor.GetValue(w);
    34.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    35.             Type windowStateType = animWindowState.FieldType;
    36.  
    37.             System.Object frame = windowStateType.InvokeMember("get_frame", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), null);
    38.  
    39.             return (int)frame;
    40.         }
    41.  
    42.         return 0;
    43.     }
    44.  
    45. //not sure if this still works
    46.     float GetAnimationWindowCurrentTime()
    47.     {
    48.         UnityEngine.Object w = GetOpenAnimationWindow();
    49.         if (w != null)
    50.         {
    51.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    52.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    53.  
    54.             Type animEditorType = animEditor.FieldType;
    55.             System.Object animEditorObject = animEditor.GetValue(w);
    56.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    57.             Type windowStateType = animWindowState.FieldType;
    58.  
    59.             System.Object timeInSeconds = windowStateType.InvokeMember("get_currentTime", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), null);
    60.  
    61.             return (float)timeInSeconds;
    62.         }
    63.  
    64.         return 0f;
    65.     }
    66.  
    67.  
    I found some of the functions from here, and also found that printing out the methods on screen as well helped depending on your unity version.
    Some links specific to the animation stuff, AnimationWindow, AnimationState

    I wouldn't mind hearing what you manage to do with it :D, as it's an area I'm curious about as well.

    Good luck!
     
  3. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Thanks for this, I'll dig in tomorrow and share whatever I come up with.
     
  4. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Sorry for dragging my feet here, but I finally got around to working on this.

    Here's where I landed, hopefully it's useful to you and others. Thanks again for sharing the above, super helpful.

    I pulled out all of the initialization from each method and put it in an init(), just to make the methods a bit easier to read/write. I call init() from the InspectorEditor code, which I show after this reflection stuff.

    Code (csharp):
    1.  
    2. static UnityEngine.Object _window;
    3.  
    4. static  BindingFlags _flags;
    5. static FieldInfo _animEditor;
    6.  
    7. static Type _animEditorType;
    8. static System.Object _animEditorObject;
    9. static FieldInfo _animWindowState;
    10. static Type _windowStateType;
    11.  
    12. public static void init ()
    13. {
    14.     _window = GetOpenAnimationWindow();
    15.  
    16.     _flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    17.     _animEditor = GetAnimationWindowType().GetField("m_AnimEditor", _flags);
    18.  
    19.     _animEditorType = _animEditor.FieldType;
    20.     _animEditorObject = _animEditor.GetValue(_window);
    21.     _animWindowState = _animEditorType.GetField("m_State", _flags);
    22.     _windowStateType = _animWindowState.FieldType;
    23. }
    24.  
    Then I exposed the following methods by using the same reflection approach you suggested...

    Code (csharp):
    1.  
    2. public static bool GetPlaying()
    3. {
    4.     bool ret = false;
    5.  
    6.     if (_window != null)
    7.     {
    8.         System.Object playing = _windowStateType.InvokeMember ("get_playing", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    9.  
    10.         ret = (bool)playing;
    11.     }
    12.  
    13.     return ret;
    14. }
    15.  
    16. public static void Repaint()
    17. {
    18.     if (_window != null)
    19.     {
    20.         _windowStateType.InvokeMember ("Repaint", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    21.     }
    22. }
    23.  
    24. public static void StartPlayback()
    25. {
    26.     if (_window != null)
    27.     {
    28.         _windowStateType.InvokeMember("StartPlayback", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    29.     }
    30. }
    31.  
    32. public static void StopPlayback()
    33. {
    34.     if (_window != null)
    35.     {
    36.         _windowStateType.InvokeMember("StopPlayback", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    37.     }
    38. }
    39.  
    40. public static void SetCurrentFrame(int frame)
    41. {
    42.     if (_window != null)
    43.     {
    44.         _windowStateType.InvokeMember ("set_currentFrame", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), new object[1] { frame });
    45.     }
    46. }
    47.  
    48. public static int GetCurrentFrame()
    49. {
    50.     int ret = 0;
    51.  
    52.     if (_window != null)
    53.     {
    54.         System.Object frame = _windowStateType.InvokeMember("get_currentFrame", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    55.  
    56.         ret = (int)frame;
    57.     }
    58.  
    59.     return ret;
    60. }
    61.  

    Lastly, I created a custom Inspector Editor, AnimEditorController, with a simple class AnimController, which allows me to set/get Start / End -frame state from the Inspector.

    I attach this to the GameObject I'm animating e.g. the game's hero -- a Shield Maiden perhaps ;)

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5.  
    6. public class AnimController : MonoBehaviour {
    7.  
    8.     public int StartFrame;
    9.     public int EndFrame;
    10.  
    11.     // Use this for initialization
    12.     void Start () {
    13.        
    14.     }
    15.    
    16.     // Update is called once per frame
    17.     void Update () {
    18.        
    19.     }
    20. }
    21.  
    Code (csharp):
    1.  
    2. using UnityEngine;
    3. using System.Collections;
    4. using System.Collections.Generic;
    5. using UnityEditor;
    6. using System.Reflection;
    7.  
    8. [CustomEditor(typeof(AnimController))]
    9. public class AnimEditorController : Editor
    10. {      
    11.     AnimController _animCtrl;
    12.  
    13.     private bool _play = false;
    14.     private bool _looped = true;
    15.  
    16.     private int _lastFrame = 0;
    17.  
    18.     void OnEnable ()
    19.     {
    20.         EditorApplication.update += Update;
    21.         Debug.Log("OnEnable");
    22.  
    23.         wAnimationWindowHelper.init();
    24.     }
    25.  
    26.     void Update ()
    27.     {
    28.         if (_play && wAnimationWindowHelper.GetPlaying() == false)
    29.         {
    30.             StopPlayback();
    31.         }
    32.  
    33.         else if (_play)
    34.         {
    35.             int frame = wAnimationWindowHelper.GetCurrentFrame();
    36.  
    37.             float time = wAnimationWindowHelper.GetAnimationWindowCurrentTime();
    38.  
    39.             if (_looped)
    40.             {
    41.                 if (frame > _animCtrl.EndFrame - 1)
    42.                 {
    43.                     wAnimationWindowHelper.SetCurrentFrame(_animCtrl.StartFrame);
    44.                 }
    45.             }
    46.             else
    47.             {
    48.                 if (frame > _animCtrl.EndFrame - 1 || frame < _lastFrame)
    49.                 {
    50.                     StopPlayback();
    51.                     wAnimationWindowHelper.SetCurrentFrame(_animCtrl.EndFrame);
    52.                 }
    53.  
    54.                 _lastFrame = frame;
    55.             }
    56.         }
    57.     }
    58.  
    59.     public override void OnInspectorGUI()
    60.     {
    61.         base.OnInspectorGUI();
    62.         _animCtrl = target as AnimController;
    63.  
    64.         EditorGUI.BeginDisabledGroup(_play);
    65.  
    66.         _looped = EditorGUILayout.Toggle("Loop Playback", _looped);
    67.  
    68.  
    69.         if (_play == false)
    70.         {
    71.             if (GUILayout.Button("Play"))
    72.             {
    73.                 wAnimationWindowHelper.SetCurrentFrame(_animCtrl.StartFrame);
    74.                 wAnimationWindowHelper.StartPlayback();
    75.  
    76.                 _lastFrame = _animCtrl.StartFrame;
    77.  
    78.                 _play = true;
    79.             }
    80.         }
    81.         else
    82.         {
    83.             if (GUILayout.Button("Stop"))
    84.             {
    85.                 StopPlayback();
    86.             }
    87.         }
    88.  
    89.         EditorGUI.BeginDisabledGroup(_play);
    90.  
    91.         if (GUILayout.Button("Set Start"))
    92.         {
    93.             _animCtrl.StartFrame = wAnimationWindowHelper.GetCurrentFrame();
    94.         }
    95.  
    96.         if (GUILayout.Button("Set End"))
    97.         {
    98.             _animCtrl.EndFrame = wAnimationWindowHelper.GetCurrentFrame();
    99.         }
    100.  
    101.         if (GUILayout.Button("Goto Start"))
    102.         {
    103.             wAnimationWindowHelper.SetCurrentFrame(_animCtrl.StartFrame);
    104.  
    105.             // keyframe line doesn't seem to auto-repaint, had to force it
    106.             wAnimationWindowHelper.Repaint();
    107.         }
    108.  
    109.     }
    110.  
    111.     void StopPlayback ()
    112.     {
    113.         wAnimationWindowHelper.StopPlayback();
    114.  
    115.         _play = false;
    116.     }
    117. }
    118.  
    A few notes.

    In order to "bracket" the animation looping I needed to check for "CurrentFrame" at regular intervals, more frequent the call rate of OnGUI(). I did this by hooking into the Update() event.

    Code (csharp):
    1.  
    2. void OnEnable ()
    3. {
    4.     EditorApplication.update += Update;
    5.     Debug.Log("OnEnable");
    6. }
    7.  
    8. void Update ()
    9. {
    10.     if (_play && wAnimationWindowHelper.GetPlaying() == false)
    11.     {
    12.         StopPlayback();
    13.     }
    14.  
    15.     else if (_play)
    16.     {
    17.         int frame = wAnimationWindowHelper.GetCurrentFrame();
    18.  
    19.         float time = wAnimationWindowHelper.GetAnimationWindowCurrentTime();
    20.  
    21.         if (_looped)
    22.         {
    23.             if (frame > _animCtrl.EndFrame - 1)
    24.             {
    25.                 wAnimationWindowHelper.SetCurrentFrame(_animCtrl.StartFrame);
    26.             }
    27.         }
    28.         else
    29.         {
    30.             if (frame > _animCtrl.EndFrame - 1 || frame < _lastFrame)
    31.             {
    32.                 StopPlayback();
    33.                 wAnimationWindowHelper.SetCurrentFrame(_animCtrl.EndFrame);
    34.             }
    35.  
    36.             _lastFrame = frame;
    37.         }
    38.     }
    39. }
    40.  
    You'll notice that I added a 'Loop Playback' option. I often find it useful to see an animation once rather than looped, especially when animating attack animations or hit reactions.

    It only sort of works :(. Perhaps the Unity devs could add something like this :D

    If you set the Start / End -frames to something "inside" the Min / Max -frames of the animation, then it works fine -- playing once and then stopping on the end frame. However, if you set your Start / End -frames to Min / Max then it doesn't work as well.

    I assume that when Start / End are equal to Min / Max we're fighting with the AnimationEditor's own "looped play" functionality. Perhaps an editor dev could confirm and or suggest a way around this?

    The best I could do is a hack. Stopping playback and snapping to the end frame if we've gone beyond the end frame or started a new loop.

    Code (csharp):
    1.  
    2. if (_looped)
    3. {
    4.      ... snipped ...
    5. }
    6. else
    7. {
    8.     if (frame > _animCtrl.EndFrame - 1 || frame < _lastFrame)
    9.     {
    10.         StopPlayback();
    11.         wAnimationWindowHelper.SetCurrentFrame(_animCtrl.EndFrame);
    12.     }
    13.  
    14.     _lastFrame = frame;
    15. }
    16.  
    I also tried do this by tracking elapsed time, but found it was too unpredictable. If someone has a better suggestion let me know.

    Anyways, that's it. If I add more I'll share it here. Cheers!
     
  5. Aedous

    Aedous

    Joined:
    Jun 20, 2009
    Posts:
    228
    Nice! Looks like you just made it a lot better and easier to manage haha.
    I haven't gone and rewritten mine, but I found a way to make an editor to copy/paste an animation event by using some of the functions above, and some easy logic you can copy / paste events, it's not as sophisticated but simple enough to achieve and hopefully someone else can expand on it and make it better!


    Reflection Functionality:
    Code (CSharp):
    1. //Getting the animation window
    2. static UnityEngine.Object GetOpenAnimationWindow()
    3.     {
    4.         UnityEngine.Object[] openAnimationWindows = Resources.FindObjectsOfTypeAll(GetAnimationWindowType());
    5.         if (openAnimationWindows.Length > 0)
    6.         {
    7.             return openAnimationWindows[0];
    8.         }
    9.         return null;
    10.     }
    11.  
    12. //Get animation window current frame //Theres also one for time
    13. static int GetAnimationWindowCurrentFrame()
    14.     {
    15.         UnityEngine.Object w = GetOpenAnimationWindow();
    16.         if (w != null)
    17.         {
    18.             BindingFlags flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    19.             FieldInfo animEditor = GetAnimationWindowType().GetField("m_AnimEditor", flags);
    20.  
    21.             Type animEditorType = animEditor.FieldType;
    22.             System.Object animEditorObject = animEditor.GetValue(w);
    23.             FieldInfo animWindowState = animEditorType.GetField("m_State", flags);
    24.             Type windowStateType = animWindowState.FieldType;
    25.  
    26.             System.Object frame = windowStateType.InvokeMember("get_frame", BindingFlags.InvokeMethod | BindingFlags.Public, null, animWindowState.GetValue(animEditorObject), null);
    27.  
    28.             return (int)frame;
    29.         }
    30.  
    31.         return 0;
    32.     }

    Copy and Paste Functionality:

    Code (CSharp):
    1. //GUI Stuff
    2. AnimationClip inspectedAnimationClip;
    3. List<AnimationEvent> copiedAnimationEvents;
    4.  
    5.     void OnGUI()
    6.     {
    7.         AnimationClip currentClip = GetAnimationWindowCurrentClip();
    8.         if (currentClip)
    9.         {
    10.             inspectedAnimationClip = currentClip;
    11.         }
    12.        
    13.         if(inspectedAnimationClip)
    14.         {
    15.               DrawCustomGUI();
    16.         }
    17.     }
    18.  
    19. void DrawCustomGUI()
    20.     {
    21.         if(GUILayout.Button("Copy Event(s)"))
    22.         {
    23.             MakeCopyOfEventAtTime();
    24.         }
    25.  
    26.         if (copiedAnimationEvents != null)
    27.         {
    28.             if (copiedAnimationEvents.Count > 0)
    29.             {
    30.                 if (GUILayout.Button("Paste Event(s)"))
    31.                 {
    32.                     PasteCopyOfEventsAtTime();
    33.                 }
    34.             }
    35.         }
    36.      
    37.     }
    38.  
    39. //Actual Calculations
    40. void MakeCopyOfEventAtTime()
    41.     {
    42.         animationWindowTime = GetAnimationWindowCurrentFrame();
    43.      
    44.         //animationFrames =
    45.         if(copiedAnimationEvents == null)
    46.         {
    47.             copiedAnimationEvents = new List<AnimationEvent>();
    48.         }
    49.         copiedAnimationEvents.Clear();
    50.  
    51.         if(inspectedAnimationClip)
    52.         {
    53.             AnimationEvent[] clonedAnimationEvents = CloneAnimationEvents(inspectedAnimationClip, inspectedAnimationClip.events.Length);
    54.  
    55.             for(int i = 0; i < clonedAnimationEvents.Length; i++)
    56.             {
    57.                 //Find any events at that particular time
    58.                 float convertedTime = animationWindowTime / inspectedAnimationClip.frameRate;
    59.                 if(clonedAnimationEvents[i].time == convertedTime)
    60.                 {
    61.                     copiedAnimationEvents.Add(clonedAnimationEvents[i]);
    62.                 }
    63.             }
    64.         }
    65.     }
    66.  
    67.     void PasteCopyOfEventsAtTime()
    68.     {
    69.         if (inspectedAnimationClip)
    70.         {
    71.             if (copiedAnimationEvents.Count > 0)
    72.             {
    73.                 animationWindowTime = GetAnimationWindowCurrentFrame();
    74.  
    75.                 int originalLength = inspectedAnimationClip.events.Length;
    76.                 int newLength = copiedAnimationEvents.Count + originalLength;
    77.  
    78.                 AnimationEvent[] clonedAnimationEvents = CloneAnimationEvents(inspectedAnimationClip, newLength);
    79.  
    80.                 int neweventsIndexes = originalLength;
    81.                 //We then cycle through copied animation events and make sure our clip
    82.                 //get's updated with these values
    83.                 for (int i = 0; i < copiedAnimationEvents.Count; i++)
    84.                 {
    85.                     //Paste these values
    86.                     float convertedTime = animationWindowTime / inspectedAnimationClip.frameRate;
    87.                     clonedAnimationEvents[neweventsIndexes] = copiedAnimationEvents[i];
    88.                     clonedAnimationEvents[neweventsIndexes].time = convertedTime;
    89.                     neweventsIndexes++;
    90.                 }
    91.  
    92.                 //Once they have been copied over
    93.                 AnimationUtility.SetAnimationEvents(inspectedAnimationClip, clonedAnimationEvents);
    94.                 //RepaintAnimationWindow();
    95.             }
    96.         }
    97.     }
    98.  
    99.     AnimationEvent[] CloneAnimationEvents(AnimationClip sourceClip, int length)
    100.     {
    101.         AnimationEvent[] animationEventsDuplicate = new AnimationEvent[length];
    102.         Array.Copy(sourceClip.events, animationEventsDuplicate, sourceClip.events.Length);
    103.         return animationEventsDuplicate;
    104.     }

    I'm using this script in a custom editor window, but I'm sure can be done in other places.

    Issues:
    -Can't copy the selected event, always have to make sure the current time (the red line in the animation window) is at the position you want to copy.
    -Copies all events at a time, you can't copy them one by one
     
  6. Egil

    Egil

    Joined:
    Jul 29, 2012
    Posts:
    35
    Hi,

    Thanks a crazy bunch for sharing. Been struggling to figure out what animation clip was currently being edited for a long time. Not having to select the clip manually makes things much smoother. Been creating separate scenes for all animations until now, such a time saver.

    Any chance you guys know how to call the button functionality of "add keyframe" to all curves? My experience with reflection is limited, the "print methods" was confusing. Looking to create a function that can add keyframes to all curves in a clip on a interval, then remove the keys in-between. Simplify so to speak, as animations can get messy for editing some times, or just to be able to more easily edit a baked animation.

    Thanks again, this rocks.
     
  7. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    (Sorry, haven't checked this thread in a while... too busy deving, haha.)

    This is fantastically useful, at least once a day I wish this existed. @Unity Would love to see copy pasting of events get first-class integration into the animation experience.

    I like where this thread is going -- a general collection of ways to improve animation editing. So here's an effort to add on...

    One thing I might try to build is a "transition joiner".

    Example... I edit my Idle animation, but now have to go and update all the animations which transition to Idle: RunEnd, AttackEnd*, DodgeEnd, etc, etc.

    I'd love to be able to press a button and have all the start keys or end keys from specific animations get updated based a set of keys from another animation, in this case Idle.

    If I come up with anything I'll share it here.
     
    theANMATOR2b likes this.
  8. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Glad to hear this is useful :).

    The thread below talks about curves, I know it's not directly related, but near the bottom is a link to some code that shows how to walk through all the curves in an animation. I'd imagine from each curve you can walk the keyframes, evaling them as you go to remove or "simplify" as needed. Hope that helps a bit. If not, let me know, I might have time to dig a bit more.

    General Thread: https://forum.unity3d.com/threads/rotation-animation-issue-with-constant-tangents.213868/

    Link to code w sample for walking curves: https://github.com/unity-cn/CustomA...AnimationTools/Assets/CustomAnimationTools.cs
     
  9. Egil

    Egil

    Joined:
    Jul 29, 2012
    Posts:
    35
    Thanks for the reply.

    I'm pretty confident editing curves through scripting, the problem is adding keys with rotation. Animation Keys can have rotations spanning from 0 to indefinite, thus creating spin between keys.

    Here is where I'm stuck, getting the rotation from transform only returns 0-360, even if the rotation is 1080 in the inspector. The add keys button creates a key with 1080, add key to curve in a script will give it a value of 360. You can probably imagine the crazy rotations that happen when a 360 rotation key is added between two 1080 keys.

    So I thought maybe the easiest way around that would be to somehow call the "Add Key" function inside the animation view.
     
  10. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Shot in the dark... can you get the Quaternion value to avoid the 0-360 limit?
     
  11. Egil

    Egil

    Joined:
    Jul 29, 2012
    Posts:
    35
    As far as I can tell that only gives values like (0.2, -0.7, -0.2, -0.6). 0-1 clamped, pretty much the same story.

    Doh! :D
    This returns the proper value:
    curve.Evaluate(UnityAnimationWindow.GetAnimationWindowCurrentTime());

    One step closer, hard part is done.
    Got so stuck on trying to figure out how to get the value from the transform.
     
  12. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    I'm sure I had no direct impact, but sometimes new input helps unstick things. Onward! :)
     
    theANMATOR2b likes this.
  13. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    115
    Not the "animation joiner" that I'm still aiming for, but this add-on allows you to remove a specific property from all clips under a specific animator. I recently needed to adjust the position of my hero's hands and rather than updating 1000+ keys where his hands are positioned (automatically because I rotate them, but for no other reason) I wrote this :).

    https://github.com/SDanton/UnityHelpers
     
    theANMATOR2b likes this.