Sorry to bump this old thread but I have the exact same doubt and I was hoping you or someone else could help me. I just implemented the EventSystem in my game but would love to have the option to pass a value on trigger. E. g. In my game, I would like to trigger a game level change event when the player reaches a certain score and also pass the current gamelevel int value while triggering. This will allow all listeners to receive the gamelevel and can act accordingly. Any help would be great.
using System;
using UnityEngine;
using UnityEngine.Events;
public class LevelChangeUnityEventManager : MonoBehaviour
{
[System.Serializable]
public class LevelChangeEvent : UnityEvent<int> {} //Unity event allow up to 4 parameters, but I think people found ways to do more?
public LevelChangeEvent levelChanged;
//void Awake()
//{
// levelChanged = new LevelChangeEvent(); //Since we have it public in a monobehaviour, we dont need to do this. If we did, it would remove any editor stuff we did with the event.
//}
public void AddListener(UnityAction<int> method)
{
levelChanged.AddListener(method);
}
public void RemoveListener(UnityAction<int> method)
{
levelChanged.RemoveListener(method);
}
public void LevelChanged(int gameLevel)
{
levelChanged.Invoke(gameLevel);
}
}
using System;
using UnityEngine;
public class TestLevelChangeUnityEvent : MonoBehaviour
{
public float coinCount;
public float changeLevelOnCoinCount = 2f;
public int changeLevelTo = 2;
bool hasChanged;
LevelChangeUnityEventManager levelChanged;
void Start() //We run this in start to make sure this runs after our LevelChangeUnityEventManager Awake was called for creating the event (wont matter in our case since we are letting the editor create it)
{
levelChanged = (LevelChangeUnityEventManager) GameObject.FindObjectOfType<LevelChangeUnityEventManager>(); //Should probably make this a singleton, but this is just an example
levelChanged.AddListener(OnLevelChanged); //Assigning the method we want event to call
}
void Update()
{
coinCount += Time.deltaTime;
if(coinCount > changeLevelOnCoinCount)
{
if(!hasChanged) levelChanged.LevelChanged(changeLevelTo); //Calling the event
}else{
hasChanged = false;
}
}
void OnLevelChanged(int gameLevel)
{
Debug.Log("Level has changed to " + gameLevel);
hasChanged = true;
}
void OnDisable()
{
levelChanged.RemoveListener(OnLevelChanged); //To avoid memory leak, unsubscribe the event if this gameobject is disabled / destroyed. Not sure if you need to do this for UnityEvents
}
}
The cool thing about UnityEvents is that you dont need to subscribe to events through code.
For example…
using System;
using UnityEngine;
public class TestLevelChangeUnityEventInEditor : MonoBehaviour
{
public void CallMeWhenLevelChanged(int gameLevel) //Must be public for UnityEvent to see in editor
{
Debug.Log("Oh hey, looks like the level changed to " + gameLevel);
}
}
To subscribe that method above to the LevelChange event, we go to our LevelChangeUnityEventManager component in the editor and click on that little circle thing to select the object that has the TestLevelChangeUnityEventInEditor component (or drag and drop the object). We then click on the thing on the right that should be saying “No Function” and find our TestLevelChangeUnityEventInEditor component. Within that there should be the CallMeWhenLevelChanged method (was at the top for me). Click that and now the event will call that method.
Using C# delegates and events
using System;
using UnityEngine;
public class TestLevelChangeEvent : MonoBehaviour
{
public float coinCount;
public float changeLevelOnCoinCount = 2f;
public int changeLevelTo = 2;
bool hasChanged;
void Awake()
{
LevelChangeEventManager.AddListener(OnLevelChanged); //Assigning the method we want event to call
}
void Update()
{
coinCount += Time.deltaTime;
if(coinCount > changeLevelOnCoinCount)
{
if(!hasChanged) LevelChangeEventManager.LevelChanged(changeLevelTo); //Calling the event
}else{
hasChanged = false;
}
}
void OnLevelChanged(int gameLevel)
{
Debug.Log("Level has changed to " + gameLevel);
hasChanged = true;
}
void OnDisable()
{
LevelChangeEventManager.RemoveListener(OnLevelChanged); //To avoid memory leak, unsubscribe the event if this gameobject is disabled / destroyed
}
}
//Made static due to laziness
public static class LevelChangeEventManager
{
public delegate void LevelChangeEvent(int gameLevel);
static event LevelChangeEvent levelChanged;
public static void AddListener(LevelChangeEventManager.LevelChangeEvent method)
{
levelChanged += method;
}
public static void RemoveListener(LevelChangeEventManager.LevelChangeEvent method)
{
levelChanged -= method;
}
public static void LevelChanged(int gameLevel)
{
if(levelChanged != null) levelChanged(gameLevel);
}
}
There are different reasons to use either UnityEvent or C# Events, of which you can research about.
Hey thanks a lot HiddenMonk. With some help from your example and the unity page linked below I managed to rewrite that the tutorial script to accept one argument and it works perfectly. The same code can be extended for 2, 3 or more parameters.
Here’s the updated code for anyone who wants to use it
using UnityEngine;
using UnityEngine.Events;
using System.Collections;
using System.Collections.Generic;
[System.Serializable]
public class ThisEvent : UnityEvent<int>
{
}
public class EventManagerOneArgument : MonoBehaviour {
private Dictionary <string, ThisEvent> eventDictionary;
private static EventManagerOneArgument eventManagerArgs;
public static EventManagerOneArgument instance
{
get
{
if (!eventManagerArgs)
{
eventManagerArgs = FindObjectOfType (typeof (EventManagerOneArgument)) as EventManagerOneArgument;
if (!eventManagerArgs)
{
Debug.LogError ("There needs to be one active EventManger script on a GameObject in your scene.");
}
else
{
eventManagerArgs.Init ();
}
}
return eventManagerArgs;
}
}
void Init ()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, ThisEvent>();
}
}
public static void startListening(string eventName, UnityAction<int> listener){
ThisEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
{
thisEvent.AddListener (listener);
}
else
{
thisEvent = new ThisEvent();
thisEvent.AddListener (listener);
instance.eventDictionary.Add (eventName, thisEvent);
}
}
public static void stopListening(string eventName, UnityAction<int> listener){
if (eventManagerArgs == null) return;
ThisEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
{
thisEvent.RemoveListener (listener);
}
}
public static void triggerEvent(string eventName, int value){
ThisEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
{
thisEvent.Invoke (value);
}
}
}
Seeing those examples I was wondering if it was possible to write this exact example with a generic type? Like, it does not matter if its a bool or string or int that I’m passing.
Sorry for the Bump but i had this problem too, but couldn’t found any nice solution on the internet… (Expect this thread) and solved the “using generic type problem” instead of “UnityAction” and “UnityEvent” use “UnityAction” and “UnityEvent”.
This way almost anything can be used as parameter but needs to be casted to the dessired type in the UnityAction. Its really ugly but till now it works…
public class TestScript: MonoBehaviour
{
private void OnEnable()
{
EventManager.StartListening("test", testFn);
}
private void testFn(object value){
var v = value as int;
Debug.Log(v)
}
Looking into the same problem i think the best way is to use lambda expressions, than the EventManager is without any modifications and you are free to use any number and type of parameters.
I know I’m late too this, but it’s exactly what I need, but as far as I can you can’t pass parameters to the Invoke method so
public static void triggerEvent(string eventName, int value){
ThisEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue (eventName, out thisEvent))
{
thisEvent.Invoke (value);
}
EDIT - this code works but see my second post for a better version using a json serialized string of a scriptable object.
I tested it using raglets code for passing the int and ChrisWhyTea’s idea of using object so you can pass any parameter(and any number of parameters at the same time by using scriptable objects or a game object with a script attached to it.) The only thing is I left the var as a var. Using the “as int” or as string gave me errors.
event manager:
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine;
/*
Event messaging system for Unity with flexible parameter passing
Code is 99% from the untiy tutorial here: https://unity3d.com/learn/tutorials/topics/scripting/events-creating-simple-messaging-system
the other 1% is from this forum post: https://discussions.unity.com/t/583746
*/
// Replacement of the basic Unityevent with a unity event that has an object parameter <T> did not work
[System.Serializable]
public class ThisEvent : UnityEvent<object> { }
// This class holds the messages, you need to attatch it to one gameobject in the scene
public class EventManager : MonoBehaviour {
private Dictionary<string, ThisEvent> eventDictionary;
private static EventManager eventManger;
// grabs the instance of the event manager
public static EventManager Instance {
get
{
if (!eventManger)
{
eventManger = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManger)
{
Debug.Log("There needs to be one active EventManager script on a gameobject in your scene.");
}
else
{
eventManger.Init();
}
}
return eventManger;
}
}
// creates dictionary for the events
void Init ()
{
if(eventDictionary == null)
{
eventDictionary = new Dictionary<string, ThisEvent>();
}
}
// function called to insert an event in the dictionary
public static void StartListening (string eventName, UnityAction<object> listener)
{
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue (eventName,out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new ThisEvent();
thisEvent.AddListener(listener);
Instance.eventDictionary.Add(eventName, thisEvent);
}
}
// removes an event from the dictionary
public static void StopListening (string eventName, UnityAction<object> listener)
{
if (eventManger == null) return;
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
// event trigger with an object passed as a parameter.
public static void TriggerEvent (string eventName, object variant)
{
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke(variant);
}
}
}
The class for the object receiving the message
using UnityEngine;
using UnityEngine.Events;
// Example of registering a new event with an object as a parameter
public class test1 : MonoBehaviour {
// make sure to include the object here
private UnityAction<object> someListener;
private void Awake()
{
//make sure to include the object here
someListener = new UnityAction<object>(SomeFunction);
}
private void OnEnable()
{
EventManager.StartListening("test",someListener);
}
private void OnDisable()
{
EventManager.StopListening("test", someListener);
}
// var has worked for strings and int. I haven't tried it yet but I'm guessing scriptable ojects or just prefabs with a script that just holds variables can be used to send unlimited parameters.
void SomeFunction(object variant)
{
var v = variant;
Debug.Log("some function was called " + v.ToString());
}
}
An example of the message being sent:
using UnityEngine;
// Example of how to trigger an existing event with an object as a parameter.
public class EventTriggerTest : MonoBehaviour {
// Update is called once per frame
void Update () {
if (Input.GetKeyDown("q"))
{
string variantTest = "test123";
EventManager.TriggerEvent("test",variantTest);
}
}
}
In retrospect is not super great. you can use the code I posted previously and replace with instead and use an empty object with a c# script attached to it but I think the easiest and most flexible was to pass messages trough the event is by serializing a scriptable object to a json string and passing it that way.
With that method you will only need 1 event manager with 1 string parameter to pass an unlimited number of parameters.
Just remember to use the same scriptable object class to send and recieve a specific event.
EventManager.cs
place on an empty (or any object in the scene)
using System.Collections.Generic;
using UnityEngine.Events;
using UnityEngine;
/*
Event messaging system for Unity with flexible parameter passing
Code is 99% from the untiy tutorial here: https://unity3d.com/learn/tutorials/topics/scripting/events-creating-simple-messaging-system
the other 1% is from this forum post: https://discussions.unity.com/t/583746
*/
// Replacement of the basic Unityevent with a unity event that has a string that will be used to serialize JSON
[System.Serializable]
public class ThisEvent : UnityEvent<string> { }
// This class holds the messages, you need to attatch it to one gameobject in the scene
public class EventManager : MonoBehaviour {
private Dictionary<string, ThisEvent> eventDictionary;
private static EventManager eventManger;
// grabs the instance of the event manager
public static EventManager Instance {
get
{
if (!eventManger)
{
eventManger = FindObjectOfType(typeof(EventManager)) as EventManager;
if (!eventManger)
{
Debug.Log("There needs to be one active EventManager script on a gameobject in your scene.");
}
else
{
eventManger.Init();
}
}
return eventManger;
}
}
// creates dictionary for the events
void Init ()
{
if(eventDictionary == null)
{
eventDictionary = new Dictionary<string, ThisEvent>();
}
}
// function called to insert an event in the dictionary
public static void StartListening (string eventName, UnityAction<string> listener)
{
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue (eventName,out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new ThisEvent();
thisEvent.AddListener(listener);
Instance.eventDictionary.Add(eventName, thisEvent);
}
}
// removes an event from the dictionary
public static void StopListening (string eventName, UnityAction<string> listener)
{
if (eventManger == null) return;
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
// event trigger with a string passed as a parameter.
public static void TriggerEvent (string eventName, string json)
{
ThisEvent thisEvent = null;
if (Instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
// finally passes the message.
thisEvent.Invoke(json);
}
}
}
Template_RecieveEvent.cs
place on objects that will receive the message, change “test” to whatever message handle you want, test_parameters to your own scriptable object and your own code instead of the debug log.
using UnityEngine;
using UnityEngine.Events;
// Example of registering a new event with a serialized scriptable object
public class Template_RecieveEvent : MonoBehaviour
{
// make sure to include the string here.
private UnityAction<string> EventListener;
private void Awake()
{
//make sure to include the string here
EventListener = new UnityAction<string>(EventTriggered);
}
private void OnEnable()
{
EventManager.StartListening("test", EventListener);
}
private void OnDisable()
{
EventManager.StopListening("test", EventListener);
}
// the actual event that will recieve the "message"
void EventTriggered(string jsonVars)
{
// Remeber to use the same scriptable object when sending and recieving the same message since thats what you serialized.
test_parameters objVars = ScriptableObject.CreateInstance<test_parameters>();
// The fromjson might also work but it gave me some errors so I just switched to overwrite.
JsonUtility.FromJsonOverwrite(jsonVars, objVars);
// from the test you see int was still included even though it was never set it just kept it to 0.
Debug.Log("some function was called " + jsonVars + " " + objVars.stringInfo);
}
}
Template_SendEvent.cs
Example of creating an instance of a scriptable object, serializing it and passing it while triggering the “test” event. Remeber to use the same scriptable object class when sending and receiving a specific message.
using UnityEngine;
/*
* Example how to send a json serialized object to objects that have registered to a "test" message.
* Requires a GameObject in the scne with the EventManager script attached to it.
*/
public class Template_SendEvent : MonoBehaviour {
// Test parameters is just a sriptable object class with a bool, a string and an int.
// scriptable objects info: https://unity3d.com/learn/tutorials/modules/beginner/live-training-archive/scriptable-objects
private test_parameters objVars;
// Update is called once per frame
void Update()
{
// just to test, message deployed when pressing q
if (Input.GetKeyDown("q"))
{
// uses createinstance instead of new test_parameters because it's a scriptable object.
objVars = ScriptableObject.CreateInstance<test_parameters>();
objVars.BoolInfo = true;
objVars.stringInfo = "Im a string";
// converts to a json string, so the messge only needs 1 string to pass a large number of diverse parameters. and multiple objects subscribed to the message can pick the ones they need.
string jsonVars = JsonUtility.ToJson(objVars);
// passing the json string as a parameter.
EventManager.TriggerEvent("test", jsonVars);
}
}
}
test_parameters.cs
just the example scriptable object used in the example.
the gist is replace monobehaviour with ScriptableObject but if you haven’t used tyhem before go to Introduction to Scriptable Objects - Unity Learn
using UnityEngine;
// just a scriptable object class to test passing json serialized parameters as strings with an event manager
public class test_parameters : ScriptableObject {
public int numberInfo;
public string stringInfo;
public bool BoolInfo;
}
Sorry for the Bump. This solution is great and totally resolved the method parameters passing problem. Just one thing is not convenient, this event system is not compatible with methods that have no parameters. Right now the function has to have a string parameter, if a non-parameters method is listening to an event, you are going to have an error that says "No overload for ‘xxxxx’ matches delegate ‘UnityAction’.
Can this event system also be compatible with non-parameters function, so we don’t need one for parameters one for non-parameters and two event system separately
Yes, you can just duplicate the functions to a version with no argument. C# is polymorphic, which means that the right function version will be called depending on the parameters passed. This tutorial (at 22:14) shows how to have base no parameter events and also events with an object parameter. Although it uses object as the type for event data rather than a scriptable object, the principle is the same.