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:
    177
    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
    mvaz_p, codestage, paul_hayes and 8 others like this.
  2. AndrewKaninchen

    AndrewKaninchen

    Joined:
    Oct 30, 2016
    Posts:
    149
    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:
    149
    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:
    669
    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:
    149
    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:
    149
    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. Deleted User

    Deleted User

    Guest

    @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:
    177
    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:
    177
    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. Deleted User

    Deleted User

    Guest

    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:
    177
    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:
    177
    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:
    197
    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,555
    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:
    1,516
    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,555
    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,555
    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:
    177
    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,555
    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:
    501
    What's the expected usage for the ID property? Is there any internal Unity stuff which expects unique IDs?
     
  24. Deleted User

    Deleted User

    Guest

  25. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    The ID property can help you identify a given notification when it is received. The ID for a notification is not used internally.
     
  26. zeotron

    zeotron

    Joined:
    Nov 30, 2018
    Posts:
    3
    It is fairly trivial to write an abstract class that works for your first example:
    Code (CSharp):
    1. public abstract class NotificationReceiver<T> : MonoBehaviour, INotificationReceiver
    2.     where T : INotification
    3. {
    4.     void INotificationReceiver.OnNotify(Playable origin, INotification notification, object context)
    5.     {
    6.         if (!(notification is T typedNotification)) {
    7.             return;
    8.         }
    9.  
    10.         OnNotify(origin, typedNotification, context);
    11.     }
    12.  
    13.     protected abstract void OnNotify(Playable origin, T notification, object context);
    14. }
    Usage:
    Code (CSharp):
    1. public class MessageReceiver : NotificationReceiver<Message>
    2. {
    3.     protected override void OnNotify(Playable origin, Message message, object context)
    4.     {
    5.         // Do stuff with "Message" here
    6.     }
    7. }
     
  27. iwaldrop

    iwaldrop

    Joined:
    Sep 3, 2012
    Posts:
    9
    I use UniRx and found this works really well with the message broker and the dynamic keyword, e.g.
    Code (CSharp):
    1. MessageBroker.Publish(notification as dynamic)
    2. ---
    3. MessageBroker.Receive<CustomMarkerType>()
     
  28. Tsequier

    Tsequier

    Joined:
    Apr 20, 2016
    Posts:
    23
    Hello @julienb and everyone, I managed to make my custom marker style with the .uss file, however, if I am not mistaken, there is no way to know, on the timeline window, if a custom marker is selected (mine doesn't brighten like the unity basics ones). Is this normal? Can I change it somehow? Thanks !
     
  29. julienb

    julienb

    Unity Technologies

    Joined:
    Sep 9, 2016
    Posts:
    177
    You can use selectors to define the appearance of a selected or collapsed marker:
    Code (CSharp):
    1. MyStyle
    2. {
    3. /* Specify the appearance of a collapsed marker here. */
    4. }
    5.  
    6. MyStyle:checked
    7. {
    8. /*Specify the appearance of a marker here.*/
    9. }
    10.  
    11. MyStyle:focused:checked
    12. {
    13. /* Specify the appearance of a selected marker here. */
    14. }
     
  30. Tsequier

    Tsequier

    Joined:
    Apr 20, 2016
    Posts:
    23
    Thank you !!
     
  31. Sangemdoko

    Sangemdoko

    Joined:
    Dec 15, 2013
    Posts:
    220
    Hi @julienb ,
    I achieved customizing my markers with the style sheet using unity 2019.1
    But I can only modify the width, height, and the background image.
    I tried using: color, backgraound-color and -unity-background-image-tint-color but none of them worked.
    I could use a image editor to color my collapsed, normal and selected image, but that could be bothersome if I want to add many colors. It would be so much easier to simply select a color directly in the sytlesheet.

    Note that my marker icon is simply a white circle with a black outline, and I want the inner white to change color.
    I later plan to add a few shapes like directional arrows, letters etc... which I would also like to color from the stylesheet.

    Am I doing something wrong or is it not supported it yet?

    That's unrelated by I was wondering if there was a way to have custom clips with custom styles. I haven't looked into it yet but I'd be great if I could have two circle icons connected by a line. It would also work for me if it was two markers for a start->end.
     
  32. strangerattractor

    strangerattractor

    Joined:
    Oct 4, 2019
    Posts:
    5
    Hiho,

    I would like to know if it is somehow possible to show the name of a marker in the timeline.
    My timeline gets pretty packed after a while, and it starts to become hard to know, which marker is where, without clicking every single one.

    Thank you for your help!
     
  33. seant_unity

    seant_unity

    Unity Technologies

    Joined:
    Aug 25, 2015
    Posts:
    1,516
    You can use a MarkerEditor and either set a tooltip explicitly, or draw an overlay of a label.

    Here is an example of an editor for a marker representing a customizable snap point, which does both.

    Code (CSharp):
    1. using System;
    2. using System.Collections.Generic;
    3. using System.Linq;
    4. using System.Reflection.Emit;
    5. using UnityEditor;
    6. using UnityEngine;
    7. using UnityEngine.Timeline;
    8. using UnityEditor.Timeline;
    9. using UnityEngine.Playables;
    10.  
    11. [CustomTimelineEditor(typeof(SnapPoint))]
    12. public class SnapPointEditor : MarkerEditor
    13. {
    14.     static GUIContent s_Temp = new GUIContent();
    15.  
    16.     public override MarkerDrawOptions GetMarkerOptions(IMarker marker)
    17.     {
    18.         var snapPoint = (SnapPoint) marker;
    19.  
    20.         var options = base.GetMarkerOptions(marker);
    21.         options.tooltip = snapPoint.tooltip;
    22.         return options;
    23.     }
    24.  
    25.  
    26.     public override void OnCreate(IMarker marker, IMarker clonedFrom)
    27.     {
    28.         var snapPoint = (SnapPoint) marker;
    29.         var track = marker.parent;
    30.         var lastMarker = track.GetMarkers().OfType<SnapPoint>().LastOrDefault(x => x != snapPoint);
    31.         if (lastMarker != null)
    32.         {
    33.             float h, s, v;
    34.             Color.RGBToHSV(lastMarker.snapLineColor, out h, out s, out v);
    35.             h = (h + 0.2f) % 1.0f;
    36.             var c = Color.HSVToRGB(h, s, v);
    37.             snapPoint.snapLineColor.r = c.r;
    38.             snapPoint.snapLineColor.g = c.g;
    39.             snapPoint.snapLineColor.b = c.b;
    40.         }
    41.     }
    42.  
    43.     public override void DrawOverlay(IMarker marker, MarkerUIStates uiState, MarkerOverlayRegion region)
    44.     {
    45.         var snapPoint = (SnapPoint) marker;
    46.  
    47.         DrawSnapLine(snapPoint, uiState, region);
    48.         DrawLabel(snapPoint, uiState, region);
    49.     }
    50.  
    51.  
    52.     static void DrawLabel(SnapPoint point, MarkerUIStates uiState, MarkerOverlayRegion region)
    53.     {
    54.         if (string.IsNullOrEmpty(point.label))
    55.             return;
    56.  
    57.         var colorScale = uiState.HasFlag(MarkerUIStates.Selected) ? 1.0f : 0.85f;
    58.  
    59.         var textStyle = EditorStyles.whiteMiniLabel;
    60.         s_Temp.text = point.label;
    61.  
    62.         var labelRect = region.markerRegion;
    63.         labelRect.x += labelRect.width;
    64.         labelRect.width = textStyle.CalcSize(s_Temp).x + 5;
    65.         var shadowRect = Rect.MinMaxRect(labelRect.xMin + 1, labelRect.yMin + 1, labelRect.xMax + 1, labelRect.yMax + 1);
    66.  
    67.         var oldColor = GUI.color;
    68.         GUI.color = Color.black;
    69.         GUI.Label(shadowRect, s_Temp, textStyle);
    70.         GUI.color = Color.white * colorScale;
    71.         GUI.Label(labelRect, s_Temp, textStyle);
    72.         GUI.color = oldColor;
    73.     }
    74.  
    75.     static void DrawSnapLine(SnapPoint snapPoint, MarkerUIStates uiState, MarkerOverlayRegion region)
    76.     {
    77.         if (snapPoint.snapLine == SnapPoint.SnapLine.None)
    78.             return;
    79.  
    80.  
    81.         var collapsed = uiState.HasFlag(MarkerUIStates.Collapsed);
    82.         if (collapsed && snapPoint.snapLine == SnapPoint.SnapLine.NotCollapsed)
    83.             return;
    84.  
    85.         float offset = collapsed ? 7: 15;
    86.         var color = snapPoint.snapLineColor;
    87.         if (uiState.HasFlag(MarkerUIStates.Selected))
    88.         {
    89.             color = color * 1.5f;
    90.         }
    91.  
    92.         var r = new Rect(region.markerRegion.center.x - 0.5f,
    93.             region.markerRegion.min.y + offset,
    94.             1.0f,
    95.             region.timelineRegion.height
    96.         );
    97.  
    98.         var oldColor = GUI.color;
    99.         GUI.color = color;
    100.         GUI.DrawTexture(r, Texture2D.whiteTexture, ScaleMode.StretchToFill);
    101.  
    102.         if (snapPoint.drawHighlight)
    103.         {
    104.             var previousTime = region.startTime;
    105.             foreach (var m in snapPoint.parent.GetMarkers())
    106.             {
    107.                 if (m.time < snapPoint.time && m is SnapPoint)
    108.                     previousTime = Math.Max(m.time, previousTime);
    109.             }
    110.  
    111.             if (previousTime != snapPoint.time)
    112.             {
    113.                 Rect highlightRect = region.markerRegion;
    114.                 highlightRect.xMin = ToPixel(region, previousTime);
    115.                 highlightRect.xMax = region.markerRegion.center.x;
    116.                 highlightRect.height = 2;
    117.                 GUI.DrawTexture(highlightRect, Texture2D.whiteTexture, ScaleMode.StretchToFill, true);
    118.             }
    119.         }
    120.  
    121.         GUI.color = oldColor;
    122.     }
    123.  
    124.     static float ToPixel(MarkerOverlayRegion region, double time)
    125.     {
    126.         var p = (time - region.startTime) / (region.endTime - region.startTime);
    127.         return region.timelineRegion.x + region.timelineRegion.width * (float) p;
    128.     }
    129.  
    130. }
     
    strangerattractor likes this.
  34. strangerattractor

    strangerattractor

    Joined:
    Oct 4, 2019
    Posts:
    5
    thank you for the quick response!
     
  35. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    211
    Hi,
    with the [TrackBindingType] attribute it is possible to restrict the type of PlayableAssets that can be added to a track. Is something similar possible with custom markers to limit the selection in the context menu?

    Another question: would it be possible to get a generic SignalAsset/Emitter workflow with signals that carry additional data? I am thinking of generic classes SignalAsset<T> and SignalEmitter<T>. Ideally, the SignalReceiver could handle any SignalAsset<T> and make use of the data as parameters for the reaction method calls. I would want this to be similar to the generic UnityEvent<T> while we currently only get the parameterless one. Do I make sense?
     
  36. Ferazel

    Ferazel

    Joined:
    Apr 18, 2010
    Posts:
    517
    I really want to say thanks for this feature. While I'm not sure why markers default to different notification flags than signals (retroactive?!), allowing us some flexibility with how they're done is great. I wish I would have done the work and used markers from the beginning instead of trying to shoe-horn signals to do what we needed. This is so much better of a system and I like that we can customize the icons too! Thanks!
     
  37. Garrafote

    Garrafote

    Joined:
    Jun 13, 2013
    Posts:
    48
    Hi, is there a way to customize the add menu entry for my custom markers? And perhaps filter (even if only at the editor level) in which tracks can a specific marker be added?

    Lets say I have Dialogue markers, Player markers and Timeline control flow markers. I'd like to be able to have my menus categorized like "Dialogue/Add Dialogue Marker 1", "Player/Add Player Marker 1", etc

    Can I do that?
     
    olejuer likes this.
  38. olejuer

    olejuer

    Joined:
    Dec 1, 2014
    Posts:
    211
    @Garrafote
    I asked the same thing with no answer. But I dug into the code of TimelineContextMenu to find this method

    Code (CSharp):
    1. public static bool DoesTrackSupportMarkerType(TrackAsset track, Type type)
    2. {
    3.     if (track.supportsNotifications)
    4.     {
    5.         return true;
    6.     }
    7.  
    8.     return !typeof(INotification).IsAssignableFrom(type);
    9. }
    and this will basically always return true and not check for any attributes, unfortunately. Nothing we can do as far as I can tell.
     
    Garrafote likes this.
  39. Jamez0r

    Jamez0r

    Joined:
    Jul 29, 2019
    Posts:
    205
    I can't seem to get my custom marker icon to change appearance when clicking it. It always displays my unselected marker (alertNew_unselected.png). I am getting these two warnings from my common.uss:


    Code (CSharp):
    1. Unknown pseudo class "focused"
    2. UnityEditor.Experimental.AssetImporters.ScriptedImporter:GenerateAssetData(AssetImportContext)
    3.  
    Code (CSharp):
    1. Unknown pseudo class "focused"
    2. UnityEditor.AssetDatabase:GetMainAssetTypeAtPath(String)
    3. TMPro.EditorUtilities.FontAssetPostProcessor:OnPostprocessAllAssets(String[], String[], String[], String[]) (at Library/PackageCache/com.unity.textmeshpro@3.0.1/Scripts/Editor/TMPro_TexturePostProcessor.cs:31)
    4. UnityEditor.AssetPostprocessingInternal:PostprocessAllAssets(String[], String[], String[], String[], String[])
    This is my full common.uss file:

    Code (CSharp):
    1.  
    2. AlertDiamond
    3. {
    4.     width: 20px;
    5.     height: 20px;
    6.     background-image: resource("Assets/Cutscene/Editor/alertNew_unselected.png");
    7. }
    8. AlertDiamond:checked
    9. {
    10.     width: 20px;
    11.     height: 20px;
    12.     background-image: resource("Assets/Cutscene/Editor/alertNew_unselected.png");
    13. }
    14. AlertDiamond:focused:checked
    15. {
    16.     width: 20px;
    17.     height: 20px;
    18.     background-image: resource("Assets/Cutscene/Editor/alertNew_selected.png");
    19.  
    20. }
    Did the keyword "focused" get renamed to something else? I've double checked all of my setup and it seems correct based on this thread.

    Would appreciate any help!
     
    cdr9042 likes this.
  40. arvzg

    arvzg

    Joined:
    Jun 28, 2009
    Posts:
    619
    I figured out how to draw a label next to the marker, but is there any way to change the color of the marker itself?
     
  41. cdr9042

    cdr9042

    Joined:
    Apr 22, 2018
    Posts:
    173
    Change the :focused:checked line to :hover:focus:checked
    Code (CSharp):
    1. PhaseChange:hover:focus:checked
    2. {
    3.     background-image: resource("Assets/_Project/Textures/Placeholders/diamond.png");
    4. }
    Reference: https://github.com/Unity-Technologi...rker/Editor/Stylesheets/Extensions/common.uss

    Code (CSharp):
    1. /* Collapsed Marker State -> Corresponds to GUIStyle.normal */
    2. JumpMarker
    3. {
    4.     width: 18px;
    5.     height: 18px;
    6.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/jumpTo_collapsed.png");
    7. }
    8.  
    9. /* Active Marker State -> Corresponds to GUIStyle.onNormal */
    10. JumpMarker:checked
    11. {
    12.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/jumpTo_normal.png");
    13. }
    14.  
    15. /* Selected Marker State -> Corresponds to GUIStyle.onFocused */
    16. JumpMarker:hover:focus:checked
    17. {
    18.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/jumpTo_selected.png");
    19. }
    20.  
    21. /* Collapsed Marker State -> Corresponds to GUIStyle.normal */
    22. DestinationMarker
    23. {
    24.     width: 18px;
    25.     height: 18px;
    26.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/destination_collapsed.png");
    27. }
    28.  
    29. /* Active Marker State -> Corresponds to GUIStyle.onNormal */
    30. DestinationMarker:checked
    31. {
    32.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/destination_normal.png");
    33. }
    34.  
    35. /* Selected Marker State -> Corresponds to GUIStyle.onFocused */
    36. DestinationMarker:hover:focus:checked
    37. {
    38.     background-image: resource("Assets/6-JumpMarker/Editor/Stylesheets/imgs/destination_selected.png");
    39. }
     
  42. Jamez0r

    Jamez0r

    Joined:
    Jul 29, 2019
    Posts:
    205

    Thanks that worked great!
     
  43. Garrafote

    Garrafote

    Joined:
    Jun 13, 2013
    Posts:
    48
    I ended up drawing a colored texture on top of the original graphic
     
  44. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    So do resources in the common.uss have to be absolute paths?

    That would be a real shame if so.
    How can you expect to make a re-usable library where the user can move the contents anywhere they want inside their project?
     
  45. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    Exact same issue I'm seeing. Can't use relative url for some reason.

    this doesn't work:

    resource.png

    Code (CSharp):
    1. background-image: url("../imgs/jumpTo_collapsed.png");
    2. background-image: url("../imgs/jumpTo_normal.png");
    3. background-image: url("../imgs/jumpTo_selected.png");
    4. background-image: url("../imgs/destination_collapsed.png");
    5. background-image: url("../imgs/destination_normal.png");
    6. background-image: url("../imgs/destination_selected.png");
    7. background-image: url("../imgs/pencil.png");
    https://docs.unity3d.com/Manual/UIE-USS-PropertyTypes.html

    according to docs, that should work. Bug report coming up...
     
    5argon likes this.
  46. CDF

    CDF

    Joined:
    Sep 14, 2013
    Posts:
    1,311
    Submitted: Case 1292311
     
  47. retrobrain_dongyi_cai

    retrobrain_dongyi_cai

    Joined:
    Feb 3, 2021
    Posts:
    10
    There is error in this case:


    NullReferenceException: Object reference not set to an instance of an object
    MessageInspector.OnInspectorGUI () (at Assets/TimlineMessage/Editor/MessageInspector.cs:46)
    UnityEditor.UIElements.InspectorElement+<>c__DisplayClass58_0.<CreateIMGUIInspectorFromEditor>b__0 () (at <d7545a46516941d4b2f2dec578cd41ee>:0)
    UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)



    How to solve it? it seems that TimelineEditor.inspectedDirector is always null.
     
  48. LazloBonin

    LazloBonin

    Joined:
    Mar 6, 2015
    Posts:
    812
    It would be great if the doc about INotificationOptionProvider was added to the blog post.

    Also, it would make sense for NotificationFlags to implement a None = 0 enum value like standard so we don't have to type "0" or "default" to disable retroactive.
     
  49. daxiongmao

    daxiongmao

    Joined:
    Feb 2, 2016
    Posts:
    412
    Was there anything on how to change a markers selected state? I can get it to change using multiple images.
    But can't seem to adjust the color of an image. Even changing size didn't seem to have an effect.

    Looks like from this thread you have to basically individual icons? Otherwise can't find a USS setting to let me see its selected like the default ones. Where they are a bit grey then brighter white when selected.

    Also what am I missing to get it to reload? I have to reimport my c# signal emitter every time to get the time line to change and show the updated uss file.
     
  50. Midiphony-panda

    Midiphony-panda

    Joined:
    Feb 10, 2020
    Posts:
    243
    I have a use case where I would like for a custom marker to work on tracks, without those having a track binding type : is it possible ?
    @DavidGeoffroy

    For example, I would like this marker to work on my custom tracks, with no binding type :
    upload_2021-12-13_18-10-4.png

    So this component could receive the events :
    upload_2021-12-13_18-11-16.png


    Implementation of the marker and the component :
    Code (CSharp):
    1. public class ClassMarker : Marker, INotification, INotificationOptionProvider
    2. {
    3.         //...
    4. }
    5.  
    6. public class UITKTimelineProxy : MonoBehaviour, IUITKTimelineBinding, INotificationReceiver
    7. {
    8.         public void OnNotify(Playable origin, INotification notification, object context)
    9.         {
    10.             if (notification is not ClassMarker classMarker)
    11.                 return;
    12.             //...
    13.          }
    14. }

    EDIT : one way to solve this, would be to implement my own ClassMarker management inside the mixer of my custom tracks.
     
    Last edited: Dec 13, 2021