1. Help us improve the editor usability and artist workflows. Join our discussion to provide your feedback.
    Dismiss Notice
  2. We're looking for feedback on Unity Starter Kits! Let us know what you’d like.
    Dismiss Notice
  3. We’re giving 2017.1 beta testers a chance to win t-shirts and a Nintendo Switch. Read more on the blog.
    Dismiss Notice
  4. We want to know how you learned Unity! Help us by taking this quick survey and have a chance at a $25 gift card
    Dismiss Notice
  5. Are you an artist or level designer going to Unite Europe? Join our roundtables there to discuss artist features.
    Dismiss Notice
  6. Unity 5.6 is now released.
    Dismiss Notice
  7. Check out all the fixes for 5.6 on the patch releases page.
    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:
    89
    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:
    226
    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:
    89
    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:
    89
    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:
    226
    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