Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Creating a Marker Track & Markers that work without Track Binding

Discussion in 'Timeline' started by arvzg, Sep 20, 2020.

  1. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    619
    Is it possible to create a MarkerTrack that works without requiring a Track Binding? Let's call it EnemySpawnMarkerTrack

    It would work alongside a custom Marker class EnemySpawnMarker. Ideally I would want some kind of method to be called in EnemySpawnMarker that is called when the timeline reaches the marker. There doesn't seem to be any such functionality in the existing API, so I imagine it would have to be implemented in some other way

    I'm aware you can just place markers on the Markers section of the Timeline and that will automatically use the gameobject that the Director is on, but I specifically want to use Markers on a custom Marker Track

    Markers seem to be designed to be used with INotification and INotificationReceiver, and I am familiar with how those work. but this requires a Track Binding
     
    Last edited: Sep 20, 2020
  2. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Definitely possible.

    You can always override CreateTrackMixer() in your custom track to create a script playable that can handle markers however you like.

    A couple of caveats/hints/etc -
    • Use the playable at the root of the graph to read time (playable.GetGraph().GetRoot(0)). The mixer time will appear to be the same, but scrubbing, looping etc.. can get them out of sync.
    • If you are spawning in the editor, make sure to use gameObject.hideFlags with HideAndDontSave, and clean up spawns in your OnPlayableDestroy() callbacks.
    • Have a look at TimeNotificationBehaviour.cs in the timeline package - it's the script playable used by signals and INotification.
    • If you have a custom mixer already, then you'll likely need to add the functionality to that. Creating multiple playables in CreateTrackMixer is tricky and error prone.
     
    arvzg likes this.
  3. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    619
    @seant_unity I've noticed that
    CreateTrackMixer(..)
    and
    CreatePlayable(..)
    on
    TrackAsset
    never actually gets called if the Track doesn't have any clips. On a Marker Track, obviously we won't have any clips in them. How should I get around this?
     
  4. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    Ah...right.... I forgot that part. The only workaround to that is to add 'animation' to the track so that it requires a track mixer.

    The simplest way to do that is just put a fake animation curve on the track. It doesn't necessary need to do anything, CreateTrackMixer will be called if there is _any_ animation on the track. Yes - this is a horrible workaround. The drawback is that the inline curve editor will be available with nothing on it (the curve needs to be valid to be shown).

    Code (CSharp):
    1. #if UNITY_EDITOR
    2.  
    3. [UnityEditor.Timeline.CustomTimelineEditor(typeof(FakeTrackCurves))]
    4. public class FakeTrackEditor : UnityEditor.Timeline.TrackEditor
    5. {
    6.     public override void OnCreate(TrackAsset track, TrackAsset copiedFrom)
    7.     {
    8.         track.CreateCurves("FakeCurves");
    9.         track.curves.SetCurve(string.Empty, typeof(GameObject), "m_FakeCurve", AnimationCurve.Linear(0,1,1,1));
    10.         base.OnCreate(track, copiedFrom);
    11.     }
    12. }
    13.  
    14. #endif
    15.  
    16. [TrackBindingType(typeof(GameObject))]
    17. public class FakeTrackCurves : TrackAsset
    18. {
    19.     public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    20.     {
    21.         Debug.Log("TrackMixer");
    22.         return base.CreateTrackMixer(graph, go, inputCount);
    23.     }
    24. }
     
    arvzg likes this.
  5. theRealWinxAlex

    theRealWinxAlex

    Joined:
    May 2, 2020
    Posts:
    73
    1) Don't want to use MonoBehaviour
    2) Custom Marker Track with Custom Markers

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3.  
    4. class ReceiverExample : INotificationReceiver
    5. {
    6.  
    7.     public void OnNotify(Playable origin, INotification notification, object context)
    8.     {
    9.         if (notification != null)
    10.         {
    11.             //double time = origin.IsValid() ? origin.GetTime() : 0.0;
    12.             double time = ((SimpleMarker) notification).time;
    13.             Debug.LogFormat("Received notification of type {0} at time {1}", notification.GetType(), time);
    14.         }
    15.     }
    16. }



    Code (CSharp):
    1. using System.Collections.Generic;
    2. using System.Linq;
    3. using UnityEditor.Timeline;
    4. using UnityEngine;
    5. using UnityEngine.Playables;
    6. using UnityEngine.Timeline;
    7.  
    8. #if UNITY_EDITOR
    9.  
    10. [CustomTimelineEditor(typeof(SignalTrackEx))]
    11. public class FakeTrackEditor : TrackEditor
    12. {
    13.     public override void OnCreate(TrackAsset track, TrackAsset copiedFrom)
    14.     {
    15.         track.CreateCurves("FakeCurves");
    16.         track.curves.SetCurve(string.Empty, typeof(GameObject), "m_FakeCurve", AnimationCurve.Linear(0, 1, 1, 1));
    17.         base.OnCreate(track, copiedFrom);
    18.     }
    19. }
    20.  
    21. #endif
    22.  
    23.  
    24. public class SignalTrackEx : MarkerTrack
    25. {
    26.  
    27.  
    28.  
    29.     ReceiverExample m_Receiver;
    30.  
    31.     public override IEnumerable<PlayableBinding> outputs
    32.     {
    33.         get
    34.         {
    35.             var playableBinding = ScriptPlayableBinding.Create(name, null, typeof(GameObject));
    36.  
    37.  
    38.             //return this == timelineAsset.markerTrack ? new List<PlayableBinding> {playableBinding} : base.outputs;
    39.             // return base.outputs;
    40.             return new List<PlayableBinding> {playableBinding};
    41.         }
    42.     }
    43.  
    44.  
    45.  
    46.     public override Playable CreateTrackMixer(PlayableGraph graph, GameObject go, int inputCount)
    47.     {
    48.        
    49.        
    50.         if (Application.isPlaying)
    51.         {
    52.             PlayableOutput playableOutput = graph.GetOutput(0);
    53.  
    54.             if (playableOutput.IsOutputValid())
    55.             {
    56.                 ScriptPlayable<TimeNotificationBehaviour> scriptPlayable =
    57.                     (ScriptPlayable<TimeNotificationBehaviour>) playableOutput.GetSourcePlayable().GetInput(0);
    58.  
    59.              
    60.  
    61.                 TimeNotificationBehaviour timeNotificationBehaviour = scriptPlayable.GetBehaviour();
    62.  
    63.                 var simpleMarkers = this.GetMarkers().OfType<SimpleMarker>();
    64.                
    65.                 m_Receiver=new ReceiverExample();
    66.  
    67.                 playableOutput.AddNotificationReceiver(m_Receiver);
    68.  
    69.                 foreach (var marker in simpleMarkers)
    70.                 {
    71.                  
    72.                     scriptPlayable.GetBehaviour().AddNotification(marker.time, marker);
    73.                 }
    74.             }
    75.             else
    76.             {
    77.              
    78.                 playableOutput = ScriptPlayableOutput.Create(graph, "NotificationOutput");
    79.                
    80.                  m_Receiver=new ReceiverExample();
    81.                
    82.                 //why also here and in "outputs"
    83.                 playableOutput.AddNotificationReceiver(m_Receiver);
    84.                
    85.                 //Create a TimeNotificationBehaviour
    86.                 var timeNotificationPlayable = ScriptPlayable<TimeNotificationBehaviour>.Create(graph);
    87.                
    88.                 playableOutput.SetSourcePlayable(graph.GetRootPlayable(0));
    89.                 timeNotificationPlayable.GetBehaviour().timeSource = playableOutput.GetSourcePlayable();
    90.                 playableOutput.GetSourcePlayable().SetInputCount(playableOutput.GetSourcePlayable().GetInputCount()+1);
    91.                 graph.Connect(timeNotificationPlayable, 0, playableOutput.GetSourcePlayable(), playableOutput.GetSourcePlayable().GetInputCount()-1);
    92.                
    93.                 var simpleMarkers = this.GetMarkers().OfType<SimpleMarker>();
    94.                
    95.                
    96.                 foreach (var marker in simpleMarkers)
    97.                 {
    98.                  
    99.                     timeNotificationPlayable.GetBehaviour().AddNotification(marker.time, marker);
    100.                 }
    101.             }
    102.         }
    103.  
    104.         return base.CreateTrackMixer(graph, go, inputCount);
    105.      
    106.     }
    107.  
    108. }

    Code (CSharp):
    1. using UnityEngine;
    2. using UnityEngine.Playables;
    3. using UnityEngine.Timeline;
    4.  
    5. public class SimpleMarker : Marker,INotification,INotificationOptionProvider
    6. {
    7.  
    8.     [SerializeField] public bool emitOnce;
    9.     [SerializeField] public bool emitInEditor;
    10.    
    11.  
    12.  
    13.     NotificationFlags INotificationOptionProvider.flags =>
    14.         (emitOnce ? NotificationFlags.TriggerOnce : default) |
    15.         (emitInEditor ? NotificationFlags.TriggerInEditMode : default);
    16.  
    17.     public PropertyName id { get; }
    18. }
    19.  
    May the Forge be with you!
     
    LorenzoValenteTBS and Valastir like this.
  6. LorenzoValenteTBS

    LorenzoValenteTBS

    Joined:
    Dec 13, 2022
    Posts:
    1
    Don't use "0,1,1,1" in the fake curves, this will force the timeline to have a minimum duration of 1 seconds. Use "0,0,0,0", instead