Communicating between game objects

In any game, there are planned interactions between any components within the game. These could be as follows:

  • Physics collision tests
  • Reacting to being shot or shooting
  • Opening and closing doors
  • Triggers, switches, or traps
  • Two or more characters talking

There are several ways in which you can achieve this, and each has their own particular traits. The selection of the implementations depends on what you need to achieve. The methods are as follows:

  • Delegates
  • Events
  • Messaging

In this section, we will go through each method in detail and highlight the best uses of each.

Delegates

We encounter delegates in our everyday lives. Sometimes they are managers, sometimes they are subordinates, and they could even be the barista at your local coffee shop. Delegates effectively are methods that accept pieces of work to do on behalf of someone else.

Tip

Another form of delegates is to use the C# generics and the Action or Action<T> methods, which is a shorthand version of the implementations mentioned in the next section. For more information about generics and Action, refer to http://msdn.microsoft.com/en-us/library/018hxwa8(v=vs.110).aspx.

There are two main patterns in which delegates are used: the configurable method pattern and the delegation pattern.

Note

The scripts in this section can be found in AssetsScriptsExamples under the Delegates script.

The configurable method pattern

The configurable method pattern is used when a piece of work or function is passed to another method to be used to complete a task. This pattern is usually used where different pieces of code can perform a common task in unique ways, such as walking, running, or patrolling. All these tasks can be the default behaviors of a character. Refer to the following diagram:

The configurable method pattern

Here, you will have your code calling a delegate method, but the contents of this method can be different depending on what you have set it to.

For instance, refer to the following code:

using System;
using UnityEngine;
public class Delegates
{
  //Define delegate method signature
  delegate void RobotAction();
  //private property for delegate use
  RobotAction myRobotAction;

  void Start ()
  {
    //Set the default method for the delegate
    myRobotAction = RobotWalk;
  }

  void Update()
  {
    //Run the selected delegate method on update
    myRobotAction();
  }

  //public method to tell the robot to walk
  public void DoRobotWalk()
  {
    //set the delegate method to the walk function
    myRobotAction = RobotWalk;
  }

  void RobotWalk()
  {
    Debug.Log("Robot walking");
  }

  //public method to tell the robot to run
  public void DoRobotRun()
  {
    //set the delegate method to the run function
    myRobotAction = RobotRun;
  }

  void RobotRun()
  {
    Debug.Log("Robot running");
  }
}

This means that when the DoRobotWalk method is called, it will set the delegate to the Walk method, and once updated, it will run the Walk behavior. If you call the DoRobotRun public method, it will change the delegate to the Run behavior, and once updated, it will run the Run behavior. This is a very simple kind of state machine with no conditions around.

The delegation pattern

The delegation pattern is used where a method calls out to a helper library, and on completion of the required task, continues on back in the main function, as shown in the following diagram:

The delegation pattern

This is usually used with what you might download from the Web. When the download is finished, we do something with what we have downloaded.

For instance, refer to the following code:

using System;
using System.Collections.Generic;
public class Delegates
{
  public class Worker
  {
    List<string> WorkCompletedfor = new List<string>();
    public void DoSomething(
      string ManagerName,
      Action myDelegate)
    {
      //Audits that work was done for which manager
      WorkCompletedfor.Add(ManagerName);

      //Begin work
      myDelegate();
    }
  }

  public class Manager
  {
    private Worker myWorker = new Worker();

    public void PeiceOfWork1()
    {
    //A piece of very long tedious work
    }

    public void PeiceOfWork2()
    {
      //You guessed it, yet more tedious work
    }

    public void DoWork()
    {
      //Send worker to do job 1
      myWorker.DoSomething("Manager1",PeiceOfWork1);

      //Send worker to do job 2
      myWorker.DoSomething("Manager1", PeiceOfWork2);
    }
  }
}

Alternatively, you could just express it using the C# lamdas, which simply means you don't need to declare separate functions as follows:

public void DoWork2()
{
  private Worker myWorker = new Worker();

  //Send worker to do job 1
  myWorker.DoSomething("Manager1", () =>
  {
    //A piece of very long tedious work
  });

  //Send worker to do job 2
  myWorker.DoSomething("Manager2", () =>
  {
    //You guessed it, yet more tedious work
  });
}

If your delegate also uses a string as a parameter, the preceding example could be used as a download pattern where a helper library does the entire download and just returns the XML asset. This asset can then be unpacked and used in the game in your main function.

Compound delegates

Both the configurable method pattern and delegation pattern are very powerful techniques when used correctly.

Another feature of delegates is that they can be compounded, meaning you can assign multiple functions to a single delegate. Also, when a delegate is called, all the methods assigned to the delegate will run, as shown in the following code. This feature is very handy when you want to chain several common functions together instead of one:

public class WorkerManager
{
  void DoWork()
  {
    DoJob1();
    DoJob2();
    DoJob3();
  }

  private void DoJob1()
  {
    //Do some filing
  }

  private void DoJob2()
  {
    //Make coffee for the office
  }

  private void DoJob3()
  {
    //Stick it to the man
  }
}

You can achieve the same output but with more flexibility by using the following code:

//A more intelligent WorkerManager
public class WorkerManager2
{
  //WorkerManager delegate
  delegate void MyDelegateHook();
  MyDelegateHook ActionsToDo;

  public string WorkerType = "Peon";

  //On Startup, assign jobs to the worker; note this is
  //configurable instead of fixed
  void Start()
  {
    //Peons get lots of work to do
    if (WorkerType == "Peon")
    {
      ActionsToDo += DoJob1;
      ActionsToDo += DoJob2;
    }
    //Everyone else plays golf
    else
    {
      ActionsToDo += DoJob3;
    }
  }

  //With Update, do the actions set on ActionsToDo
  void Update()
  {
    ActionsToDo();
  }

  private void DoJob1()
  {
    //Do some filing
  }

  private void DoJob2()
  {
    //Make coffee for the office
  }

  private void DoJob3()
  {
    //Play Golf
  }
}

This also means it's dynamic and you can add additional functions to the delegate that will be called whenever the delegate is called.

Tip

Word to the wise: only use chained delegates when you absolutely need the flexibility to do so as they are a more complex pattern to implement. They are also difficult to debug should something untoward happen.

Events

We can describe events as "expected announcements". Imagine you have a bat phone at your desk; when it rings, you know it's "Batman" on the other end, usually telling you some trouble has been averted. Events are similar to this pattern where there is a hook; this is where you can listen for something to happen and then do something with that event. When it occurs, additionally, through events, you can pass this information to provide yourself with additional information about what has occurred, as depicted in the following image:

Events

In the following code, events use delegates to describe how they are going to communicate. It defines the form that communication will take and what information will be passed when the event is fired:

//Delegate method definition
public delegate void ClickAction();

//Event hook using delegate method signature
public static event ClickAction OnClicked;

Now when an event needs to be initiated in your class, all it needs to do to notify any other code that is listening to the event is call the event like a method using delegate as the signature.

Tip

However, what you must be careful about is that no one is listening to the event (no one has subscribed to it). To do this, you need to check that delegate is not null before you call it.

Refer to the following code:

void Update()
{
  //If the space bar is pressed, this item has been clicked
  if (Input.GetKeyDown(KeyCode.Space))
  {
    //Trigger the event delegate if there is a subscriber
    if (OnClicked != null)
    {
      OnClicked();
    }
  }
}

With the event exposed, any other class or game object that needs to be informed about the occurrence of the event just needs to subscribe to the event as follows using the += syntax:

void Start()
{
  //Hook on to the function's onClicked event and run the
  //Events_OnClicked method when it occurs
  OnClicked += Events_OnClicked;
}

  //Subordinate method
  void Events_OnClicked()
  {
    Debug.Log("The button was clicked");

  }

void OnDestroy()
{
  //Unsubscribe from the event to clean up
  OnClicked -= Events_OnClicked;
}

Note

It's always a good idea to clean up after yourself and unsubscribe from the events when you no longer need them, as shown in the preceding code, using the -= syntax.

This is a very simple example, but you could imagine exposing an event for when an enemy is destroyed and hooking your score system into it so that the score is incremented every time an enemy dies.

A better way is to write a separate method to call when you need to trigger the event; refer to the following code. In this way, you don't have the preceding code repeated throughout:

//Safe method for calling the event
void Clicked()
{
  //Trigger the event delegate if there is a subscriber
  if (OnClicked != null)
  {
    OnClicked();
  }
}

Now, all you have to do whenever the event needs to be fired is call the Clicked method that is shown in the preceding code, which is always safe and won't crash if there are no subscribers.

As a help, this code is the template I always use when creating an event. To simplify its creation, all you have to do to use it each time is change the name, and if necessary, the delegate signature if you need additional parameters; the following code will tell you how to do this:

//Logging template to send a string/report every time something //happens
public delegate void LogMessage(string message);
public static event LogMessage Log;

void OnLog(string message)
{
  if (Log != null)
  {
    Log(message);
  }
}

Messaging

Communication is a key factor in any game. A lot of times, we just use colliders or physics to notify two components that there is something to be aware of. This is a very basic form of communication. Other times, we use referencing or (in the case of Unity) trawl through the project's hierarchy to find another game object to communicate with or notify.

Unity has its own messaging-type functions, such as SendMessage and BroadcastMessage. Both functions actually implement event-style code (as in the preceding case) without actually declaring events, but they are very slow and shouldn't be used extensively.

The SendMessage function will call a named method on a game object (any method with the same name) with a single optional parameter as follows:

void OnCollisionEnter(Collision col)
{
  col.gameObject.SendMessage("IHitYou");
}

So, it will call the IHitYou method on whatever you will collide with. By default, this will not cause an error to be raised if whatever you collide with does not have the IHitYou method. However, if you wish, you can change this by adding SendMessageOptions when you call SendMessage, as follows:

void OnCollisionEnter(Collision col)
{
  col.gameObject.SendMessage("IHitYou", SendMessageOptions.RequireReceiver);
}

If you want to send a value (there can only be one) with the call, just add it after the method name and before SendMessageOptions (if set).

The BroadcastMessage method works in a similar way but will attempt to run your selected method on the selected gameObject and all its children as follows:

void OnCollisionEnter(Collision col)
{
  col.gameObject.BroadcastMessage("IHitYou");
}

Using either of the methods (as stated) is very slow. This is because it has to try and discover (under the hood) if the game object (and its children if using broadcast) has the method first; it will then attempt to run it. As Unity will not know until your game starts running and whether a game object will have that method, it has to perform this each and every time you try it.

A better way

To break this dependency between the game objects and the need to keep references or the need to discover each other at the design or runtime stage, we need an intermediary that all objects know about, that is, a Manager class.

With this manager class, it will manage the list of game objects that want to listen to the messages and provide an easy way to notify anyone who's listening.

To implement this, we will use the singleton behavior described earlier by creating three simple, reusable components as a test case.

First, we create the manager class itself. So, create a MessagingManager.cs C# script and then replace its contents as follows:

using System;
using System.Collections.Generic;
using UnityEngine;

public class MessagingManager : MonoBehaviour
{
  //Static singleton property
  public static MessagingManager Instance { get; private set; }

  // public property for manager
  private List<Action> subscribers = new List<Action>();
}

The first property is the singleton instance for the manager class, while the second is a list of delegates that will be used to keep track of who needs to be notified.

Next, we add the Awake function to initialize the singleton approach:

void Awake()
{
  Debug.Log("Messaging Manager Started");
  //First, we check if there are any other instances conflicting
  if (Instance != null && Instance != this)
  {
    //Destroy other instances if it's not the same
    Destroy(gameObject);
  }

  //Save our current singleton instance
  Instance = this;

  //Make sure that the instance is not destroyed between scenes
  //(this is optional)
  DontDestroyOnLoad(gameObject);
}

This is the same as before but with a little extra debug information so you can see when it is initialized in the Console window.

Then, we add a method so we can register recipients or subscribers to the messages (with the associated UnSubscribe and ClearAllSubscribers methods), as follows:

//The Subscribe method for manager
public void Subscribe(Action subscriber)
{
  Debug.Log("Subscriber registered");
  subscribers.Add(subscriber);
}

//The Unsubscribe method for manager
public void UnSubscribe(Action subscriber)
{
  Debug.Log("Subscriber registered");
  subscribers.Remove(subscriber);
}

//Clear subscribers method for manager
public void ClearAllSubscribers()
{
  subscribers.Clear();
}

This method just adds the delegate you passed to the manager class to be added to the notification list.

Finally, we add a Broadcast method that tells the messaging system to let all the subscribers know that something has happened; the following code tells us how to do this:

public void Broadcast()
{
  Debug.Log("Broadcast requested, No of Subscribers = " + subscribers.Count);
  foreach (var subscriber in subscribers)
  {
    subscriber();
  }
}

Here, we simply loop through all the subscribers and notify them using their delegates; very simple!

As you can see, this is just a very basic messenger that when called will tell anyone who is listening that something has happened; there will be no extra information, no details, just an event. This is like the fire alarm in your building; when it goes off, you just run—you don't (usually) ask, you don't question—you just know that when that alarm goes off, you need to get out of the building!

To finish this manager class off, simply create an empty game object in your scene and add the script to it. There are ways to do that automatically, but I find this way is cleaner so that you always know what the active agents in the scene are. Later in Chapter 10, The Battle Begins, I'll show you a way to create an editor menu option to do this automatically for you.

Putting this to use is simple. As mentioned before, we need three scripts; we have the manager class, so now we need a client and a broadcast agent.

For the broadcast agent, create a C# script named MessagingClientBroadcast and replace its contents with the following code:

using UnityEngine;

public class MessagingClientBroadcast : MonoBehaviour {

  void OnCollisionEnter2D(Collision2D col)
  {
    MessagingManager.Instance.Broadcast();
  }
}

The preceding code is just a simple example so that when attached to an object with a 2D collider, it will trigger a broadcast. To test, just add it to one or both of the border objects in our game scene. In this way, if the player tries to leave the scene, it will ring the alarm bells.

At the moment though, no one is listening, so let's add a listener/receiver. Create another C# script and name it MessagingClientReceiver. This script will register for events and log in to the Console window with some information about the object it's attached to (obviously, there will be no information from the broadcast event as it has none); the following code will tell you how to do this:

using UnityEngine;

public class MessagingClientReceiver : MonoBehaviour
{
  void Start()
  {
    MessagingManager.Instance.Subscribe(ThePlayerIsTryingToLeave);
  }

  void ThePlayerIsTryingToLeave()
  {
    Debug.Log("Oi Don't Leave me!! - " + tag.ToString());
  }
}

In simple words, when the game object script is attached to a startup, it will register itself with the MessagingManager script, telling the manager class to run the second method in the script when the event occurs. As stated before, this just logs in to the Console window for now so that we have something to see.

Just for fun, also add this script to one or both of the borders in our scene; this is simply because we don't have anything else at the moment. You could add it to the player, making the event as an alarm that goes off and changing the ThePlayerIsTryingToLeave method to cause the player to run in the opposite direction if you wish.

If you run the project now, you should get the following results:

  • One message to tell you that the MessagingManager script has started.
  • One message per subscriber that has registered with the manager (although in the Console window, you may just see 2 next to the event because it is the same).
  • When the event is triggered, you will get one message per subscriber to tell you that they have received it. Note that each message from the client is particular to the game object you attached it to as the message is different.

Now, you could have just executed the preceding code using the Send or Broadcast Unity methods, and it would have been much simpler. However, you should note that since we are using a single manager class, which is a static instance in the scene, at no point should any of the game objects involved need to know about each other. There is no need to search the hierarchy or add components to each other at editing time; it just works.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.11.227