Search Unity

How to tell if an animation clip is playing in the editor(NOT in playmode) with an editor script

Discussion in 'Animation' started by arisonu123, Apr 20, 2017.

  1. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    Hi! I'm trying to tell if an animation clip is playing in the inspector. This clip is just being played from an animation clip inspector. Is it possible to tell if an animation clip is playing in the editor preview window and not in the game? I've been trying to do it with reflection by trying to access the AnimationInspectorEdtior and getting the animation controller from it but am unsure how to get my specific instance of this AnimationInspectorEditor(this is also the first time I've tried to use reflection so its entirely possible I'm doing it wrong). If anyone knows of a better way/has a solution please share.
     
  2. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    Although I'm pretty sure this tool will not provide the information you are looking for, @TrickyHandz created an editor window to identify when animation events are firing without having to run the game.
    https://forum.unity3d.com/threads/previewing-animations-with-events-attached.459628/#post-2984013
    This might provide enough information to develop a tool that can be suited for your specific needs.
    With that said - I remember someone asking a very similar question and a long time member (maybe hippo or TH) provided a useful workflow to see animations playing in editor without running the game. Might consider searching the animation thread - a little while before the original thread linked above was posted.

    Hope this points in the right direction.
     
  3. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    Well the reason I'm trying to do this in the first place is that I don't know of a way to trigger animation events in the editor in Unity. I was thinking that if I could tell when an animation is playing and get the time in the animation that is currently being played(still not entirely sure if I can even do that part...) then I could just have it call the function that my events call in my update for my editor window scripts at the appropriate times. I'm trying to remake a tool that my team had made in Unreal, in Unity and so far its proving to be really difficult. They want it to be basically the same thing and Unreal has an animation notify system(their animation events) that works in game and in the editor , it doesn't even require scripts on objects. It's proving to be a serious pain to remake this tool :(
     
  4. TrickyHandz

    TrickyHandz

    Joined:
    Jul 23, 2010
    Posts:
    196
    @arisonu123 this might be a bit of a tough one to tackle. As @theANMATOR2b mentioned I did write a quick tool that allows for being able to instantly trigger animations to see an AnimatorEvent being fired. It does require you to be in playmode, but you don't have to go through any gameplay actions, just click a button in the editor. The big limitation on it right now is that it won't properly trigger StateMachineBehaviours as it jumps directly to a state and might not hit things like OnStateEnter, OnStateExit, etc.

    I would be interested to know more about what you are trying to do and what you are currently looking at. Are you referring to the preview window that is seen on the model importer, the preview on a Mecanim transition or do you have the Animation Window up to scrub through an animation's curves/keyframes?

    Cheers,
    TrickyHandz
     
  5. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @TrickyHandz I am referring to the preview window that is seen on the model importer and the animation clips. I am interning at a company and they are making me remake their entire Unreal tool in Unity since apparently I know more about Unity than anyone else here(I guess they have mostly just used Unreal in the past and its not a game studio, the tool is my team's product. All of my school's game classes are Unity and C# pretty much and since I'm a game programming major I've take like 6 classes that were all Unity and C#.) Anyways the tool puts animation events on a selected animation at different times depending on things the user has put into my custom serialized object's inspector. I'm trying to make a preview for my animation with the events on it. Now I know events don't work in the editor so I'm trying to tell when the animation is playing and get the time that the animation is at(is this possible ?) to call the functions that my events would normally play at the proper times. Now I'm trying to use Unity's AvatarPreview, so since I couldn't figure out how to get just that tacked on as a preview on the bottom of my custom inspector's window I used reflection to create an inspector window. I'm creating the window, saving my currently selected active object(my custom object that is being inspected)in a variable,setting the active object to my animation clip variable, locking the animation clip inspector window so its information stays, and then setting my active object back to my custom object. I am unlocking the window when it closes or when my animation variable changes. Now all of this works fine, the window will show a new animation clip whenever my animation clip is changed , and the window will open when one of my serialized custom objects is selected if the serialized custom object has something set in its animation variable . I want to be able to tell when that is playing and the time it is at in the animation so that I can call functions at specific times in my update.

    Now I also tried to make a custom preview window instead of using the super awesome stuff that I think does what I want that Unity tries to keep from us but I couldn't get that working right either. Part of me feels like creating a custom preview window is the proper way/the way Unity wants you to do it, but this is the most I've ever done with editor scripts(before my internship all I had done was make a custom property drawer). I could get an animation playing but it was playing on objects in my scene and not this nice clean little scene thing like in Unity's AvatarPreview(Its called this in the decompiled dll, I found it there). I had it cloning the objects I dragged in the scene at position (0,0,0) and playing the animation on that. Now this didn't always work for some reason( I was sampling the animation based on time which i got from a slider that I had for trying to remake the scrubber bar and calling my function). I also couldn't figure out how to rotate the object for some reason. Being able to rotate,zoom in, and pan just like Unity's own animation/avatar preview is a requirement.

    I've also considered just taking the internal classes I need, copying them from the decompiled dll, dropping them into my project and making the things I need public, but its a lot of classes I would need to this with and I'm still not entirely sure if this can be made to work how I need it to(can I just do this and make an empty class that inherits from animation clip and make a new inspector that is basically the AnimationClipEditor but that inspects my class that would inherit from animation clip). Also I'm not sure if its a good idea to just do that since this is for a company and I'm not sure if there would be any legal issues with it.

    Now I also know I could just have people open(or I think I can open it by code too) a dummy empty scene, instantiate the object to preview in, and use what you have going to maybe play the events, but like you said this requires actually playing the game and honestly I don't think it seems too professional to do it this way(Maybe i'm wrong? Maybe AAA Unity devs are used to previews this way?).

    But ya its been a pain and I've been trying to get a working animation preview that does what I need it too for like 2 months.
     
  6. TrickyHandz

    TrickyHandz

    Joined:
    Jul 23, 2010
    Posts:
    196
    Well, this definitely has my interest. This is the type of tooling I really like experimenting with. I won't be able to really dig into a problem set of this size until at least next month. However, there are a few things you might want to look at. I would start with checking out Editor.OnPreviewGUI() and Editor.OnInteractivePreviewGUI(). These are the windows that are used for the preview windows of prefabs and materials. I would also recommend this blog on Working with Custom Object Previews which helped me out quite a bit when I was making something a custom preview window for one of my projects. I hope that might help you out.

    Cheers,
    TrickyHandz
     
  7. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @TrickyHandz @theANMATOR2b
    Ok! So I was able to get the instance of the animation clip editor with refleciton but now it throwing a null reference whenever I try to access its fields, also with reflection.

    Code (CSharp):
    1. private void Update(){
    2.    GetAnimationContoller();
    3. }
    4.  
    5. private void GetAnimationController(){
    6. var AnimPreviewInspector =  Resources.FindObjectsOfTypeAll(typeof(Editor).Assembly.GetType("UnityEditor.AnimationClipEditor"));      if((AnimPreviewInspector[0]==null)==true){    
    7.    Debug.Log("Your animation preview inspector is null");//this never prints out
    8. }
    9. var test=AnimPreviewInspector[0];    Debug.Log(test);//prints what is expected.
    10. //UnityEditor.AnimationClipEditor, does not say it is null    var  AnimController=typeof(Editor.GetField("m_Controller,System.Reflection.BindingFlags.NonPublic |  System.Reflection.BindingFlags.Instance).GetValue(test);//this throws a null reference, I have also tried just passing  AnimPreviewInspector[0] to the GetValue function
    11. }
    It doesn't make any sense to me because according to my debug logs my instance is not null. What am I missing here?
     
    Last edited: Apr 29, 2017
  8. theANMATOR2b

    theANMATOR2b

    Joined:
    Jul 12, 2014
    Posts:
    7,790
    Code makes little sense to me so I will only be good for moral support from here on out. :oops:

    One thing I know people request is to use code tags so they can read the code easier - with the line numbers and such - to help them help you.
    Code (CSharp):
    1. I'm a code tag
     
  9. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    You can get the current clip via reflection by calling " get_activeAnimationClip". Set up the context of the open animation window, then we can call methods on it.

    Code (csharp):
    1.  
    2. using System.Collections;
    3. using System.Collections.Generic;
    4. using UnityEngine;
    5. using UnityEditor;
    6. using System.Reflection;
    7. using System;
    8.  
    9. public class wAnimationWindowHelper
    10. {
    11.     static System.Type animationWindowType = null;
    12.  
    13.     static UnityEngine.Object _window;
    14.  
    15.     static  BindingFlags _flags;
    16.     static FieldInfo _animEditor;
    17.     static Type _animEditorType;
    18.     static System.Object _animEditorObject;
    19.     static FieldInfo _animWindowState;
    20.     static Type _windowStateType;
    21.  
    22.     public static void init ()
    23.     {
    24.         _window = GetOpenAnimationWindow();
    25.  
    26.         _flags = BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance;
    27.         _animEditor = GetAnimationWindowType().GetField("m_AnimEditor", _flags);
    28.  
    29.         _animEditorType = _animEditor.FieldType;
    30.         _animEditorObject = _animEditor.GetValue(_window);
    31.         _animWindowState = _animEditorType.GetField("m_State", _flags);
    32.         _windowStateType = _animWindowState.FieldType;
    33.     }
    34.  
    35.     public static UnityEngine.AnimationClip GetActiveAnimationClip()
    36.     {
    37.         UnityEngine.AnimationClip clip = null;
    38.  
    39.         if (_window != null)
    40.         {
    41.             System.Object o = _windowStateType.InvokeMember ("get_activeAnimationClip", BindingFlags.InvokeMethod | BindingFlags.Public, null, _animWindowState.GetValue(_animEditorObject), null);
    42.  
    43.             clip = (UnityEngine.AnimationClip)o;
    44.         }
    45.  
    46.         return clip;
    47.     }
    48. }
    49.  
    Then from a monobehavior or editor extension you can init() the AnimationWindowHelper...

    Code (csharp):
    1.  
    2. void OnEnable ()
    3. {
    4.     wAnimationWindowHelper.init();
    5. }
    6.  
    And then find out the active animation clip...

    Code (csharp):
    1.  
    2. if (GUILayout.Button("Get Clip"))
    3. {
    4.     Debug.Log("Current Clip: " + wAnimationWindowHelper.GetActiveAnimationClip());
    5. }
    6.  
    Hope that helps.

    You can print out all the methods if you want to do something else using...

    Code (csharp):
    1.  
    2. if (GUILayout.Button("Print Methods"))
    3. {
    4.     wAnimationWindowHelper.PrintMethods();
    5. }
    6.  
    Thanks to @Aedous for a good deal of the leg work on this.
     
  10. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @v2-Ton-Studios
    Hi! What you have looks like its for the AnimationWindow and not the AnimationClipEditor(Animation clip inspector)/Avatar Preview. Am I missing something here or?
     
  11. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Last edited: Apr 28, 2017
  12. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @v2-Ton-Studios
    I have looked at that already as well as AnimationController, Runtime Animation Controller, and Avatar Preview. You would think I would be able to do something like this:

    Code (CSharp):
    1.  
    2.        AnimatorClipInfo[] clipInfo=GetCurrentAnimatorClipInfo(0);
    3.          for(int i=0; i<clipInfo.Length; i++)
    4.          {
    5.              if(AnimatorClipInfo.clip.name==MyClip.name){
    6.                  Debug.Log("clip is playing");
    7.             }
    8.          }
    or perhaps something like this:

    Code (CSharp):
    1.  
    2.       AnimatorStateInfo stateInfo=GetCurrentAnimatorStateInfo(0);
    3.        if(stateInfo.IsName("preview"){
    4.            Debug.Log("clip is playing");
    5.        }
    but for whatever reason neither of them work. They both print the debug log message even when the animation is not playing. It honestly makes no sense to me as I thought both of these functions only did anything when a state/animation was playing.
     
    Last edited: Apr 29, 2017
  13. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Thinking about this a bit more... this seems to work to at least do a look-up of AnimationClipEditors...

    Code (csharp):
    1.  
    2. public static void GetClipEditor ()
    3. {
    4.     System.Type clipEditor = System.Type.GetType("UnityEditor.AnimationClipEditor,UnityEditor");
    5.  
    6.     if (clipEditor != null)
    7.     {
    8.         UnityEngine.Object[] openEditors = Resources.FindObjectsOfTypeAll(clipEditor);
    9.         if (openEditors.Length > 0)
    10.         {
    11.             Debug.Log (openEditors[0].ToString());
    12.         }
    13.     }
    14. }
    15.  
    For me openEditor.Length is always 0, because the thing I'm hanging this code off of does not preview animation clips and thus there is no clip editor.

    Perhaps you've already tried this... I read through the above replies... but didn't see this exact approach... sorry if this is well worn ground... but it seems @TrickyHandz's suggestion of overriding the Inspector's PreviewGUI for the prefab (that I'm assuming has the animation clips you want) and then running the above reflection should return the open clip editor. For there you should be able to get the clip.

    ////

    Having said that I'm not entirely sure this will solve your tooling problem. Nor I'm I convinced I understand the "design" you're trying to land.

    ... the tool puts animation events on a selected animation at different times depending on things the user has put into my custom serialized object's inspector.

    >> You mean, when previewing an animation clip, from a 3d model (e.g. an imported FBX) a user can select a point in time and add a event?

    I'm trying to make a preview for my animation with the events on it.

    >> What are the requirement of this? Does the user have to play the clip and then see the events fire? Do they have to be able to debug the events?

    Now I know events don't work in the editor so I'm trying to tell when the animation is playing and get the time that the animation is at (is this possible?) to call the functions that my events would normally play at the proper times.

    >> You can definitely do the following:

    - Define the different clips from the imported model
    - Open each clip and preview it in the AnimationEditorWindow (the dopesheet + curves thing)
    - When playing see what time the preview is at

    In addition, Unity maintains .meta files for everything that the editor does. If you select Forced Text as the serialization format you could examine that as a flat file and then see if the currently playing animation clip is at a time where there is an event (according to the .meta file for that clip) and know that an event should play.

    Then perhaps you could use reflection against your own DLL, not Unity's but the build of your game and call the event from there?

    ////

    FWIW, I know how it is to be an intern... most of the time, for me at least, it felt like I was poking around in the dark and no one really cared that I was bumping into walls over and over again. Looking back those are great times, but when you're stuck in the mud it can feel pretty awful.

    Stick with it, communicate your thinking, ask smart questions, don't assume anything, relentlessly attack problems from as many ways as you can imagine, never give up. You'll impress people with effort and passion more than you might realize :).

    Anyways, I'll try to circle back to this over the weekend and see if I can't be some help -- admittedly I'm not a dev, just a designer with some programming "awareness", so my impact will probably be limited.
     
    Last edited: Apr 29, 2017
  14. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @v2-Ton-Studios
    I have gotten access to the animation clip editor with reflection now. I have a timeline where the user can insert clips and each clip will cause an animation event to be placed on the animation for it at the beginning of the clip. I'm using animation curves for my "clips". These clips are going to be used to do something in game and that something will need to be previewable as well(honestly questioning whether some of the things they want are even possible in Unity).

    Yes the user needs to be able to preview and see the same things that the events will cause in the preview. Which is why I'm trying to figure out when the animation is playing in the animation clip editor and then from there figure out the time it is at.

    Also yes I can even see about the time it is at in the animation clip editor, although it doesn't show exact times like the Animation Editor Window. Now the purpose of knowing the time the animation playing is at is to call the same functions that my animation events would otherwise do in game.

    Also for the meta files thing, my team's product is this tool(and the one we have in Unreal which is doing what this one will need to eventually do). We are going to license it to game studios and other companies outside of the company I am interning at. This means we couldn't know where the meta files are at nor the name of every single game dll that our plugin will be in. I think I could figure out the names of the animation meta files based on my custom objects that store a reference to the animation and maybe I could get the directory of the dll. Then maybe I could assume the meta files are in a specific location but I'm not even sure exactly where I would begin on this route. Also while it may get me times where events should play it isn't going to tell me when the animation is playing and what point in time the animation is at. Also the place where my code is getting called from has a reference to the animation so I can just look at that for events and the time they should play in my preview.

    As for the layout of this tool, I have a custom editor window for a timeline and I have a custom inspector for the tool's custom objects that it is creating. When one places an animation clip into my animation clip variable on the custom inspector it stores the selected active object in a variable, opens a new inspector editor window using reflection, sets the active object to my animation clip variable, locks my new inspector editor window, and then sets my selected active object back to my custom object(this is a serialized object asset). In this way I have the custom timeline editor(which is also still very much a work in progress but the place I am interning at wants me to prioritize being able to preview what the tool is doing and then make a dll to see if it works in other projects and all that), as well as two inspectors inspecting different things.
     
  15. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @v2-Ton-Studios
    Hmm, it appears if I do:

    Code (CSharp):
    1. private float CurrentAnimTime=0;
    2.  
    3. private void Update{
    4.    if(AnimCon.GetCurrentAnimatorStateInfo(0).normalizedTime * AnimCon.GetCurrentAnimatorStateInfo(0).length>0){                      
    5.       if( AnimCon.GetCurrentAnimatorStateInfo(0).normalizedTime*AnimCon.    
    6.          GetCurrentAnimatorStateInfo(0).length).Equals(CurrentAnimTime)!=true{
    7.            CurrentAnimTime=AnimCon.GetCurrentAnimatorStateInfo(0).normalizedTime*
    8.                AnimCon.GetCurrentAnimator StateInfo(0).length;
    9.          
    10.               Debug.Log("Animation is playing");
    11.       }
    12.    }
    13. }
    14.  
    It will sometimes get the current time of the animation and it will stop printing debug logs if the animation is paused, however they will still print if the user manually moves the slider thumb for the animation. Also strangely the above code does not work for all of the animations in the test demo project that I am making this tool in. I have tried debugging what the normalized time is for those animations that the above code doesn't seem to work with and for whatever reason it is always 0. As for getting it to not play events if the user is just dragging the slider thumb for the animation's playback perhaps I can test if the CurrentAnimTime has increased by a very tiny amount that would be hard to do with a human dragging it and if it has increased more then that then don't print my debug log(where my debug log is I plan to eventually check if the time is the same time as one of my animation events and call the function that my animation events will call in game if so to try and preview in the editor)
     
  16. v2-Ton-Studios

    v2-Ton-Studios

    Joined:
    Jul 18, 2012
    Posts:
    238
    Does this tool have to live inside Unity?

    Feels like you're trying to do something that should be pretty straight forward, but simply isn't, given the current hooks exposed by Unity.
     
  17. arisonu123

    arisonu123

    Joined:
    Feb 19, 2015
    Posts:
    26
    @v2-Ton-Studios
    Yes, this tool needs to live inside of Unity. It is making serialized objects in Unity and needs to be a plugin that users can eventually just drop into their Unity project. It may also eventually be put on the Unity Asset Store. Besides all of this the place I am working for wants it done inside Unity, just like their tool for Unreal that does the same stuff this Unity tool needs to be able to do , is done inside Unreal. And yes I agree the fact that it needs to be done inside Unity can make it more difficult because all of the stuff I really would like to use is internal so I have to use reflection to try and get to it.
     
  18. SkywardRoy

    SkywardRoy

    Joined:
    Jan 14, 2017
    Posts:
    22
    I was browsing some animations in my project window recently and was very annoyed that the animations don't play automatically. Looking at a solution brought me to this thread so I thought I'd share what I came up with. The class uses reflection to get at the time controls of the selected animation so it may also be useful for anyone with the same question as @arisonu123

    https://github.com/KuroiRoy/UnityAnimationPreviewAutoplay
    Code (CSharp):
    1. using System;
    2. using System.Linq;
    3. using System.Reflection;
    4. using JetBrains.Annotations;
    5. using UnityEditor;
    6. using UnityEngine;
    7. using Object = UnityEngine.Object;
    8.  
    9. namespace EditorScripts {
    10.  
    11. [InitializeOnLoad]
    12. public static class AnimationPreviewPlayer {
    13.  
    14.     private const BindingFlags PRIVATE_FIELD_BINDING_FLAGS = BindingFlags.NonPublic | BindingFlags.Instance | BindingFlags.GetField;
    15.     private const BindingFlags PUBLIC_FIELD_BINDING_FLAGS = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField;
    16.     private const BindingFlags PUBLIC_PROPERTY_BINDING_FLAGS = BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetProperty;
    17.    
    18.     private static Type animationClipEditorType;
    19.     private static Type avatarPreviewType;
    20.     private static Type timeControlType;
    21.  
    22.     private static Object selectedObject;
    23.     private static bool shouldFindTimeControl;
    24.  
    25.     static AnimationPreviewPlayer () {
    26.         animationClipEditorType = Type.GetType("UnityEditor.AnimationClipEditor,UnityEditor");
    27.         avatarPreviewType = Type.GetType("UnityEditor.AvatarPreview,UnityEditor");
    28.         timeControlType = Type.GetType("UnityEditor.TimeControl,UnityEditor");
    29.     }
    30.  
    31.     private static void Update () {
    32.         if (Selection.activeObject != selectedObject) {
    33.             selectedObject = Selection.activeObject;
    34.  
    35.             if (selectedObject is AnimationClip) {
    36.                 shouldFindTimeControl = true;
    37.             }
    38.             else if (selectedObject is GameObject) {
    39.                 var assetPath = AssetDatabase.GetAssetPath(selectedObject);
    40.  
    41.                 if (!string.IsNullOrWhiteSpace(assetPath)) {
    42.                     foreach (var child in AssetDatabase.LoadAllAssetsAtPath(assetPath)) {
    43.                         if (child is AnimationClip) {
    44.                             shouldFindTimeControl = true;
    45.                             break;
    46.                         }
    47.                     }
    48.                 }
    49.             }
    50.  
    51.             return;
    52.         }
    53.  
    54.         if (shouldFindTimeControl) {
    55.             var animationClipEditor = Resources.FindObjectsOfTypeAll(animationClipEditorType).FirstOrDefault();
    56.             if (animationClipEditor == null) return;
    57.  
    58.             var avatarPreview = animationClipEditorType.GetField("m_AvatarPreview", PRIVATE_FIELD_BINDING_FLAGS)?.GetValue(animationClipEditor);
    59.             if (avatarPreview == null) return;
    60.  
    61.             var timeControl = avatarPreviewType.GetField("timeControl", PUBLIC_FIELD_BINDING_FLAGS)?.GetValue(avatarPreview);
    62.             if (timeControl == null) return;
    63.            
    64.             shouldFindTimeControl = false;
    65.  
    66.             var playingProperty = timeControlType.GetProperty("playing", PUBLIC_PROPERTY_BINDING_FLAGS);
    67.             if (playingProperty == null) return;
    68.            
    69.             playingProperty.SetValue(timeControl, true);
    70.         }
    71.     }
    72.  
    73.     [InitializeOnLoadMethod, UsedImplicitly]
    74.     private static void SubscribeToUpdate () {
    75.         EditorApplication.update += Update;
    76.     }
    77.  
    78. }
    79.  
    80. }