Search Unity

How to optimize AnimationTrack.CreateClip()

Discussion in 'Timeline' started by Lafsody_UrniqueStudio, Sep 11, 2019.

  1. Lafsody_UrniqueStudio

    Lafsody_UrniqueStudio

    Joined:
    Mar 2, 2013
    Posts:
    11
    We are currently creating a game that receives input and creates Unity Timeline at runtime.
    After we recognized that performance drops a lot. So we check the profiler and found that one of the problems is AnimationTrack.CreateClip().
    AnimationTrack.CreateClip (that is only one line of code) cause 23.92 ms is very huge.
    image.png

    Other problem we already know how to deal with it. But about Timeline we don't know much.
    In this case, How could we deal with it? We know that Timeline doesn't design for runtime much.
    Is there a way like pooling or pre-create? Or create a custom track that do exactly the same thing but with least features can help?
     
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You can pre-create using PlayableDirector.RebuildPlayableGraph(). That will let you prepare the timeline (i.e. compile the clips) when it's convenient and avoid those calls without actually playing the graph.
     
  3. Lafsody_UrniqueStudio

    Lafsody_UrniqueStudio

    Joined:
    Mar 2, 2013
    Posts:
    11
    What if we want to modify Timeline at runtime? We create Clip at that instant.

    Example:
    At runtime, player can control time on the timeline through our UI.
    We have a timeline that show player walking forward.

    AnimationTrack on Timeline
    (0-30 sec) |-----WalkingAnimation--------|

    Player rewind time to sec 15 and press spacebar to jump.
    We create a clip in AnimationTrack that bind with the jumping animation clip (that length = 5 sec) insert and blend it on track.

    (According to my understanding, we need to RebuildPlayableGraph here one time. So player can control time and see the animation show correctly.)

    AnimationTrack on Timeline will become like this.
    (0-15 sec) |-----WalkingAnimation--------|
    (15-20 sec) |-----JumpingAnimation-------|
    (20-30 sec) |-----WalkingAnimation--------|
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    What might work better than modifying an existing timeline is to play two timelines. The second timeline is a single animation track with a clip from 15-20 that possibly has some ease in and out.

    Timelines can stack - meaning starting a second timeline can superseed the first, but both are running (it's like dynamically adding an animation layer to a state machine). The second will blend to and from the original if the clip has easing.

    The second timeline could be dynamically created, or pre-baked. Since it's a single clip it will be cheaper. And you wouldn't need to recompile the original timeline, which is where most of the cost is.

    Modifying timelines can be expensive because compiling is an all or nothing operation, there isn't any partial compile.

    I don't know all the constraints of the problem you are trying to solve, but hopefully that helps, at least in part.
     
  5. Lafsody_UrniqueStudio

    Lafsody_UrniqueStudio

    Joined:
    Mar 2, 2013
    Posts:
    11
    Wow! That solution is very smart!
    A little more complicated but worth to try.
    Thank you very much.

    I think, in our game, modifying timeline on realtime is inevitable. So we need to do it as cheapest as possible.
    Creating controls track is look cheaper than AnimationTrack. So, it should be solved.

    I know it's a little hard to help without context.
    But it's a little hard to explain our game without a clip or demo (I can't post it in public forum). But I will try my best.

    Our core concept is about time. Players can rewind and look through the future as their wish.
    The goal is to control character to the exit. Obstacles are some puzzle and enemies.
    Players can move their character and interact with something e.g. switch to open the door.
    So we thought the Timeline is fit perfectly well. If we can control the time on timeline we can control time.
    We just only need to modify timeline at runtime.
    In the Timeline we have already create Track Group of player and enemy.
    Track Group contains
    - Transform Track that control the transform using Infinite clip
    - Animation Track that control animation
    timeline.png

    This is a mockup used to explain.
    Mock.png
    We have grids. Player character. Enemies that move go and back from A to B. Exit point.
    The slider on UI is mapping to time and also time on Timeline. The scissor button (Trim button) that can trim (or delete) action that happened at later than the present time.
    Time only moves when receiving action from the player or slide the slider.
    For example scenario.
    If I slide the slider I can control time and see what it happens.
    Because we still not add any input. We can see the character playing idle animation and don't move anywhere. Enemy moves back and forth from point A to B.
    FirstScenario.png

    I touch the exit grid to command the character to move to the exit.
    The script will calculate the shortest path and calculate what happened.
    Then, the scripts will process and modify a timeline base on it.
    Player character will try to move to the exit but was seen by the enemy and then was hit and die.
    scenario2.png

    Then, the player needs to slide the slider back to the beginning to rewind the time and press scissor button to cancel move action.
    After that, the player slides the slider forward to wait till the enemy move pass to grid B then click to the exit grid.
    This time the player character will move pass enemy back to the exit safely.
    scenario3.png

    So I think it inevitably to modify Timeline at runtime.
     
  6. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    That is a very interesting (and fun!) problem.

    The best I can give is some observations and suggestions.

    It seems separating timelines for what changes and what doesn't would be ideal. i.e. try to isolate and rebuild only what is needed. There is a big cost associated with rebuilding a timeline asset. i.e. if the enemies timelines don't change, have them separated and play them in parallel. That would be good way to reduce the cost.

    If it's still too expensive and you are only using animation tracks, you could consider skipping timeline and creating the playable graph directly using simple animation https://blogs.unity3d.com/2017/11/28/introducing-the-simple-animation-component/. That would let you skip the 'create animation tracks and compile' step, and just generate the resulting animation graph directly. There is pros and cons to that approach, but it would be the most efficient.

    It seems there are two separate, but related issues - the root transform of the characters and the animations. Since the character animation is using baked clips, there isn't really an issue. You can compose new tracks and timelines with those at runtime.

    Root transform might be a problem here. It appears you are generating 'infinite' animation tracks to control the position/rotation of the generated timeline. There is a limitation in the player where you can't create animation clips at runtime that work with timeline. So if you are trying to generate infinite tracks at runtime, you might run into a problem. AnimationTracks only support non-legacy clips, but only legacy clips can have curves set on them at runtime.

    If you are just changing offsets to those infinite curves, then it's not an issue. Alternatives would be a separate custom track (something like the TransformTween track in the default playables package), or overriding OnAnimatorMove() to modify the root motion dynamically. Or just don't use RootMotion on your clips and calculate the transform outside of timeline/playables completely (say, in a LateUpdate() script).

    Might not be an issue, but I wanted to give fair warning and not see you go down a path that won't work in a player.

    Thanks for sharing the use case though - like I said, that looks like a fun challenge. Other alternatives is to pre-generate the possible timelines in editor and pick which one(s) should be played. Custom animation tracks that use AnimationJobs are also a possibility.
     
  7. Lafsody_UrniqueStudio

    Lafsody_UrniqueStudio

    Joined:
    Mar 2, 2013
    Posts:
    11
    It has a lot of detail. I really appreciate it. It maybe took some time for me to deal with.
    I will come back later with some update.

    P.S. Sorry for a little late reply. I have to deal with another problem. It's about audio on the Timeline.
    I know that Timeline only plays sounds when playing. But I try to do some workaround. Already fine I think.
     
  8. Lafsody_UrniqueStudio

    Lafsody_UrniqueStudio

    Joined:
    Mar 2, 2013
    Posts:
    11
    For some update:
    We already try the pooling method. I can call it a success. "Create Clip" is vanishing from the profiler. And the game can run smooth.

    We create clips, return it to the pool and reuse it.
    like this
    Code (CSharp):
    1. protected virtual TimelineClip CreateTimelineClipFromSnapShot(AnimationTrack track, AnimationLog log)
    2.     {
    3.         ActionToClipMap clipMap = mapper.GetClipDetail(log.actionType);
    4.  
    5.         if (clipMap.clip == null)
    6.             return null;
    7.    
    8.         TimelineClip timelineClip = null;
    9.         if(clipCaches.Count > 0 && cacheIndex < clipCaches.Count)
    10.         {
    11.             timelineClip = clipCaches[cacheIndex];
    12.             var animPlayableAsset = ((AnimationPlayableAsset)timelineClip.asset);
    13.             animPlayableAsset.clip = clipMap.clip;
    14.             timelineClip.easeInDuration = 0;
    15.             timelineClip.easeOutDuration = 0;
    16.             timelineClip.blendInDuration = 0;
    17.             timelineClip.blendOutDuration = 0;
    18.             timelineClip.start = 0;
    19.             timelineClip.timeScale = 1;
    20.         }
    21.         else
    22.         {
    23.             timelineClip = track.CreateClip(clipMap.clip);
    24.             clipCaches.Add(timelineClip);
    25.             // Debug.Log("Create new clip for " + clip.name);
    26.         }
    27.         ++cacheIndex;
    28.  
    29.         if(config.blend)
    30.             timelineClip.easeInDuration = config.easeInDuration;
    31.        
    32.         timelineClip.start = RenderUtilities.GetGameTimeFromTimeslot(log.startTimeslot);
    33.         timelineClip.duration = RenderUtilities.GetGameTimeFromTimeslot(log.duration);
    34.         timelineClip.displayName = clipMap.clip.name;
    35.         timelineClip.timeScale = clipMap.speedMultiplier == 0 ? 1 : clipMap.speedMultiplier;
    36.  
    37.         return timelineClip;
    38.     }
    It's already fine for our gameplay video now. We will use other methods in the late process later.

    P.S. After optimized, sometimes it happens some weird behavior on blending.
    The leg of the character stretches out like a monster in some movement.
    The tail and the back part of the cat stretch out like a giraffe in some walk movement.
    We already call rebuildGraph whenever we modify the timeline. So we still can't figure it out.
    I guess we still forgot to clear something when we return the clip back to pool.
     
    Thr3dee likes this.