Simonas Holcmann
Simonas Holcmann

Reputation: 41

What is the most optimal way of communication between scripts in Unity

Say, for example, that I have a GameObject A with a Manager script attached, which on start spawns in x amount of GameObjects with B script attached.

A different GameObject with script C is supposed to do something when the GameObject with script B says so.

So the questions is, what would be the best way for these three to communicate?

Obviously, Script B could just call Script C, however I feel like this method lacks structure and organisation.

Script A could also have reference to script C, and script B could tell the Script A to act on Script C.

I feel like there is some sort of rule I am supposed to follow, however I haven't come across it yet. Any help is much appreciated!

Upvotes: 3

Views: 8084

Answers (5)

Overbyte
Overbyte

Reputation: 136

Check out this really good article on communicating through Unity's scriptable objects. The removes the need for scripts to have references to each other, facilitating much more modular code. Within that link I shared, I really recommend the links there about Richard Fine and Ryan Hipple's presentations too.

Upvotes: 0

Dave
Dave

Reputation: 2862

Delegates and Events are mostly used for comunication as Programmer wrote.

For better structure and organization I would suggest using a MVC pattern or any other design pattern you like. Here you can find a great example of MVC implementation for Unity3D with simple yet powerful notification system:

Unity with MVC by Eduardo Dias da Costa

In that example you don't need to use delegates/events for communication and you keep everything well organized.

Some of the communication functions used in quoted tutorial in case the link gets deprecated:

1.

// Iterates all Controllers and delegates the notification data
   // This method can easily be found because every class is “BounceElement” and has an “app” 
   // instance.
   public void Notify(string p_event_path, Object p_target, params object[] p_data)
   {
      BounceController[] controller_list = GetAllControllers();
      foreach(BounceController c in controller_list)
      {
         c.OnNotification(p_event_path,p_target,p_data);
      }
   }

   // Fetches all scene Controllers.
   public BounceController[] GetAllControllers() { /* ... */ }

2.

 // This class will give static access to the events strings.
class BounceNotification
{
   static public string BallHitGround = “ball.hit.ground”;
   static public string GameComplete  = “game.complete”;
   /* ...  */
   static public string GameStart     = “game.start”;
   static public string SceneLoad     = “scene.load”;
   /* ... */
}

3.

 // Handles the ball hit event
   public void OnNotification(string p_event_path,Object p_target,params object[] p_data)
   {
      switch(p_event_path)
      {
         case BounceNotification.BallHitGround:
            app.model.bounces++;
            Debug.Log(“Bounce ”+app.model.bounce);
            if(app.model.bounces >= app.model.winCondition)
            {
               app.view.ball.enabled = false;
               app.view.ball.GetComponent<RigidBody>().isKinematic=true; // stops the ball
               // Notify itself and other controllers possibly interested in the event
               app.Notify(BounceNotification.GameComplete,this);            
            }
         break;

         case BounceNotification.GameComplete:
            Debug.Log(“Victory!!”);
         break;
      } 
   }

4.

   // Callback called upon collision.
   void OnCollisionEnter() { app.Notify(BounceNotification.BallHitGround,this); }

Of course you can still implement MVC and use Delegates and Events. It is just to show another way of doing things.

Upvotes: 0

derHugo
derHugo

Reputation: 90872

You could also use the not Unity specific Action delegates. I like to use a static class for that but you could as well implement it in one of your existing classes (as long as you use static members and methods)

E.g.

public static class MyEvents 
{
    public static event Action SomeEvent;

    public static void InvokeSomeEvent()
    {
         // Make sure event is only invoked if someone is listening
         if (SomeEvent == null) return;

        SomeEvent.Invoke();
    }
}

This makes your classes completely independent (well, ok they share the MyEvents class) and easy to modularize.

In script C add a "listener" e.g.

private void Start()
{
    // It is save to remove a listener also if it wasn't there yet
    // This makes sure you are not listening twice by accident
    MyEvents.SomeEvent -= OnSomeEvent;


    // Add the listener for that event
   MyEvents.SomeEvent += OnSomeEvent;
}

private void OnSomeEvent ()
{
    // Do something if SomeEvent is invoked
}

Then somewhere in script B just call

MyEvents.InvokeSomeEvent();

So class B doesn't have to know or care who listens for that event; it just invokes it and cares for it's own business.

On the other side C or (any other class where you add a listener for the event) doesn't have to know/cares where the invoke came from; it just handles it and does its stuff.

Note however, that this also makes debugging a little bit harder since it is not that easy anymore to tell where the invoke came from ;)


Note: You can also add parameters to an Action e.g.

public static event Action<int> SomeParameterEvent;

In this case ofcourse all methods have to also implement that parameter

public static InvokeSomeParameterEvent(int value)
{
    if(SomeParameterAction == null) return;

    SomeParameterEvent.Invoke(value);
}

In C (the listener) you also have to receive the parameters

// name can be changed
private void OnSomeParameterEvent(int value)
{
    //...
}

And ofcourse also call it with the parameter in B

MyEvents.InvokeSomeParameterEvent(someInt);

And than you can take it even on step further and instead of a value or a reference pass a complete delegate method as parameter. See examples here

Upvotes: 0

Programmer
Programmer

Reputation: 125455

Obviously, Script B could just call Script C, however I feel like this method lacks structure and organisation.

True. This is what the GameObject.SendMessage function is used for. Unfortunately, it is slow and I wouldn't recommend it but it's worth mentioning.

If you have many objects that will need to communicate with other objects, implement an event manager with event and delegate. This is the proper way to do it. You can find full EventManager implementation here.

With it, you can register any amount of function to an event with:

EventManager.StartListening("jump", someFunction);

Un-register any function from an event with:

EventManager.StopListening("jump", someFunction);

From there, you can invoke the event on any object listening to it:

EventManager.TriggerEvent("jump");

Upvotes: 4

JCooper
JCooper

Reputation: 19

If A already has a reference to script C, it can pass on this reference to B, when it is created. Thus, B can communicate with C, without going through A.

i.e

Script A:
// variables
     public ScriptC c;

// methods
void SpawnB(){
    // spawn B
    B.setC(c); // B's variable for script C is passed in from A
}

Script B:
// variables
     ScriptC c;
// methods
void setC(ScriptC v){
    c = v;
}

Something along those lines.

Upvotes: 1

Related Questions