Thanks. I’ll try with FullInspector see if that makes a difference. Unity serialization is so limited. I wish they would improve it and implement FullInspector or something.
Yeah I was just curious why that is just seems strange and inconvenient. UnityEvent are really powerful but having to create a new class for every parameter combination is quite a limitation.
You can create an UnityEvent type that takes the generic C# “object” type. You can then pass in absolutely anything, but will need to recast it on the other end of the process (although the object.GetType() method could be your friend there).
// Create a one-input generic derivation of UnityEvent that takes a boxed "object"
// as input. The input will need to be "unboxed" at the other end of the line
// either with knowledge of its original type or using its GetType() method.
public class UnityObjectEvent : UnityEvent<object> { }
It then works like your example above. A modified version of the Unity tutorial for a basic messaging system would thus look like this:
EventManager.cs
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
// Create a one-input generic derivation of UnityEvent that takes a boxed "object"
// as input. The input will need to be "unboxed" at the other end of the line
// either with knowledge of its original type or using its GetType() method.
public class UnityObjectEvent : UnityEvent<object> { }
// Event management script for creating/removing listeners, and triggering.
public class EventManager : MonoBehaviour
{
private Dictionary<string, UnityObjectEvent> eventDictionary;
private static EventManager eventManager;
public static EventManager instance
{
get
{
{
eventManager = FindObjectOfType(typeof(EventManager)) as EventManager;
// If there isn't one to be found, log an error:
if (!eventManager)
{
Debug.LogError("There needs to be one active EventManager script on a GameObject in your scene.");
}
else // If one was found, assume it wasn't initialised and initialise it.
{
eventManager.Init();
}
}
return eventManager;
}
}
void Init()
{
if (eventDictionary == null)
{
eventDictionary = new Dictionary<string, UnityObjectEvent>();
}
}
// Start listening to an event
public static void StartListening(string eventName, UnityAction<object> listener)
{
UnityObjectEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.AddListener(listener);
}
else
{
thisEvent = new UnityObjectEvent();
thisEvent.AddListener(listener);
instance.eventDictionary.Add(eventName, thisEvent);
}
}
// Stop listening to an event
public static void StopListening(string eventName, UnityAction<object> listener)
{
if (eventManager == null) return; // In case we've already destroyed our eventManager, avoid exceptions.
UnityObjectEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.RemoveListener(listener);
}
}
// Trigger an event
public static void TriggerEvent(string eventName, object argument)
{
UnityObjectEvent thisEvent = null;
if (instance.eventDictionary.TryGetValue(eventName, out thisEvent))
{
thisEvent.Invoke(argument); // Run all listener functions associated with this event.
}
}
}
EventTestScript.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.Events;
public class EventTestScript : MonoBehaviour
{
private UnityAction<object> someListener;
public string getVarFromCaller;
void Awake()
{
someListener = new UnityAction<object>(SomeFunction);
}
// Event listening should always be registered and de-registered on enable and disable.
void OnEnable()
{
EventManager.StartListening("test", someListener);
}
void OnDisable()
{
EventManager.StopListening("test", someListener);
}
void SomeFunction(object argument)
{
Debug.Log("SomeFunction was called with argument " + argument + " of type " + argument.GetType());
}
}
and
EventTestTrigger.cs
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class EventTestTrigger : MonoBehaviour
{
public float testValue = 0f;
public string testString = "Hello world!";
public object testObject = new object();
// Update is called once per frame
void Update()
{
if (Input.GetKeyDown("q"))
{
if(testObject.GetType() != testValue.GetType())
{
testObject = testValue;
}
else
{
testObject = testString;
}
EventManager.TriggerEvent("test", testObject);
}
}
}
I’ve found the best solution so far is to reuse the existing events instead of defining your own. Such as Button.ButtonClickedEvent, Slider.SliderEvent, etc. Makes for less typing/reading.
If you can find a built-in event to suite your needs, that’s great. A custom event system comes in when you’d like a custom game event to trigger other actions.
For example, in a game I’m currently working on, I’d like a character’s body-slam to make all enemies in the scene go flying into the air. I create a body-slam event that takes the magnitude of the impact as an input, and then make all enemy prefabs listen for that event and go flying when it’s triggered.
Great example, minvertedm! I’m using something similar to pass a string parameter. Does anyone know why the methods of the EventManager class are declared public static? I would like to mock my EventManager using Nsubstitute for testing, but can’t do it unless I change the methods to public – because of the static methods I can’t create an IEventManger interface.
Great info. Thanks. Do you know if you are able to make a public UnityEvent work correctly in the editor? Anytime I try to link a function that has a type of object, the inspector says the method is missing.
These are the two functions I am trying to assign…One accepts an int, and one accepts an object.
public void ReceiveMessageInt(int package = 0)
{
Debug.Log(string.Format(“Received Message with Object {0}”,package));
}
public void ReceiveMessageObject(object obj)
{
Debug.Log(string.Format(“Received Message with Object {0}”,((GameObject)obj).name);
}
Just tested this and it “works” in the fact that I can assign the method, but not set any object(it has nothing shown where you would set it.) The reason? UnityEvent is limited in what it can display in the editor. If you try it with an enum it will be the exact same. It will still work just isn’t settable in the editor. It can be used, just needs to be assigned in a script instead.
Thanks for taking the time to test that. I will have to step back and figure out what else I’m doing wrong. One thing I’m probably having issues with is that I was using Action instead of UnityAction. But I am still overlooking something else and am having problems with the object UnityEvent.
I have a simple class that I think illustrates this. I would think I could just assign the TestReceiverObject method and receive the object and cast it to an int…But it still shows a missing method in the inspector.
When I assign a method that takes an int, the value from the inspector comes through with is 0 instead of 1. What am I overlooking here?
Thanks
using System;
using UnityEngine;
using UnityEngine.Events;
using Debug = UnityEngine.Debug;
[Serializable]
public class UnityObjectEvent2 : UnityEvent<object> { }
public class SampleEvent : MonoBehaviour
{
public UnityObjectEvent2 TestEvent;
private void Update()
{
if (Input.GetKeyUp(KeyCode.T))
{
var payload = 1;
TestEvent?.Invoke(payload);
}
}
/// <summary>
/// Asssign this to the test event target
/// </summary>
/// <param name="obj"></param>
public void TestReceiverObject(object obj)
{
int convertedObj = (int) obj;
Debug.Log("Received Message: " + convertedObj);
}
public void TestReceiverInt(int obj)
{
int convertedObj = (int) obj;
Debug.Log("Received Int Message: " + convertedObj);
}
}
When you use the drop down to select the method there will be two options for the method. The top ones that don’t have (int) beside it are the ones you are looking for. The (type) indicates the option to directly set what gets passed into the method, the one without will pass what the event is supposed to be passing. It will also pass the values into properties, so you can have a property instead of a method if you wanted.
These will both work and show up near the top of the drop down.
public int ReceiverIntProp { get; set; }
public void ReceiverIntMethod(int value) { }