Pass Parameters with ScriptableObject Events in Unity

pass parameters with scrptableobject events

How to Pass Parameters with ScriptableObject Events

Introduction

Events are an essential component in having clean, understandable, modular, and encapsulated code. They allow you to decouple scripts and Classes from one another. Events are like mailing lists. When the event is triggered a message is sent out to everyone that has subscribed to the event’s mailing list. The script or Class does not know or care about anyone on the list. The code just says “Hey, check this out!” and anyone that received the message does what they were told to do when they received the alert.

ScriptableObjects allow you to turn events and event listeners into modular pieces that you can plug and play across your GameObjects. You can learn more about Events as ScriptableObjects from the great Unite talks given by Richard Fine in 2016 and Ryan Hipple in 2017.

Sometimes, you may want to do a little more with your events besides just trigger another method or basic action. Sometimes, you need to pass information with the Event to make it more useful. Usually, you would store or get a reference to the component you want to pass the new data to. Then call a public method or set a public variable on that component. This, however, couples your classes and makes them dependent on one another, which is bad for everyone.

Today, we are going to show you how to solve this problem and pass parameters with ScriptableObject Events. To do this we are going to create our ScriptableObject Event, write a custom UnityEvent, code a GameEventListener, and attach a response with a dynamic string.

Getting Started

Create a ScriptableObject Event in Unity

The first thing we want to do is create a ScriptableObject to hold our Event. If you do not know what a ScriptableObject is I would recommend you watch the two Unite talks linked above, but here are the basics. A ScriptableObject can be used as an asset to hold data. This is useful because you can centralize data that a lot of components will reference. This saves you time when you eventually want to tweak values that would traditionally be stored in multiple places throughout multiple Classes or components.

We can use that same abstraction for Events. To create our ScriptableObject Event we want to have our Class, named GameEvent, inherit from ScriptableObjects instead of MonoBehavior. We also want to be able to create the GameEvent from the

We then will need to store a list of GameEventListeners, which is a class we will create later, that wants to subscribe to this event. The Listeners themselves will subscribe to GameEvents by calling the public method RegisterListener and passing themselves in as a parameter to be registered. Listeners can also unsubscribe by doing the same with UnregisterListener.

Last thing to note, we have a Raise method that gets called when a GameEvent is triggered. This loops through our GameEventListeners and calls their public method OnEventRaised, which we will create in the next step.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
[CreateAssetMenu(menuName = "Event/GameEvent"), System.Serializable]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();
 
    public virtual void Raise()
    {
        for(int i = listeners.Count -1; i >= 0; i--)
            listeners[i].OnEventRaised();
    }
 
    public virtual void RegisterListener(GameEventListener listener) => listeners.Add(listener);
 
    public virtual void UnregisterListener(GameEventListener listener) => listeners.Remove(listener);
}

Create a Game Event Listener

Next, we need to create our GameEventListener. Luckily, this is much more straightforward. We first need to create our fields. Create a field to hold a reference to the GameEvent we want to subscribe to. Also create a field to hold a UnityEvent.

For methods, we will use OnEnabled to subscribe us to the GameEvent by calling RegisterListener. This will subscribe us when the object is enabled. We also need to call UnregisterListener when our object is disabled, so we will do that in OnDisable. These methods are part of the Unity Lifecycle. If you want to know more about when they are called check out our article on Awake vs Start vs OnEnabled.

Lastly, if you remember, we called OnEventRaised on our GameEventListeners inside of our ScriptableObject GameEvent class. So, let us create that method next. Simply call the Invoke method on our UnityEvent when OnEventRaised is triggered.

Here is what the entire GameEventListener script should look like. Since our methods only contained one line of code, we were able to format them as expression bodies. Which replaces the brackets with arrow operator, =>, and makes the code a little easier to read.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
 
public class GameEventListener : MonoBehaviour
{
    public GameEvent Event;
    public UnityEvent Response;
 
    private void OnEnable() => Event.RegisterListener(this);
 
    private void OnDisable() => Event.UnregisterListener(this);
 
    public void OnEventRaised() => Response.Invoke();
}

Custom Unity Event

Now, what we have written so far will work 100% as a ScriptableObject Event system. But you cannot pass any values with it. To do that we will need to create a custom Event that extends UnityEvent. In this example we will be passing a String type through our event. You can also change this to whatever type you wish to pass by replacing String with the desired type. We are placing this code inside of our GameEventListener script, just above the class declaration and listing it as System.Serializable.

[System.Serializable]
public class MyStringEvent : UnityEvent<string>
{
}

Response.Invoke with Event

Furthermore, since our custom event now takes a String parameter, we need to update the OnEventRaised method. Add a String parameter to the method signature and pass the String value through to our MyStringEvent Invoke method.

public class GameEventListener : MonoBehaviour
{
    public GameEvent Event;
    public MyStringEvent Response;
 
    private void OnEnable() => Event.RegisterListener(this);
 
    private void OnDisable() => Event.UnregisterListener(this);
 
    public void OnEventRaised(string text) => Response.Invoke(text);
}

Pass Parameter to Event

If you look back at our GameEvent class, you will notice a new error in our Raise method. Here, we need to also add a String parameter to the method signature and again pass it through to the OnEventRaised method.

    public virtual void Raise(string text)
    {
        for(int i = listeners.Count -1; i >= 0; i--)
            listeners[i].OnEventRaised(text);
    }

Pass a String with an Event in Unity

At last, we can trigger our event from the required component. We have set up a game where, when a team wins, a game over screen is enabled displaying the winning team’s name. Since we do not want to couple the scoring component with the game over screen, we required an event. The event is also useful for other components that need to know when the game is over to stop things like player movement, game clocks, and timers.

To call the GameEvent we store it in a GameEvent field in the script that will trigger the event. To trigger the event, we call the Raise method while passing in the string we want to broadcast with the event.

public GameEvent gameOver;
gameOver.Raise(vertWin.ToString());

The game over screen does not need to know about our GameEvent or the GameEventListener. It does, however, have a method called SetWinnerText that takes a String value and sets the text to equal that String.

    public void SetWinnerText(string winner)
    {
        winnerText.text = winner;
    }

Create a ScriptableObject Event Asset

Finally, we can create our GameEvent asset. Since we added the CreateAssetMenu code to the top of our GameEvent script, we can create it directly in the editor. Click Assets in the menu bar and select Create > Event > GameEvent. This will create a new GameEvent in your project. We will name ours GameOver. Because our GameEvent inherits from ScriptableObject it cannot be attached directly to a GameObject in our scene. We will use the GameEventListener as the intermediary. Also, we can attach this new GameOver event to as many GameEventListeners as needed and they will all respond to this one event. Additionally, you create as many GameEvent’s as you need and tie them to separate triggers and listeners.

Attach GameEventListener to Object

Currently, we only need our game over screen to listen for the GameOver event. So, we will attach the GameEventListener to the game over screen GameObject and drag in our GameOver event to the Event property.

Invoke method with Dynamic Parameter

Next, we need to add items for the GameEventListener to invoke. To do this, click the plus button in the Response box and drag in the GameObject that should respond. Here, we have selected our game over screen. In the drop down you will now be able to select methods from the components attached to that GameObject. Normally, you would only see on the Static Parameters listed at the bottom of the menu. Since we have added our custom GameEvent, MyStringEvent, we can select Dynamic values. When we select the GameOverScreenController component attached to our GameOverScreen we can select the SetWinnerText method we created earlier. Note, there is an entry for Static and Dynamic parameters. Make sure to select the method from the Dynamic section to pass in the String value.

As a result, our game will now trigger the game over screen when the game has reached its end state. Moreover, the Text is now dynamically updated to show the winner.

pass parameters with scriptableobject events unity tutorial

GameEventListener with Parameter Example

Here is the full script for our GameEventListener. If you want to modify the code to accept another parameter type, such as a float or int, change the string type to the desired type.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
 
[System.Serializable]
public class MyStringEvent : UnityEvent<string>
{
}
 
public class GameEventListener : MonoBehaviour
{
    public GameEvent Event;
    public MyStringEvent Response;
 
    private void OnEnable(){ Event.RegisterListener(this); }
 
    private void OnDisable(){ Event.UnregisterListener(this); }
 
    public void OnEventRaised(string text){ Response.Invoke(text); }
}

Unity GameEvent with Parameter Example

Also, the code for our GameEvent script. If you change the type of parameter in the GameEventListener make sure to change the parameter type in the Raise method’s signature.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
 
[CreateAssetMenu(menuName = "Event/GameEvent"), System.Serializable]
public class GameEvent : ScriptableObject
{
    private List<GameEventListener> listeners = new List<GameEventListener>();
 
    public virtual void Raise(string text)
    {
        for(int i = listeners.Count -1; i >= 0; i--)
            listeners[i].OnEventRaised(text);
    }
 
    public virtual void RegisterListener(GameEventListener listener) => listeners.Add(listener);
 
    public virtual void UnregisterListener(GameEventListener listener) => listeners.Remove(listener);
}

And now you are ready to create and pass parameters with custom ScriptableObject Events in Unity. Thank you for stopping by. Stick around and check out more of our tutorials or posts like our piece on Unity’s Lerp Methods. Also, leave a comment telling us what you liked or did not like about the tutorial. Was it easy to follow along? What do you want to learn next? As always check out some of our published apps below.

6 thoughts on “Pass Parameters with ScriptableObject Events in Unity”

  1. Been trying to do this for a while. Couldn’t figure out how to get the variable into the event. Thank you!!

      1. This has been super useful for me! I appreciated it. I do have a question. Have you been able to extend this GameEvent/GameEventListener to accept any type of data? I was messing with the idea of using GameEvent and GameEventListener as a base class and redeclare the Event/Extended UnityEvent depending on what data I want. The issue I have here is that even redeclaring the same variable name as different type with the “new” keyword causes Unity to say that the same variable has been serialized more than once. I hope this all makes sense, but if not, I can try to explain it further. Thanks again for getting me started in the right direction!

  2. Piece of art here Matthew. The perfect complement to Ryan’s Hipple talk.
    Well explained.
    Thank you very much.

  3. This is a fantastic expansion to Ryan’s talk exactly what I was looking for, I’m now using this system to check player identification in a local COOP game. Makes the original Events system in the talk so much more flexible! Thanks so much for this Matthew!

Comments are closed.