Search Unity

New in 2019.1 - Marker Customization

Discussion in 'Timeline' started by julienb, Dec 7, 2018.

  1. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    A new item has appeared: markers!

    A marker is new item that can be added to a timeline. It is used to represent a point in time.
    Markers can be added and manipulated on a timeline the same way as clips; selection, copy-paste, edit modes etc. should work. A markers also has a specialization, just like clips (Animation Clip, Activation Clip, Control Clip, etc.). In 2019.1, Timeline offers its first built-in marker: Signal Emitter marker. See this thread to know more about signals.

    It is also possible to write custom markers!

    How to create a custom marker

    In order to let timeline know that you have a new type of marker, all you have to do is to create a class that inherits Marker.

    public class CustomMarker : UnityEngine.Timeline.Marker {}


    That’s it! This custom marker can now be added on any track and on the timeline marker area.

    At this point, the custom marker is only a visual item on the timeline. This means that this marker cannot invoke code when triggered. That doesn’t mean it is not useful; a marker can be a snap point or a chapter. It is also accessible through the Timeline API in editor and at runtime

    How to create a custom marker that will invoke code

    In order to have a marker trigger something, it needs to implement the INotification interface.

    public class CustomMarker : UnityEngine.Timeline.Marker, INotification
    {
    public PropertyName id { get { return new PropertyName(); } }
    }


    Implementing the INotification interface will tell timeline that the marker will need to react when triggered.

    The INotification interface is part of the Playable notification system. Each marker that implements INotification will have a node added in the timeline’s Playable Graph.



    To make things clear, here’s a table of the particularities of the different ways to create custom marker:
    upload_2018-12-7_17-13-27.png

    When a INotification node in the Playable Graph is sent, the PlayableOutput will receive it. One last piece will need to be added in order to process this notification.

    public class MessageReceiver : MonoBehaviour, INotificationReceiver
    {
    public void OnNotify(Playable origin, INotification notification, object context) {}
    }


    The PlayableOutput will need to have a CustomReceiver component in order to receive and process the INotifications.

    Here’s a summary of what you need to do if you want to invoke code with your custom marker:
    1. Create a class inheriting from Marker and implementing INotification.
    2. Create a component implementing INotificationReceiver.
    3. Add this component on the correct Gameobject
      • If your custom marker is on a track, add the component on the track’s bound object.
      • If your custom marker is on the timeline itself (the timeline marker area), add the component on the Gameobject that has the PlayableDirector component.
    In order to show you what is possible to do with custom markers, I created Message markers. These markers act like Animation events, but directly in the Timeline. See this Github repo to download the project and see how I did it. The relevant code is here (the rest is just inspector code).

    One last thing...

    We just saw that we can customize the behaviour of a marker. But that is not the only you can do; it is also possible to customize a marker’s appearance. It is not yet available in 2019.a11, but it will release in 2019.1. Stay tuned.
     
    Last edited: Dec 7, 2018
    5argon, ModLunar, tarahugger and 4 others like this.
  2. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    79
    Is there a particular reason as to why this example uses custom code to extract available methods from the gameobject in question instead of using a regular UnityEvent? Is it only to show that you can directly reference the bound object?
     
  3. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    79
    Ok, I get it, UnityEvents don't have a way to insert context in them, so if they're in an asset (like something from a Timeline, which obligatorily resides in an asset), they can only see Assets.

    UnityEvents really should get a little power-up. They can only have a single parameter, you need to inherit from them because Unity can't serialize generic stuff, and now this as well.
     
    Last edited: Jan 28, 2019
  4. recursive

    recursive

    Joined:
    Jul 12, 2012
    Posts:
    556
    From my perspective:

    The sample is more demonstrating you can build the old (and kinda not great, what with the SendMessage call and fixed argument types) animation events functionality on Timeline if you really wanted to. This could be useful if you're moving from a cutscene controlled by an Animation clip to a cutscene controlled by a timeline and you wanted to salvage code that relied on the old events system before fully migrating to a cleaner setup with signals or other custom marker types specific to your use case (and likely more performant that this by a good margin).

    UnityEvents are just a way to serialize parameters and delegate references to other objects in the editor, they're not a great solution for completely decoupled communication due to way delegates work. The subscriber has to know about where the UnityEvent's containing object is before subscribing/unsubscribing. On top of this, Delegate invocation is often the slowest type of call in the .NET framework and UnityEvents add another layer on top of that.

    The signals way is just a contract using the Signal Asset as a token that maps to a function call. This allows a more complete decoupling. That said I'll need to get on the 2019.1 alpha at some point and actually get a feel for it before I go deeper into the pros and cons.

    I can see where your frustration lies, the signals way is very different than what seems to be intuitive but there may be performance/implementation simplicity reasons for going that route that remain unseen what with all of this being brand new.
     
  5. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    79
    Sure, that looks pretty much like the idea, even if only to serve as an example:

    Nothing much to add to that, it was more that I didn't really understand the reason as to why not use UnityEvents in a case in which they'd have been able to fulfill exactly the same purpose. That is, until I realised why.

    That could easily be solved, I think, by extending the UnityEvent API to allow for it to have basically that same UI, but also to have context injected into it. In this case, it could even use the kind of references the Timeline Asset itself uses, with Exposed Properties which are populated through the Director Component.

    I haven't read the UnityEvent source code yet, and by a glance it does seem to be quite intricate, but it surely is far from being one of Unity's most complex features. Given it's importance, I do think it's worth the investment into improving it.

    See, that I don't buy as a problem. At all. At which order of magnitude in call counts would you have to be for your timeline events to actually become a performance problem? You'd need to have an insane amount of events at the same frame, which makes no sense at all if you think about most if not any use case for Timeline. And, of course, you could write your own call system if you are in one of the unlikely edge cases.

    Yes, I realize that as well. I even mentioned it in the other thread. Although I do lack to see the reason as to why that much decoupling would be necessary. As I mentioned in the other thread, the current way the system does it is by creating an insane list of signals with only their names to help identify them. I think it'd be much better if they could lie in Timeline Assets themselves, at least. That way, at least you'd have the context in which said signal name is supposed to happen.

    The problem with this approach is it forces all of the context to the callee, leaving none of it to the caller. In some cases, it'd be very useful to be able to divide it between the two, as my example with "A bunch of slighly different explosions" tries to illustrate.

    My frustrations don't lie simply on the Signal system itself (although I've already explained a bit of the problems I immediatly see in it in the other thread). It's more on the fact that said complexity could be opt-in. There are a ton of usecases in which said complexity isn't needed at all, as a lot of sequences can be directly mapped to the scene they should reside in.
     
  6. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    79
    Something else I always forget to say:

    There is no visual cue for a selected marker in the Timeline view. Is it just a bug for me or was this never present? Shouldn't it be blue when selected, like every other selectable piece of the UI?
     
  7. Alexees

    Alexees

    Joined:
    Nov 8, 2017
    Posts:
    167
    @julienb could you quickly explain what a Marker Track does? It seems to be related, talking about Markers and all, but it's never mentioned, explained anywhere, used...
     
  8. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    A marker should be white when selected. upload_2019-1-7_11-42-5.png
     
  9. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    Marker Track is a utility class that you can inherit from when creating custom tracks. It is a track that does not accept clips.

    By the way, the Marker Track should never have been shown in the Add menu:(. It's a bug that appeared right before releasing the alpha version.
     
  10. Alexees

    Alexees

    Joined:
    Nov 8, 2017
    Posts:
    167
    Hihi, I thought something like that was the cause.
     
  11. TFgamesOG

    TFgamesOG

    Joined:
    Jun 2, 2017
    Posts:
    16
    Thanks for this awesome API!

    I have a question: when you select "Loop" for the timeline's "Wrap Mode" all my custom markers are fired simultaneously right at the beginning of the next round of the timeline. Why is that?
     
  12. TFgamesOG

    TFgamesOG

    Joined:
    Jun 2, 2017
    Posts:
    16
    Also, it would be cool, to be able to specify the type of marker a receiver can process.

    Something like:
    Code (CSharp):
    1. public class MessageReceiver : MonoBehaviour, INotificationReceiver<Message>
    2. {
    3.     public void OnNotify(Playable origin, Message message, object context)
    4.     {
    5.         var methodToCall = message.method;
    6.         var argument = ArgumentForMessage(message, origin.GetGraph().GetResolver());
    7.      
    8.         if (EditorApplication.isPlaying)
    9.             SendMessage(methodToCall, argument);
    10.     }
    11. }
    Or maybe with a class-attribute like
    Code (CSharp):
    1. [MarkerType(typeof(Message))]
    2. public class MessageReceiver : MonoBehaviour, INotificationReceiver
    3. {
    4.     public void OnNotify(Playable origin, INotification notification, object context)
    5.     {
    6.         var message = notification as Message;
    7.         var methodToCall = message.method;
    8.         var argument = ArgumentForMessage(message, origin.GetGraph().GetResolver());
    9.      
    10.         if (EditorApplication.isPlaying)
    11.             SendMessage(methodToCall, argument);
    12.     }
    13. }
     
  13. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    Seems weird... Do you have the Retroactive flag enabled on your markers?

    I think it's a good suggestion! Right now you'll have to filter the markers manually.
     
  14. TFgamesOG

    TFgamesOG

    Joined:
    Jun 2, 2017
    Posts:
    16
    It works when I implement the
    INotificationOptionProvider
    and return the default NotificationFlags:
    Code (CSharp):
    1. public NotificationFlags flags => default;
    I assumed that Unity uses the default flags by default when I don't realize
    INotificationOptionProvider
    at all...
    Is this a bug or desired behaviour?
     
    Last edited: Mar 6, 2019
  15. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    By default, a notification will have NotificationFlags set to retroactive. (API doc).

    However,
    that is a bug. I just verified and you are right, when the timeline is looping and retroactive is true, notifications are fired when they shouldn't be. Thanks, I'll fix ASAP.
     
    Last edited: Mar 15, 2019
  16. alanmthomas

    alanmthomas

    Joined:
    Sep 7, 2015
    Posts:
    120
    I have a question regarding implementation of signals. I have a script on an object with a function that takes a boolean. I would like to be able to set that bool with the signal to true or false. Does this mean that I'd need a signal for true and another for false?

    This becomes less useful for cases where I need to pass a string. How would one go about implementing that case, if possible, using a signal and not needing a new signal for every possible string variation?
     
  17. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,232
    Hi, small question. Is this already available? Or is it "Timeline: Added ClipEditor, TrackEditor, and MarkerEditor classes the user can derived from to modify custom clips, tracks and marker functionality inside the Timeline Editor" in the 2019.2 patch note? If not available, will it be backported during 2019.1?
     
  18. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    883
    ClipEditor, MarkerEditor and TrackEditor are available in 2019.2 only. They won't be backported to 2019.1, unfortunately.
     
    5argon likes this.
  19. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,232
    Thank you, one more question. In 2019.1 I saw an attribute [UnityEngine.Timeline.CustomStyle("ussStyle")] available. It looks like what could do a subset of what MarkerEditor can. So for the time being I want to style the marker based on this while waiting for 2019.2. Is there any documentation on this?

    So in the code I saw [CustomStyle("SignalEmitter")] and I could also apply that style over my custom marker, and the default (curved top) custom marker style changes to be like the signal emitter (flat top). This is an image before CustomStyle, when applied with [CustomStyle("SignalEmitter")], they are indistinguishable.

    Screenshot 2019-04-30 15.46.50.png

    So when I create multiple custom markers I want to be able to discern them at glance rather than them being all curved top. I tried [CustomStyle("MyStyle")] according to the code comment :

    Code (CSharp):
    1.     ///<summary>
    2.     /// Use this attribute to customize the appearance of a Marker.
    3.     /// </summary>
    4.     /// Specify the style to use to draw a Marker.
    5.     /// <example>
    6.     /// [CustomStyle("MyStyle")]
    7.     /// public class MyMarker : UnityEngine.Timeline.Marker {}
    8.     /// </example>
    9.     /// Then, add a 'common.uss' file in the Editor folder.
    10.     /// Create an element to customizes the appearance of the marker.
    11.     /// <example>
    12.     /// MyStyle
    13.     /// {
    14.     ///   /* Specify the appearance of the marker in the collapsed state here. */
    15.     /// }
    16.     ///
    17.     /// MyStyle:checked
    18.     /// {
    19.     ///   /* Specify the appearance of the marker in the expanded state here. */
    20.     /// }
    21.     ///
    22.     /// MyStyle:focused:checked
    23.     /// {
    24.     ///   /* Specify the appearance of the marker in the selected state here. */
    25.     /// }
    26.     /// </example>
    I have already add "common.uss" to the Editor folder with empty styles like the documentation, but I get this warning "Cannot find style MyStyle for MyMarker". I even tried changing "MyStyle" to "MyMarker" (In the same fashion that Unity is doing "SignalEmitter" style for "SignalEmitter") but it still throws a warning "Cannot find style MyMarker for MyMarker". What am I missing to make this work in 2019.1?
     
    Last edited: Apr 30, 2019
  20. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,232
    Maybe you could make a custom SignalReceiver with a pair of SignalAsset -> UnityEvent<bool/string> instead? Then make a custom SignalEmitter that also holding bool/string. Then, in your custom receiver's OnNotify after matching the correct SignalAsset, you could invoke the event with parameter from the emitter. This way you could still use the UnityEvent dropdown to select your method with bool/string argument (without specifying a parameter, it is now dynamic)

    Screenshot 2019-04-30 16.44.21.png

    The editor for SignalAsset -> method pair didn't look as good as SignalReceiver however, but it is workable. In this example when the timeline passed through my marker with `true`, the audio source will be looped.

    Screenshot 2019-04-30 16.42.17.png
     
  21. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    123
    Unity now lets you write stylesheets to customize the appearance of the software (called USS for
    Unity style sheets). See this page for more info. The custom style sheets are updated on a domain-reload.

    Your USS files need to be located in an Editor folder in a StyleSheets/Extensions folder hierarchy. Here are some valid folder paths:
    • Assets/Editor/StyleSheets/Extensions
    • Assets/Editor/Markers/StyleSheets/Extensions
    • Assets/Timeline/Editor/MyMarkers/StyleSheets/Extensions
    The code comment did not correctly specify that the stylesheets needed to be located in the StyleSheets/Extensions folder hierarchy. I will update it.

    Also, make sure to provide a width and a height in your styling rules:
    Code (CSharp):
    1. MyStyle
    2. {
    3.     width: 12;
    4.     height: 14;
    5.     background-image: resource("Assets/myImage.jpg");
    6. }
     
    5argon likes this.
  22. 5argon

    5argon

    Joined:
    Jun 10, 2013
    Posts:
    1,232
    Thank you. According to https://docs.unity3d.com/Manual/UIE-USS-PropertyTypes.html I was able to use `resource` to access my `marker.png` in my Resources folder with resource("marker"), however, I would like to do relative path from my common.uss file, like directly near the file.

    In that page suggest that I should use url("./marker.png") instead. However I get this warning and the CSS was not applied :

    Code (CSharp):
    1. Cannot compile StyleCatalog: System.InvalidCastException: Specified cast is not valid.
    2.   at UnityEditor.StyleSheets.StyleSheetResolver+Value.AsString () [0x00002] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/Converters/StyleSheetResolver.cs:52
    3.   at UnityEditor.StyleSheets.StyleCatalog.ReduceStyleValueType (UnityEditor.StyleSheets.StyleSheetResolver+Value value) [0x00041] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/StyleCatalog.cs:1305
    4.   at UnityEditor.StyleSheets.StyleCatalog.CompileBaseValue (System.String propertyName, UnityEditor.StyleSheets.StyleState stateFlags, UnityEditor.StyleSheets.StyleSheetResolver+Value value, System.Collections.Generic.List`1[T] numbers, System.Collections.Generic.List`1[T] colors, System.Collections.Generic.List`1[T] strings, System.Collections.Generic.List`1[T] functions) [0x00022] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/StyleCatalog.cs:1288
    5.   at UnityEditor.StyleSheets.StyleCatalog.CompileValue (UnityEditor.StyleSheets.StyleSheetResolver+Property property, UnityEditor.StyleSheets.StyleState stateFlags, System.Collections.Generic.List`1[T] numbers, System.Collections.Generic.List`1[T] colors, System.Collections.Generic.List`1[T] strings, System.Collections.Generic.List`1[T] rects, System.Collections.Generic.List`1[T] groups, System.Collections.Generic.List`1[T] functions) [0x0002b] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/StyleCatalog.cs:1214
    6.   at UnityEditor.StyleSheets.StyleCatalog.Compile (UnityEditor.StyleSheets.StyleSheetResolver resolver, System.Collections.Generic.List`1[T] numbers, System.Collections.Generic.List`1[T] colors, System.Collections.Generic.List`1[T] strings, System.Collections.Generic.List`1[T] rects, System.Collections.Generic.List`1[T] groups, System.Collections.Generic.List`1[T] functions, System.Collections.Generic.List`1[T] blocks) [0x000b2] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/StyleCatalog.cs:1069
    7.   at UnityEditor.StyleSheets.StyleCatalog.Load (UnityEditor.StyleSheets.StyleSheetResolver resolver) [0x00046] in /Users/builduser/buildslave/unity/build/Modules/StyleSheetsEditor/StyleCatalog.cs:924
    8. UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
    But the path is correct, because when I intentionally change it to `../marker.png` I get an error instead

    Code (CSharp):
    1. [StyleSheetImportError: error=Semantic, code=InvalidURIProjectAssetPath, context=Assets/E7Unity/-EditorScripts/AnimatorTriggerMarker/StyleSheets/marker.png]
    2. UnityEditorInternal.InternalEditorUtility:ProjectWindowDrag(HierarchyProperty, Boolean)
    3. UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
    4.  
    So what is this "cast" that I have to correct to get it working with `url`?
     
    Last edited: May 6, 2019
  23. Silly_Rollo

    Silly_Rollo

    Joined:
    Dec 21, 2012
    Posts:
    475
    What's the expected usage for the ID property? Is there any internal Unity stuff which expects unique IDs?