Wayback Machinekoobas.hobune.stream
May JUN Jul
Previous capture 14 Next capture
2021 2022 2023
2 captures
13 Jun 22 - 14 Jun 22
sparklines
Close Help
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
  • Asset Store
  • Get Unity

UNITY ACCOUNT

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account
  • Blog
  • Forums
  • Answers
  • Evangelists
  • User Groups
  • Beta Program
  • Advisory Panel

Navigation

  • Home
  • Products
  • Solutions
  • Made with Unity
  • Learning
  • Support & Services
  • Community
    • Blog
    • Forums
    • Answers
    • Evangelists
    • User Groups
    • Beta Program
    • Advisory Panel

Unity account

You need a Unity Account to shop in the Online and Asset Stores, participate in the Unity Community and manage your license portfolio. Login Create account

Language

  • Chinese
  • Spanish
  • Japanese
  • Korean
  • Portuguese
  • Ask a question
  • Spaces
    • Default
    • Help Room
    • META
    • Moderators
    • Topics
    • Questions
    • Users
    • Badges
  • Home /
avatar image
18
Question by Statement · Dec 09, 2010 at 11:15 AM · animationcoroutineanimationevent

How can I wait for an animation to complete?

I have a piece of code that want to wait for an animation to complete.

I realize I can try to time the event with Invoke or WaitForSeconds, but since the animation can vary in length I don't find this method that appealing.

I'd like there to be a WaitForAnimation yield instruction so I can easily play code in sequence.

Something like:

// Warning: Code does not work. 
//          Don't use in your project.
function Start()
{
    yield WaitForAnimation( animation.PlayQueued( "Intro" ) );
    Debug.Log("Animation is complete");
}

or even shorter like

// Warning: Code does not work. 
//          Don't use in your project.
function Start()
{
    yield animation.PlayQueued( "Intro" );
    Debug.Log("Animation is complete");
}

Are there nice options to synchronize animation completion? (Is there a global list of methods that work with coroutines?)

Comment
Add comment
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users

14 Replies

· Add your reply
  • Sort: 
avatar image
23
Best Answer

Answer by Statement · Dec 09, 2010 at 03:22 PM

Here is a solution I came up with that waits for the animation to finish. It doesn't work too well in case you have multiple animations queued up from any other source, but as long as you're calling the animations from one single place, this works.

Basically you need to do two things for this solution to wotk;

  1. Start the animation.
  2. Wait for the animation to finish before you play next animation.

An example of how this could be done is:

animation.PlayQueued( "Something" );
yield WaitForAnimation( animation );

And the definition for WaitForAnimation follows:

C#:

private IEnumerator WaitForAnimation ( Animation animation )
{
    do
    {
        yield return null;
    } while ( animation.isPlaying );
}

JS:

function WaitForAnimation ( Animation animation )
{
    yield; while ( animation.isPlaying ) yield;
}

The do-while loop came from experimentation that the animation.isPlaying return false the same frame you call PlayQueued for some reason.

With a little tinkering you can create a extension method for animation that simplifies this such as:

public static class AnimationExtensions { public static IEnumerator WhilePlaying( this Animation animation ) { do { yield return null; } while ( animation.isPlaying ); }

 public static IEnumerator WhilePlaying( this Animation animation, 
                                               string animationName )
 {
     animation.PlayQueued(animationName);
     yield return animation.WhilePlaying();
 }

}

Finally you can easily use this in code:

IEnumerator Start()
{
    yield return animation.WhilePlaying("Something");
}
Comment
Add comment · Show 2 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image techmage · Jul 25, 2012 at 12:58 AM 0
Share

I can't get this to work at all,

if I do a yield return and then specify another IENumerator function, that IEnumerator function seems to just not run at all.

Look here

[QUOTE] IEnumerator WhilePlaying( Animation animation, string animationName ) { Debug.Log("RUNNING WHILE PLAYING"); do { Debug.Log("YIELD WHILE PLAYING"); yield return null; } while ( animation.IsPlaying(animationName) ); }

IEnumerator PlayOpen() { yield return new WaitForSeconds(waitTillPlay); ballGroupAnimation.Play("open"); yield return WhilePlaying(ballGroupAnimation, "open"); ballGroupAnimation.Play("idleOpen");

//if(plusSignShared$$anonymous$$aterial) StartCoroutine( guiControl.Tween$$anonymous$$aterialColor( Utility.ChangeColorAlpha(plusSignShared$$anonymous$$aterial.color,1), 1, plusSignShared$$anonymous$$aterial) );

} } [/QUOTE]

the two Debug.Log's Debug.Log("RUNNING WHILE PLAYING"); Debug.Log("YIELD WHILE PLAYING");

they just never output anything. The WhilePlaying function just never gets run...

How do I make this work? It seems really useful.

avatar image Statement · Jul 25, 2012 at 10:42 AM 2
Share

You need to call

 yield return StartCoroutine(WhilePlaying(ballGroupAnimation, "open"));
avatar image
4
Best Answer

Answer by loopyllama · Mar 12, 2011 at 03:03 AM

You could try checking to see when the current time of your animation is larger than the length of the animation. The docs say that the animation time goes to infinity because it loops the animation over and over. I am assuming that animation time stops when you pause an animtion. I have not tested this but it would go something like:

while (AnimationState.time >= AnimationState.length)
{
    yield return null;
}
return bAnimFinished;

or the non-coroutine way

if (AnimationState.time >= AnimationState.length)
{
    // animation finished...
}
Comment
Add comment · Show 4 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Statement · Mar 12, 2011 at 03:11 AM 1
Share

The problem is an animation may be stopped at an arbitrary point in time. Relying on the duration ins$$anonymous$$d of check it's actually playing doesn't cover this scenario. Trying to add extra support to handle the case quickly lead to messy code. If you are absolutely certain no other code will interfere with method, it's clean way of waiting for it with $$anonymous$$imal code. A flaw in the current AA is however that another animation might be started so the coroutine waits for the second animation to complete.

avatar image loopyllama · Mar 12, 2011 at 05:30 AM 1
Share

if (AnimationState.time >= AnimationState.length)

:)

avatar image Statement · Mar 16, 2011 at 02:02 PM 0
Share

Now that's something! Thanks a ton I will consider trying this out tonight. Put it in an answer and I'll vote it up.

avatar image loopyllama · Mar 17, 2011 at 06:06 AM 0
Share

added comment in answer form!

avatar image
19

Answer by s_guy · May 10, 2013 at 05:18 AM

Even though it's older, this Q&A tends to rise to the top in searches. But fortunately, I dug deeper before going to work. This solution may not have been available with previous builds, but nowadays, you can use the built-in animation events system.

Comment
Add comment · Show 5 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image MartinLyne · Aug 04, 2013 at 05:23 PM 0
Share

Thanks for the pointer, I don't have enough repto upvote sadly.

avatar image Jesdisciple · Mar 25, 2014 at 03:51 PM 0
Share

I wasn't able to find how to use this with the legacy system, but see also http://docs.unity3d.com/Documentation/ScriptReference/AnimationClip.AddEvent.html

avatar image deviantony · Feb 17, 2016 at 08:57 AM 0
Share

This should now be referred as the best answer !

avatar image mr_blahblah · Jun 26, 2016 at 06:51 AM 0
Share

This is a terrible idea - animation events will not fire if a frame is skipped, making them unreliable. Vote this down.

avatar image lclemens · Apr 23, 2020 at 07:24 PM 0
Share

That link to the built-in animation events system is 404. $$anonymous$$aybe this one? https://docs.unity3d.com/$$anonymous$$anual/script-AnimationWindowEvent.html

avatar image
2

Answer by Kourosh · Dec 09, 2010 at 12:56 PM

   function Update() {
        if (!animation.IsPlaying("YourAnimation"))
            print("Animation Done");
    }
Comment
Add comment · Show 3 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image Statement · Dec 09, 2010 at 01:07 PM 1
Share

Actually, when I run that code (except that my animation reference is on another object), it reports Animation Done the first frame it runs. Then it doesn't say anything for a while, and after it is done spams out Animation Done. Also, it doesn't really help since I want to play code in sequence, so that I can play one animation, then do something else, then play another animation, then do something else etc.

avatar image Kourosh · Dec 09, 2010 at 02:08 PM 0
Share

Ok, I guess the first time printing "done" is because your this code is run before you animation starts. To apply this to your case you need to know which interactions happen to trigger an animation or check whether is stopped playing. If you clarify more I can help you write the code.

avatar image Statement · Dec 09, 2010 at 03:11 PM 0
Share

I start the animation in Start. It appears that the animation.IsPlaying returns false during the same frame.

avatar image
2

Answer by hideouswill · Mar 01, 2012 at 04:28 AM

A more general solution is to set up a notification at some arbitrary time in the animation cycle, where the end of the cycle is a special case. I've created a class that holds an animation clip, and fires an event at a specified time in the animation cycle. I will present two equivalent implementations below, one that uses class AnimationEvent (which is sort of the native Unity way), and one that uses a Coroutine to measure the animation time against the notification time.

Personally, I prefer using the second technique. Why? Unity uses its messaging system (e.g. SendMessage()) to fire the AnimationEvent. The message is bound only by the name of the method, and the message is sent to all script components on the GameObject. If some other script on your GameObject has a method with the same name, it too will receive the message, which is almost certainly a bug.

You can mitigate the issue by creating a mangled name for your method, but that gets really ugly--and it doesn't even completely solve the problem! Consider if you have two instances of the script component on a single GameObject. (You might do this if you have two different clips that you want to monitor.) Now both components receive the message, regardless of the method name. I've addressed this by adding the script's object ID as a parameter on the AnimationEvent, and then checking the value in the message handler. It works, but it does add complexity.

As for sending the notification to the outside world, you could use SendMessage(), but I prefer using a .NET event. It has several advantages: you get better performance; you avoid the binding issue described above; it is architecturally more elegant, as it insulates the class from being concerned with who's listening for the notification.

So, the prose are nearly complete. :) To use this, add it as a component to the GameObject that gets animated. You can set the clip at design time or at run-time. I've expressed the time to fire the notification as animation frames, but you could just as well use seconds; if the frame is set to zero, I take that to mean at the end of the clip. You attach a handler to the NotifyFrameReached event, and voila! You have notification that you've reached the specified frame!

CAVEATS:

  • In the Message-based implementation, I don't provide means to remove the AnimationEvent; if you were to set a second clip at run-time, the event on the old clip would continue to fire each time it runs.

  • When you add a handler to a .NET event, you should make sure to remove it once you no longer need to handle the event; else you will leak memory.

And without further ado, my preferred Coroutine-based implementation:

using UnityEngine;

using System;

using System.Collections;

public class NotifyingAnimControl_CoroutineBased : MonoBehaviour

{

 //--------------------------------------------------------------------------
 public float  notificationFrame;

 //--------------------------------------------------------------------------
 [SerializeField]
 AnimationClip animClip;

 //--------------------------------------------------------------------------
 public event EventHandler NotifyFrameReached;

 //--------------------------------------------------------------------------
 protected void RaiseNotifyFrameReached()
 {
     var handler = NotifyFrameReached;

     if( null == handler ) return;

     handler( this, EventArgs.Empty );
 }

 //--------------------------------------------------------------------------
 public void SetClip( AnimationClip clip )
 {
     animClip = clip;

     // Note--you can have different wrap modes if you want...
     animClip.wrapMode = WrapMode.Once;

     ScheduleNotification();
 }

 //--------------------------------------------------------------------------
 public void PlayAnimation()
 {
     animation.CrossFade( animClip.name );

     // NOTE: It is critical to start this *after* starting the clip, else
     // the CheckEventTime fails on it's first iteration!
     StartCoroutine( CheckEventTime() );
 }

 //--------------------------------------------------------------------------
 void Start()
 {
     if( null != animClip )
     {
         SetClip( animClip );
     }
 }

 //--------------------------------------------------------------------------
 float EventTime { get; set; }

 //--------------------------------------------------------------------------
 AnimationState AnimState { get { return animation[ animClip.name ]; } }

 //--------------------------------------------------------------------------
 protected void ScheduleNotification()
 {
     EventTime = (0 == notificationFrame)
               ? animClip.length
               : notificationFrame / animClip.frameRate;
 }

 //--------------------------------------------------------------------------
 // The virtue of using this technique (rather than an AnimationEvent) is
 // that AnimationEvent fires its signal using SendMessage(), which winds
 // up going to all components on the GameObject.  If the GameObject has
 // more than one of these components on it, they *all* receive the message.
 // You then need a filter to figure out if the component is the intended
 // receiver, and in the mean time, you are getting superfluous events.
 IEnumerator CheckEventTime()
 {
     while( AnimState.time < EventTime  &&
            animation.IsPlaying( animClip.name ) )
     {
         yield return new WaitForEndOfFrame();
     }

     RaiseNotifyFrameReached();
 }

}

And here's the Messaged-based implementation:

using UnityEngine; using System;

public class NotifiyingAnimControl_MessageBased : MonoBehaviour

{

    //--------------------------------------------------------------------------
 public float notificationFrame;
 
 //--------------------------------------------------------------------------
     [SerializeField]
 AnimationClip animClip;
 
 //--------------------------------------------------------------------------
 public event EventHandler NotifyFrameReached;
 
 //--------------------------------------------------------------------------
 protected void RaiseNotifyFrameReached()
 {
     var eventDelegate = NotifyFrameReached;
     if( null == eventDelegate ) return;
     
     eventDelegate( this, EventArgs.Empty );
 }

 //--------------------------------------------------------------------------
 public void SetClip( AnimationClip clip )
 {
     animClip = clip;

     // Note--you can have different wrap modes if you want...
     animClip.wrapMode = WrapMode.Once;

     ScheduleNotification();
 }

 //--------------------------------------------------------------------------
 void Start()
 {
     if( null != animClip )
     {
         SetClip( animClip );
     }
 }

 //--------------------------------------------------------------------------
 protected void ScheduleNotification()
 {
     var animEvent = new AnimationEvent();

             animEvent.time = (0 == notificationFrame)
                                      ? animClip.length
                                      : notificationFrame / animClip.frameRate;

     animEvent.functionName = "DoGenericOneShotNotification";
     animEvent.intParameter = GetInstanceID();
     
     animClip.AddEvent( animEvent );
 }

 //--------------------------------------------------------------------------
 public void PlayAnimation()
 {
     animation.CrossFade( animClip.name );
 }
 
 //--------------------------------------------------------------------------
 void DoGenericOneShotNotification(AnimationEvent animEvent)
 {
             if( animEvent.intParameter != GetInstanceID() ) return;
     
     RaiseNotifyFrameReached();
 }

}

Comment
Add comment · Show 1 · Share
10 |3000 characters needed characters left characters exceeded
▼
  • Viewable by all users
  • Viewable by moderators
  • Viewable by moderators and the original poster
  • Advanced visibility
Viewable by all users
avatar image mr_blahblah · Jun 26, 2016 at 06:52 AM 1
Share

If that works - great, but what Unity needs is a built-in function 'IsComplete' or something similar. I've spent a day digging into this issue - a day that means I might not meet a deadline - and there's many threads with arcane solutions to this problem. $$anonymous$$ost of them don't work.

At this point, I regret using Unity and am considering a switch to Unreal.

  • 1
  • 2
  • 3
  • ›

Your answer

Hint: You can notify a user about this post by typing @username

Up to 2 attachments (including images) can be used with a maximum of 524.3 kB each and 1.0 MB total.

Follow this Question

Answers Answers and Comments

17 People are following this question.

avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image avatar image

Related Questions

Animation not running 1 Answer

Staggered Animations - Do I use Pre-fabs ? 1 Answer

is it possible to save over animation clips and keep events? 1 Answer

Is it possible to turn off/suppress event calls in an animation at runtime? 2 Answers

Animation control using Events and .enable 1 Answer


Enterprise
Social Q&A

Social
Subscribe on YouTube social-youtube Follow on LinkedIn social-linkedin Follow on Twitter social-twitter Follow on Facebook social-facebook Follow on Instagram social-instagram

Footer

  • Purchase
    • Products
    • Subscription
    • Asset Store
    • Unity Gear
    • Resellers
  • Education
    • Students
    • Educators
    • Certification
    • Learn
    • Center of Excellence
  • Download
    • Unity
    • Beta Program
  • Unity Labs
    • Labs
    • Publications
  • Resources
    • Learn platform
    • Community
    • Documentation
    • Unity QA
    • FAQ
    • Services Status
    • Connect
  • About Unity
    • About Us
    • Blog
    • Events
    • Careers
    • Contact
    • Press
    • Partners
    • Affiliates
    • Security
Copyright © 2020 Unity Technologies
  • Legal
  • Privacy Policy
  • Cookies
  • Do Not Sell My Personal Information
  • Cookies Settings
"Unity", Unity logos, and other Unity trademarks are trademarks or registered trademarks of Unity Technologies or its affiliates in the U.S. and elsewhere (more info here). Other names or brands are trademarks of their respective owners.
  • Anonymous
  • Sign in
  • Create
  • Ask a question
  • Spaces
  • Default
  • Help Room
  • META
  • Moderators
  • Explore
  • Topics
  • Questions
  • Users
  • Badges