In any game, there are planned interactions between any components within the game. These could be as follows:
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:
In this section, we will go through each method in detail and highlight the best uses of each.
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.
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.
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:
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 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:
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.
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.
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:
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.
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; }
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); } }
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.
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:
MessagingManager
script has started.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.
3.145.11.227