I have class "A", which will send event "a". Classes that are subscribing to event "a" will react to this event. Other classes can subscribe to event "a" without changing anything in class "A";
Now, what is the most reasonable way to do this in unity? Is there some messaging system that can already do that?
If not, should I make something like EventManager class that will store subscriber classes in array and call their methods?
There are probably many ways to do this.
Public static List
public class A : MonoBehaviour
{
public static List<A> AvailableAs = new List<A>();
private void Awake()
{
if(!AvailableAs.Contains(this)) AvailableAs.Add(this);
}
private void OnDestroy()
{
if(AvailableAs.Contains(this)) AvailableAs.Remove(this);
}
public void SomePublicMethod()
{
// magic
}
}
and use it e.g. like
public class B : MonoBehaviour
{
// run the method on all currently registered A instances
public void DoIt()
{
foreach(var a in A.AvailableAs)
{
a.SomePublicMethod();
}
}
}
Global EventHandler
Or if you rather want to go for encapsulation have as you mentioned a global event handler for all A's like
namespace ANamespace
{
public static class AEventHandler
{
internal static event Action OnInvokeA;
public static void InvokeAEvent()
{
if(OnInvokeA != null) OnInvokeA.Invoke();
}
}
}
and in A have
namespace ANamespace
{
public class A : MonoBehaviour {
private void Awake()
{
// it is save to remove a callback first even
// if it was not added yet. This makes sure it is
// added only once always
AEventHandler.OnIvokeA -= SomePrivateMethod;
AEventHandler.OnIvokeA += SomePrivateMethod;
}
private void OnDestroy()
{
AEventHandler.OnIvokeA -= SomePrivateMethod;
}
private void SomePrivateMethod()
{
// magic
}
}
}
Now in B you would rather simply do
// invoke the event and whoever is added as listener will do
// whatever he registered
// in this case all A instances execute their method
AEventHandler.InvokeAEvent();
Unity Event
If you have however only one class A which throws an event and you want others to react to it simply use a UnityEvent like
public class A : MonoBehaviour
{
public UnityEvent OnSomethingHappened = new UnityEvent();
private void SomeMethodIWillRun()
{
//...
OnSomethingHappened.Invoke();
}
}
Now you cann easily add callbacks to that event in the Unity Inspector by dragging in GameObjects/Components and select the method to call. (Exactly the same thing is used for the onClick event of the Button component btw. ;) )
And you could still add callbacks via script on runtime like
public class B : MonoBehaviour
{
public A referenceToA;
private void Start()
{
referenceToA.OnSomethingHappened.AddCallback(OnASomethingHappened);
}
private void OnDestroy()
{
referenceToA.OnSomethingHappened.RemoveCallback(OnASomethingHappened)
}
private void OnASomethingHappened()
{
//
}
}
Note: Typed on smartphone so no warranty but I hope the idea gets clear.
Related
For example, there's two classes here.
Class One:
using UnityEngine;
using System.Collections.Generic;
public class One:MonoBehavior
{
private List<string> _assetBundleList;
private void Start()
{
InitializeList();
}
private void InitializeList()
{
//Add assetBundleList field some value.
}
public IEnumerator<string> GetEnumerator()
{
return _assetBundleList.GetEnumerator();
}
}
Class two:
public class Two:MonoBehavior
{
public GameObject gameObjectWithScriptOne;
private One _scriptOne;
private void Start()
{
scriptOne = gameObjectWithScriptOne.GetComponent<One>();
DoSomething();
}
private void DoSomething()
{
foreach(var assetBundle in scriptOne)
{
//Load asset
}
}
}
Script one is just like a manager things, I use this for storing asset bundle data, perhaps the list value will change. Script two must wait for initializing done. Is there any way to wait for it except adjusting script order?
It can be handled with easily creating a context that initialize things sequentially. In short you need to block the main thread.
Let's say you have a root or persistent scene that do must things like loading assets etc.
//The only object in the Root Scene.
public class RootContext : MonoBehaviour
{
private const string _playerAddress = "Player";
public void Awake()
{
//Load assets
//wait for assets loading
//instantiate things.
//new instantiated things can also initalize with this way.
var handle = Addressables.LoadAssetAsync<GameObject>(_playerAddress);
var asset = handle.WaitForCompletion();
var go = Instantiate(bla bla bla);
}
}
I think the what you want is a framework that organize things. You can look at Strange. It is MVCS + IoC framework. Addionality what you said about "when field or property initialization is done" with [PostConstruct] attribute with Strange.
[Inject(ContextKeys.CONTEXT)]
public IContext Context { get; set; }
/*Initialization of injections are guaranteed at here. Their initialization
is also quaranteed like this class.
*/
[PostConstruct]
public void Initalize()
{
}
I'd go with refactoring, but if this is not the case C# events might be the solution.
Class One
public class One:MonoBehavior
{
private List<string> _assetBundleList;
public event Func Initialized;
private void Start()
{
InitializeList();
}
private void InitializeList()
{
//Add assetBundleList field some value.
Initialized?.Invoke();
}
public IEnumerator<string> GetEnumerator()
{
return _assetBundleList.GetEnumerator();
}
}
Class Two:
public class Two:MonoBehavior
{
public GameObject gameObjectWithScriptOne;
private One _scriptOne;
private void Start()
{
scriptOne = gameObjectWithScriptOne.GetComponent<One>();
scriptOne.Initialized += DoSomething;
}
private void DoSomething()
{
foreach(var assetBundle in scriptOne)
{
//Load asset
}
}
}
Also, you should unsubscribe from events when disabling an object, but you'll figure it out by yourself.
I have a script that consist of an event and a function that is triggered when this is called. Now I have another script. How do I check if the event from first script is triggered?
First script
public class MyEventManager: MonoBehaviour
{
private static MyEventManager _instance;
public static MyEventManager Instance
{
get
{
if (_instance == null)
_instance = FindObjectOfType<MyEventManager>();
return _instance;
}
}
public delegate void OpenEventHandler(bool wasOpened);
public event OpenEventHandler Opening;
public void OpeningObj()
{
OpeningObj(true);
}
public void OpeningObj(bool wasOpened)
{
Opening?.Invoke(wasOpened);
}
Second script
private MyEventManager eventMan;
private void Start () {
eventMan= (MyEventManager ) GameObject.FindObjectOfType (typeof (MyEventManager ));
}
private void CalledFromFunction()
{
Debug.Log("Is Called");
}
I get the error - No overload for 'CalledFromFunction' matches delegate 'MyEventManager.OpenEventHandler'
Since you use a singleton approach, you don't actually need the private MyEventManager eventMan; in the other class. You can get access to the manager from anywhere with the public static property you have declared ( public static Instance MyEventManager), so to get acces to the manager just use MyEventManager.Instance, since it's static it will be accessible from anywhere.
To react to some event the listener need to subscribe to the event. It's done like this:
public class ListenerClass : MonoBehaviour
{
void Start()
{
MyEventManager.Instance.Opening += OnOpening;
}
It means whenever the Opening event is triggered in the manager, the OnOpening method of the listener class should be called:
void OnOpening(bool value)
{
// do stuff when the Opening event is triggered
}
}
EDIT: my browser failed to load the second part where you have posted the second script and the error message.
You get the error because you have declared the event delegate as a function that takes 1 boolean argument and has no return value ( void OpenEventHandler(bool wasOpened) but in the second script you set the event handler to the method that doesn't have arguments, therefore it doesn't match the delegate signature.
You need to change
private void CalledFromFunction()
to
private void CalledFromFunction(bool value)
to get the method match the delegate.
You got error No overload for 'CalledFromFunction' matches delegate 'MyEventManager.OpenEventHandler' because OpenEventHandler is a delegate with bool type parameter. So method CalledFromFunction requires a bool type parameter.
Second script:
private MyEventManager eventMan;
private void Start () {
eventMan= (MyEventManager ) GameObject.FindObjectOfType (typeof (MyEventManager ));
browserOverlay.Opening+= this.CalledFromFunction;
}
private void CalledFromFunction(bool value)
{
Debug.Log("Is Called");
}
There is a video tutorial on unity official website called:
Events: Creating a simple messaging system.
They create a Event Manager or Messaging System.
I watch it and it was very helpful so I create that system on my game and now decided to instead of using UnityEvent and UnityAction use delegate and event which is better performance and good practice. So here is my code [StopListen() function not included yet]:
public class EventManager : MonoBehaviour
{
public delegate void GameEvents();
public event GameEvents onGameEvent;
private static EventManager _instance = null;
public static EventManager Instance
{
get
{
if (_instance == null)
{
_instance = FindObjectOfType(typeof(EventManager)) as EventManager;
}
if(!_instance)
{
Debug.LogError("Put a GameObject with this scrip attach to it on your scene.");
}
else
{
_instance.InitializeEventDictionary();
}
return _instance;
}
}
private Dictionary eventsDictionary;
void InitializeEventDictionary()
{
if (eventsDictionary == null)
eventsDictionary = new Dictionary();
}
public void Listen(string eventName, GameEvents gameEvent)
{
GameEvents thisEvent = null;
if (Instance.eventsDictionary.TryGetValue(eventName, out gameEvent))
{
thisEvent += gameEvent;
}
else
{
thisEvent = new GameEvents();
thisEvent += gameEvent;
Instance.eventsDictionary.Add(eventName, thisEvent);
}
}
public void TriggerEvent(string eventName)
{
GameEvents thisEvent;
if(Instance.eventsDictionary.TryGetValue(eventName, out thisEvent))
thisEvent.Invoke();
}
}
In my Listen() function this line thisEvent = new GameEvents(); gets me in trouble and I don't know how to fix it! (Help!) :-)
[PS] :
Is delegate and event have better performance then UnityEvent and UnityAction ?
What more should be or must be add to this code, to make it more efficient?
You must define what should invoked when the delegate is accessed, otherwise if nothing should be done the delegate is not necessary.
Like a lamda:
thisEvent = new GameEvents(() => { Console.WriteLine("TODO");});
Or a method:
thisEvent = new GameEvents(Target);
private void Target()
{
throw new NotImplementedException();
}
may be have a look at https://www.codeproject.com/Articles/624575/Delegate-Tutorial-for-Beginners
For execution time, I think best is to make a test and see what performs better.
I'm used to using delegate EventHandler for event callbacks but now I'm attempting to use event Action for invoking events. I couldn't find much info on how this can be used properly anywhere so I'm hoping someone can point me in the right direction.
I have an Action event handler that handles string objects. In my subscriber class I have public event Action<string> UploadProgress;. The event handler is invoked like this:
UploadProgress.Invoke(string.Format("sending file data {0:0.000}%", (bytesSoFar * 100.0f) / totalToUpload));
The listener class is subscribed to this event as below:
uploadFile.UploadProgress += uploadFile_UploadProgress;
void uploadFile_UploadProgress(string obj)
{
var prog = obj;
}
When the event is invoked, I get System.NullReferenceException: Object reference not set to an instance of an object. I'm not sure what I need to change in the subscriber class to avoid this error. Can someone tell me the proper way to use event Action or provide me the link to where I can read up on it? I know how to use the normal Action but confused about declaring it as an event. Any help is appreciated.
This way is much better, send bytesToUpload and totalToUpload through event, instead of the whole Action (right sample):
internal class Program
{
private static void Main(string[] args)
{
SomeClass someClass = new SomeClass();
someClass.UploadProgress += SomeClass_UploadProgress;
someClass.DoSomeUpload();
}
private static void SomeClass_UploadProgress(object sender, UploadEventArgs e)
{
string s = string.Format("sending file data {0:0.000}%", (e.BytesSoFar * 100.0f) / e.TotalToUpload);
Console.WriteLine(s);
}
}
public class UploadEventArgs : EventArgs
{
public float BytesSoFar { get; set; }
public float TotalToUpload { get; set; }
}
public class SomeClass
{
public event EventHandler<UploadEventArgs> UploadProgress;
public void DoSomeUpload()
{
if (UploadProgress != null)
{
UploadEventArgs e = new UploadEventArgs();
e.BytesSoFar = 123f;
e.TotalToUpload = 100000f;
UploadProgress.Invoke(this, e);
}
}
}
I am making a game and I'm trying to create an way for objects to handle collisions with each other. I want to do something like:
//Imaginary C#
public SomethingThatCollides()
{
CollisionEvent<ObjectA> += CollisionWithObjA;
CollisionEvent<ObjectB> += CollisionWithObjB;
}
void CollisionWithObjA(ObjectA other)
{
//Do something
}
void CollisionWithObjB(ObjectB other)
{
//Do something else
}
When, say, CollisionEvent<ObjectA> is raised (perhaps by some collision checking code), CollisionWithObjA should get called. Same for CollisionWithObjB; when a collision with ObjectB is detected, it will raise the CollisionEvent<ObjectB> event which results in CollisionWithObjB getting called.
Is something like this possible?
Here is the thing, if class is generic and it has static field, it can work like a dictionary with key being type
public class Something {
public class EventsHolder<T>
{
static event Action<T> CollideEvent;
}
public void AddEvent<T>(Action<T> collisionEvent)
{
EventsHolder<T>.CollideEvent = collisionEvent;
}
public void RaiseCollision<T>(T Obj)
{
var Event = EventsHolder<T>.CollideEvent;
if (Event != null) Event(Obj);
}
}
Downside is that it uses static fields which can be inapropriate.
In this case you can use code #Daniel posted.
You can't really create a generic event like that. I suggest you create a special event arguments class that also encapsulates the collided object and check for its type in the event handler method:
public class CollisionEventArgs : EventArgs {
public object Object {
get; private set;
}
// ...
}
You'll need a special dispatcher method to use it:
class SomethingThatCollides {
public SomethingThatCollides(CollisionManager cm) {
cm.CollisionEvent += CollisionWithObj;
}
void CollisionWithObj(object sender, CollisionEventArgs args) {
if (args.Object is ObjectA) {
CollisionWithObjA((ObjectA)args.Object);
}
else if (args.Object is ObjectB) {
CollisionWithObjB((ObjectB)args.Object);
}
}
// ...
}
Or, you can try to solve this with double-dispatching, without using C# events. Look at wikipedia for a collision example.
That's uggly, but...You could have a dicionary of events by type:
Dictionary<Type, object> MyEventsByType;
event Action<A> CollisionEventA;
event Action<B> CollisionEventB;
event Action<C> COllisionEventC;
void Initialize()
{
MyEventsByType = new Dictionary<Type, object>();
MyEventsByType.Add(typeof(A), CollisionEventA);
MyEventsByType.Add(typeof(B), CollisionEventB);
MyEventsByType.Add(typeof(C), CollisionEventC);
}
void RaiseCollision<T>(T Obj)
{
Action<T> Event = (Action<T>)MyEventsByType[typeof(T)];
if (Event != null) Event(Obj);
}