How can I simply use a Marker to trigger a UnityEvent?

Hi guys, I've been struggling with this off and on for months now, and it is definitely my biggest gripe with Timeline. Besides this, I like pretty much everything about Timeline.

I want a way to trigger any generic function on any gameObject in the scene. The exact same way a UnityEvent would. There are threads from 2018 and earlier where people were asking for this, and Markers were supposed to be the solution. I have set up several Markers that all do specific things that I have configured them for, and that is nice - but I still don't see any way to "trigger any function on a gameobject" even with using a Marker.

I understand that I could use a Signal to trigger a UnityEvent, but I really dislike the workflow of Signals. I don't want to have hundreds of ultra-specific signals that I have to create and keep organized.

I also understand that I could make a Marker that triggers a UnityEvent that is on the receiving gameobject (the object bound to the timeline signal track), but that doesn't have any flexibility.

The ideal workflow would be to have a Marker track, right-click and add a "UnityEvent Marker", and then simply set up the UnityEvent on the Marker itself. Anything else feels unnecessarily overcomplicated.

If I try a simple setup like this, I can't drag scene objects into the UnityEvent. I'm assuming its because, by default, the Marker can't reference things in the scene?

public class Marker_UnityEvent : Marker, INotification {

    public PropertyName id => new PropertyName();

    [SerializeField] private UnityEvent unityEvent;
    public UnityEvent UnityEvent => unityEvent;

}

I have a different Marker that uses an "ExposedReference" that lets me drag a gameobject (Transform) from the scene into the marker's inspector:

    public ExposedReference<Transform> faceTowardsThisTarget;

Is there some magic that could let me use an ExposedReference and somehow apply it to the UnityEvent? Or some other configuration that lets me set up the UnityEvent?

Thanks for any help!

Edit: Bonus Question

Using Signals wouldn't be so bad, but I find the workflow of having to create the signal really annoying. You either have to stop what you're doing, navigate to the location in your project where you keep the signal assets, right-click -> create signal, name the signal, now go back to your Timeline, add a new signal to the track, now find the new signal you made in the huge list, and now you can finally configure the events it triggers.

The other option is the use 'Create New Signal' from the "Emit Signal" dropdown selector in the inspector when you select a signal, but you still have to navigate to your signal folder and either organize things into subfolders or just have a single folder with hundreds of signal assets in them.

I just find it strange that when all I want to do is trigger a function, I instead have to create a permanent asset and think of 'naming convention' for the asset and how I'm going to keep them organized. I don't know about you other programmers, but it makes me cringe to create these ultra specific "Blue_Dog_Jumped_On_Red_Chair.signal" assets, and having a huge list of them.

If I could at least change the default folder location when I do "Create New Signal" to where my actual Signals folder is, that would help. Is that possible? Sorry that this is sort of a rant...

Edit 2:

Since I'm ranting anyway, I might as well mention that not being able to re-order the "Reactions" on a Signal Receiver component feels really bad. They are permanently in the order that you added them in. Meaning you can't group similar things together (like all audio-related Reactions), and they aren't alphabetically ordered so even if you have good naming convention they are still in the order that you added them. These need to be re-orderable :face_with_spiral_eyes:

I concur that signal setup is possibly the most disengenious thing i've seen in years. Absolutely hideous implementation. Why the use of serialised objects? Haven't they heard of observer pattern. Not surprised given the generally awefull implementation of Events in Unity period.

Whats wrong with something like

class MyClassThatNeedsToDoSomething{
OnStart(){
Timeline.AddEventLister( (Signal signal)=>{
// Let me do something with signal
})
}
}

Anyway, I digress, this helped a lot with working out how to do something akin to being able to implement my own listener.
https://gametorrahod.com/how-to-make-a-custom-signal-receiver-with-emitter-parameter/

Ended up with
1. Create signal Track
2. set object on signal Track to gameobject with my CustomSignalLister class on it, that implements
INotificationReceiver interface.
3. On the same object add a SignalListener, otherwise your timeline will complain. In fact draggint he object onto the signal tracks target field asked me to add a signalListener to it.

Now, your CUstomSignalListener will run the OnNotify () method when the signal is dispatched.

This this message

public class CustomSignalLister : MonoBehaviour, INotificationReceiver
{
public void OnNotify(Playable origin, INotification notification, object context)
        {
            Debug.Log("timeline message received ");
/// Note that i've implemented a custom SignalEmitter so as to be able to pass a string. See the article link above.
            if(notification is MissionSignalEmitter missionSignalEmitter)
            {
                MissionDispatcher.DispatchMissionEvent(new MissionTaskEvent(MissionTaskType.MatchKey, missionSignalEmitter.missionEventKey));
            }
        }
}

This is about as good as i can get it. Unfortunately you still need to create a Signal, and you also get console errors complainging about serialised obejct properties. Timeline Signals are so dumb.

Edit
[The Error you might get]
Though it seems this is fixed after a restart

SerializedObjectNotCreatableException: Object at index 0 is null
UnityEditor.Editor.CreateSerializedObject () (at <d7545a46516941d4b2f2dec578cd41ee>:0)
UnityEditor.Editor.GetSerializedObjectInternal () (at <d7545a46516941d4b2f2dec578cd41ee>:0)
UnityEditor.Editor.get_serializedObject () (at <d7545a46516941d4b2f2dec578cd41ee>:0)
UnityEditor.Timeline.Signals.SignalReceiverInspector.OnEnable () (at Library/PackageCache/com.unity.timeline@1.2.15/Editor/Signals/SignalReceiverInspector.cs:26)

I know your pain, because I feel the same, bro! Please try my free plugin in Store, it doesn't require extra code, and it's easy to deal with Timeline Event, maybe it will solve your problem.

Here's the link: EventPlayer

God i'm thick.

There was like one line that pointed out that the OnNotify is received by the Playable director object. So putting customReceiver on that object, and adding a customMarker, meant i could easily call my own script without having to worry about signals.

https://github.com/Unity-Technologies/TimelineMarkerCustomization
https://blogs.unity3d.com/2019/06/25/how-to-create-custom-timeline-markers/

using UnityEngine;
using UnityEngine.Playables;
using UnityEngine.Timeline;

This will be available when right clicking on marker track.
namespace missions.core.signals
{

    [ExecuteAlways]
    public class MissionMarker: Marker, INotification, INotificationOptionProvider

    {
        public PropertyName id { get { return new PropertyName(); } }

        [SerializeField] public bool emitOnce;
        [SerializeField] public bool emitInEditor;


        [SerializeField] public string missionEventKey;


        /// <summary>
        /// Override this method to receive a callback when the marker is initialized.
        /// </summary>
        public override void OnInitialize(TrackAsset aPent)
        {
            Debug.Log("Marker initialised;");
        }

        public void OnNotify(Playable origin, INotification notification, object context)
        {
            Debug.Log("Marker notified.");
        }

        NotificationFlags INotificationOptionProvider.flags =>
            (emitOnce ? NotificationFlags.TriggerOnce : default) |
            (emitInEditor ? NotificationFlags.TriggerInEditMode : default);

    }
}
Pop this on the playable object.
namespace missions.core.signals
{
    [ExecuteAlways]
    class MissionReceiver : MonoBehaviour, INotificationReceiver
    {
        public void OnNotify(Playable origin, INotification notification, object context)
        {
            if (notification != null)
            {
                Debug.Log("Marker received.");
                if(notification is MissionMarker missionMarker)
                {
                    MissionDispatcher.DispatchMissionEvent(new MissionTaskEvent(MissionTaskType.MatchKey, missionMarker.missionEventKey));
                }
            }
        }
    }
}
1 Like

https://blog.unity.com/engine-platform/how-to-create-custom-timeline-markers