3

Let's assume I have a class which does not inherit from MonoBehavior that looks like this:

public interface IApplication
{
    void run();
}

public class Application : IApplication
{
    public static IServiceManager serviceManager;
    public delegate void ApplicationStartedEvent(object source, EventArgs args);
    public event ApplicationStartedEvent applicationStartedEvent;

    public void run()
    {

    }

    protected virtual void OnApplicationStarted()
    {
        if (applicationStartedEvent != null)
        {
            applicationStartedEvent(this, EventArgs.Empty);
        }
    }
}

And a Unity controller which is a MonoBehavior like this:

public class LoginController : MonoBehaviour 
{
    void Start()
    {
        Hide();
    }

    public void OnApplicationStarted(object source, EventArgs e)
    {
        Show();
    }

    public virtual void Show()
    {
        gameObject.SetActive(true);
    }

    public virtual void Hide()
    {
        gameObject.SetActive(false);
    }
}

How do I connect those two?

Let's assume I have the following Bootstrap code:

public class Setup : MonoBehaviour
{
    Application application;

    void Awake()
    {
        application = new Application();
        application.run();
    }
}

How do I make my LoginController OnApplicationStarted method run when Application run method is being invoked or Dispatch an event that is ApplicationStartedEvent.

An example of how do I think EventManager should look like:

addEventListener(string eventName, Callback callback)

dispatchEvent(Event event)

Here's how ull use it in the LoginController:

EventManager.addEventListener("ApplicationStarted", this.OnApplicationStarted)

And here's how ull use it in the Application:

public void run()
{
    EventManager.dispatchEvent(new ApplicationStartedEvent());
}

Obviously it's just an idea and not have to be the same syntax.

EDIT: I think I have found something that is almost similar to what I am looking for: http://wiki.unity3d.com/index.php/Advanced_CSharp_Messenger

Jacob Cohen
  • 1,232
  • 3
  • 13
  • 33

1 Answers1

2

How do I make my LoginController OnApplicationStarted method run when Application run method is being invoked or Dispatch an event that is ApplicationStartedEvent.

Create an EventManager script that can register and un-register events. This functions should take ApplicationStartedEvent as a parameter. Add one more function that can be used to invoke the subsribed events.

You can then subsribe to the events from the other scripts such as the LoginController script. The subscription should be done in the OnEnable and OnDisable function.

Events can then be invoked from the run() function in the Application script.

Very important:

Please rename your Application script to Application2 or something else. There is a Unity API named Application and you will run into compile-time error if you ever use functions from this class.

I will use Application2 instead of Application in this solution.

EventManager.cs:

Please attach this to an Empty GameObject

public class EventManager : MonoBehaviour
{
    private static EventManager localInstance;
    public static EventManager Instance { get { return localInstance; } }

    private void Awake()
    {
        if (localInstance != null && localInstance != this)
        {
            Destroy(this.gameObject);
        }
        else
        {
            localInstance = this;
        }
    }

    public delegate void ApplicationStartedEvent(object source, EventArgs args);
    private event ApplicationStartedEvent applicationStartedEvent;


    public void dispatchEvent(object source, EventArgs args)
    {
        foreach (ApplicationStartedEvent runEvent in applicationStartedEvent.GetInvocationList())
        {
            try
            {
                runEvent.Invoke(source, args);
            }
            catch (Exception e)
            {
                Debug.LogError(string.Format("Exception while invoking" + runEvent.Method.Name + e.Message));
            }
        }
    }

    public void registerEvent(ApplicationStartedEvent callBackFunc)
    {
        applicationStartedEvent += callBackFunc;
    }

    public void unRegisterEvent(ApplicationStartedEvent callBackFunc)
    {
        applicationStartedEvent -= callBackFunc;
    }
}

Application2.cs:

public class Application2 : IApplication
{
    public void run()
    {
        OnApplicationStarted();
    }

    protected virtual void OnApplicationStarted()
    {
        EventManager.Instance.dispatchEvent(this, EventArgs.Empty);
    }
}

LoginController.cs:

public class LoginController : MonoBehaviour
{

    void Start()
    {
        Hide();
    }

    public void OnApplicationStarted(object source, EventArgs e)
    {
        Show();
    }

    public virtual void Show()
    {
        gameObject.SetActive(true);
    }

    public virtual void Hide()
    {
        gameObject.SetActive(false);
    }

    void OnEnable()
    {
        //Subscribe to event
        EventManager.Instance.registerEvent(OnApplicationStarted);
    }

    void OnDisable()
    {
        //Un-Subscribe to event
        EventManager.Instance.unRegisterEvent(OnApplicationStarted);
    }
}
Programmer
  • 121,791
  • 22
  • 236
  • 328
  • This is pretty neat solution and I might use it. What if I wanted to make my EventManager not a MonoBehavior but just a C# pure class that my LoginController will be able to use? – Jacob Cohen Dec 15 '16 at 11:52
  • @JacobCohen That's called singleton. See the new `EventManager` script [here](http://pastebin.com/yyJEg2Tv) – Programmer Dec 15 '16 at 12:34
  • 1
    Chapeau! This is great! It will allow me to develop my application layer by itself and only handle the views in Unity. – Jacob Cohen Dec 15 '16 at 12:45
  • Nice. Good luck with your app. – Programmer Dec 15 '16 at 12:47
  • It seems there's one slight problem with the usage. Looks like `Hide()` and `Show()` at the same frame won't actually turn the gameObject back on. And since the application runs at the same time the login screen is showing it won't actually show it. Disabling it it through the Editor will avoid it being subscribed. Any thought about how to overcoming this? – Jacob Cohen Dec 15 '16 at 14:45
  • It might not be due to the fact it's in the same frame but rather because the gameObject is disabled. The script actually does run, but does not reactivates the object even though SetActive(true) is being called. – Jacob Cohen Dec 15 '16 at 14:51
  • Ha it seems the it just Hiding after showing because the Application run happens before the LoginController Start, thus showing is then hiding it ^_^ – Jacob Cohen Dec 15 '16 at 15:00
  • Hi, I read your last three comments and can tell you that I have no idea the problem you are having. Can you create a new question with this code and clearly describe what problems you are having? I will be out for hours but will answer when I come back if there is no answer yet. – Programmer Dec 15 '16 at 15:39
  • Though the code is right and working, it is quite wrong pattern. The principle of event is that it can only be called by its own class. But this pattern wraps it in a method so anyone can call it, real bad. It would be like preaching encapsulation and then showing an example using reflection to access private members. http://stackoverflow.com/questions/4378339/raise-an-event-of-a-class-from-a-different-class-in-c-sharp – Everts Dec 16 '16 at 04:50
  • @Everts If you follow **all** C# specific rules when working with Unity, you won't be able to accomplish anything. Some of these rules don't apply to Unity and this is one of them. You can have a central script that connects all other scripts so that when you invoke one function, other functions will be called automatically. I used pure delegate and event here. No reflection involved. The goal is to protect the data inside the class then use functions to expose the functionality. – Programmer Dec 16 '16 at 10:34
  • See here for similar example from [Unity](https://unity3d.com/learn/tutorials/topics/scripting/events-creating-simple-messaging-system) where anyone can invoke the event. So, its fine. The only difference is that I did not use a Dictionary like they did but OP can use dictionary if he wants. It's not needed here though. – Programmer Dec 16 '16 at 10:35
  • But why not just using a public delegate instead? – Everts Dec 16 '16 at 18:56
  • @Everts "But why not just using a public delegate instead?" Encapsulation - By doing this you prevent the user from mistakenly overwriting the subscribed functions with `applicationStartedEvent = listener.OnApplicationStarted;` instead of `applicationStartedEvent += listener.OnApplicationStarted;` OP can make it public if he wants but the reason for that is Encapsulation. It will break when the subscribed functions are gone. – Programmer Dec 16 '16 at 19:34
  • "why using an event when a delegate would do it even more easily" Because OP said "How to subscribe to Events". OP's answer had delegate and event in it so I wanted to stay in that lane. Like I said in my comment above, I could have done this with just a delegate and dictionary. To reply to your deleted UWP comment, You don't program Unity like a standard C# app. There are **some** rules that you shouldn't really care about in Unity. – Programmer Dec 16 '16 at 19:35
  • For example, I have seen many people use so much LINQ, lambda and foreach statement in the Update instead of a normal for loop with if statement inside it. This is ok and recommended in a normal C# program but not ok in Unity since that executes every frame and you will probably instantiate and have many instance of that script running which leads to memory and slow game problems on mobile devices. – Programmer Dec 16 '16 at 19:35
  • I looked at your answer and there are so many questions I have. One of them was already asked by the OP under your answer. Where is `IApplicationListener` defined? How do you invoke the registered functions? application.run(); will not even invoke anything. It is about 50% complete but it is missing lots of stuff. You have a Register function in the Application class then another one in Setup class. Isn't that redundant? – Programmer Dec 16 '16 at 19:36
  • OP wants to subscribe to the event from the LoginController or even any script and be notified when Application2.run is called. I think you should read that question again because it was modified heavily after you and I posted our solutions. I had to delete my original one after I understood what he was doing. – Programmer Dec 16 '16 at 19:36
  • I stopped at Encapsulation on the first comment. You preach encapsulation but allow the event to be called from outside the class. Well let's just stop this whole thing and say that u right, im wrong. Not leading anywhere near something useful anyway. – Everts Dec 17 '16 at 20:14
  • @Programmer I have solved all the issues I had. But thanks for everything. – Jacob Cohen Dec 18 '16 at 09:11