Search Unity

Code Example: How To Execute Logic At The Beginning And End Of A Clip

Discussion in 'Timeline' started by f0ff886f, Apr 14, 2019.

  1. f0ff886f

    f0ff886f

    Joined:
    Nov 1, 2015
    Posts:
    201
    Inspired by this post: https://forum.unity.com/threads/code-example-how-to-detect-the-end-of-the-playable-clip.659617/, and enabled by @drihpee to execute this, I wanted to share some simple code that lets you do two things:

    When Timeline enters a Behaviour (clip, in my mind), run some code, and when it exits (this is possible again by @drihpee in the above thread), run other code.

    I wanted to enable some clips where for the course of an animation, I can disable player input, for example, and be able to do that straight from a timeline. Or, trigger a script function just on the start to initiate some other script.

    This is the entire set of code you need to create a custom track that handles these clips, so it looks something like this, just simple blocks that will set some state for their duration:
    upload_2019-4-14_14-8-11.png

    I hope this helps someone else who just wants to do basic things with Timelines. If you want to learn how to do more with blending, I recommend the Subtitle Example available here: https://forum.unity.com/threads/tim...s-of-custom-script-clips.477017/#post-3113560

    If any of the information is incorrect please let me know so I can update this post.

    SimpleBehaviour.cs implements your actual runtime logic in the context of the clip on the timeline:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.Playables;
    4.  
    5. [Serializable]
    6. public class SimpleBehaviour : PlayableBehaviour
    7. {
    8.     public override void OnBehaviourPlay(Playable playable, FrameData info)
    9.     {
    10.         // Only execute in Play mode
    11.         if (Application.isPlaying)
    12.         {
    13.             // Execute your starting logic here, calling into a singleton for example
    14.             Debug.Log("Clip started!")
    15.         }
    16.     }
    17.  
    18.     // source: https://forum.unity.com/threads/code-example-how-to-detect-the-end-of-the-playable-clip.659617/
    19.     public override void OnBehaviourPause(Playable playable, FrameData info)
    20.     {
    21.         // Only execute in Play mode
    22.         if (Application.isPlaying)
    23.         {
    24.             var duration = playable.GetDuration();
    25.             var time = playable.GetTime();
    26.             var count = time + info.deltaTime;
    27.            
    28.             if ((info.effectivePlayState == PlayState.Paused && count > duration) || Mathf.Approximately((float)time, (float)duration))
    29.             {
    30.                 // Execute your finishing logic here:
    31.                 Debug.Log("Clip done!")
    32.             }
    33.             return;
    34.         }
    35.     }
    36. }
    SimpleClip.cs takes care of storing the clip as a PlayableAsset:
    Code (CSharp):
    1. using System;
    2. using UnityEngine;
    3. using UnityEngine.Playables;
    4. using UnityEngine.Timeline;
    5.  
    6. [Serializable]
    7. public class SimpleClip : PlayableAsset, ITimelineClipAsset
    8. {
    9.     // Create the runtime version of the clip, by creating a copy of the template
    10.     public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    11.     {
    12.         return ScriptPlayable<SimpleBehaviour>.Create(graph);
    13.     }
    14.  
    15.     // Make sure we disable all blending since we aren't handling that in the mixer
    16.     public ClipCaps clipCaps
    17.     {
    18.         get { return ClipCaps.None; }
    19.     }
    20. }
    21.  
    SimpleMixerBehaviour.cs is required since we are implementing a custom track, maybe there is a way around not needing a mixer but I haven't found how:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3.  
    4. public class SimpleMixerBehaviour : PlayableBehaviour
    5. {
    6.     // Override the ProcessFrame because we want to have our own color coded tracks
    7.     // to keep things in the Editor visually clean
    8.     public override void ProcessFrame(Playable playable, FrameData info, object playerData) { }
    9. }
    10.  
    SimpleTrack.cs instantiates the mixer at runtime and specifies what kind of clips it handles:
    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4.  
    5. [TrackColor(1.0f, 0.0f, 0.0f)]
    6. [TrackClipType(typeof(SimpleClip))]
    7. // No track binding since we're executing general logic during our scene
    8. public class SimpleTrack : TrackAsset
    9. {
    10.     public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    11.     {
    12.         // This Mixer won't do anything, but I want to use our own track type instead of a PlayableTrack to keep things simple
    13.         return ScriptPlayable<SimpleMixerBehaviour>.Create(graph, inputCount);
    14.     }
    15. }
    16.  
     
    Last edited: Apr 14, 2019
  2. Kabab_mey_haddi

    Kabab_mey_haddi

    Joined:
    Jul 1, 2018
    Posts:
    1
    any short video showing which script to add and where?? thx in advance
     
  3. PudgeCake

    PudgeCake

    Joined:
    Aug 8, 2019
    Posts:
    4
    Thank you So Much for this!
    This makes some things I was doing much cleaner and easier. I'll definitely be expanding on this approach!

    For Kabab and anyone else who doesn't understand how to use this:

    SimpleBehaviour - this script is where your runtime behaviour goes. Whatever the in-game action you want to perform is, put it here.

    SimpleTrack - with this script added to your project you can right-click in any existing timeline and add a "SimpleTrack" instance.
    The simple track can host instances of the SimpleClip.
    SimpleClip - the code provided doesn't do much, it just starts and pauses the SimpleBehaviour BUT you can add more to it, such as variables, e.g.:
    Code (CSharp):
    1. public ExposedReference<GameObject> Target;
    2. public int someValue;
    If you add this to SimpleClip then your clip can have a reference to a gameobject assigned to it, and you can type an int in that field in the inspector.

    But to use these variables you have to get them into the SimpleBehaviour, so lets suppose you add a method called SetMyVariables to the SimpleBehaviour.
    Then you can do this inside the SimpleClip CreatePlayable method:
    Code (CSharp):
    1.     public ExposedReference<GameObject> Target;
    2.  
    3.     public int someValue;
    4.     public override Playable CreatePlayable(PlayableGraph graph, GameObject go)
    5.     {
    6.         ScriptPlayable<SimpleBehaviour> playable = ScriptPlayable<SimpleBehaviour>.Create(graph);
    7.         SimpleBehaviour playableBehaviour = playable.GetBehaviour();
    8.         //now you can pass your varibles into the SimpleBehaviour
    9.         playableBehaviour.SetMyVariables(Target.Resolve(graph.GetResolver()), someValue);
    10.         return playable;
    11.     }
    And boom, now you can assign gameobjects and variables in the Clip in the Timeline, and when that clip is run it will create the SimpleBehaviour you wrote and do whatever it is you want it to do.

    The names "Simple" are just an example.
    I'm creating a BaseClip and inheriting from it so that my SimpleTrack (renamed to GameLogicTrack) can contain many clip types that each run their own behaviours.