Social Scripting Part 1

It doesn’t take long before even the most basic of programmers realize there is only so much that can be done in a single script.  You really need to craft a system where different objects know about each other and disconnected scripts can talk to each other.  The goal of Part 1 is to introduce several options which Unity has built in to the engine to facilitate these needs.

Direct Reference

One of the first ways many programmers learn to accomplish this goal is by utilizing public properties and public methods.  For example, I created a scene with two GameObjects named “Captain” and “Soldier”.  Each object has a similarly named script attached, each are shown below:

public class Captain : MonoBehaviour
{
	public Soldier soldier;

	void Start ()
	{
		Debug.Log("Captain: Jump Soldier!");
		soldier.Jump();
	}
}
public class Soldier : MonoBehaviour
{
	public void Jump ()
	{
		Debug.Log("Soldier: How high?");
	}
}

In Unity’s inspector, make sure to drag the Soldier object into the “Soldier” field of the Captain’s script or you’ll see a nasty error when running the scene: “NullReferenceException: Object reference not set to an instance of an object

Pros and Cons of Direct Reference

Direct references are very fast and efficient to execute.  However, the larger the system you create the more you may find it difficult to maintain.  One issue is that these two scripts are “Tightly Coupled” which is a nerdy way of saying that they know too much about each other.  Good scripts know as little as possible about other scripts, otherwise, a change in one script can have an unfortunate chain reaction of fixes that need to be propagated all over.

Legacy Messaging (Beginner)

The first step in “De-coupling” this system is to remove the specified dependencies.  The current dependencies in this example are that the “Captain” script must currently have a “Soldier” reference and the “Soldier” script must have a public method called “Jump”.  We can make the system far more flexible and reusable by changing the Captain’s reference from an instance of a “Soldier” script to any instance of a GameObject.  However, GameObject’s don’t have a “Jump” method, so how can we complete the process?  The answer is a feature that all MonoBehaviour’s support called Messaging.  We can send a message to a GameObject and allow that object to figure out if and how it will respond.  See below how the Captain script can be modified to accomplish this goal, and note that you will need to re-link the objects in the inspector since we changed the type of the public property.

public class Captain : MonoBehaviour
{
	public GameObject soldier;

	void Start ()
	{
		Debug.Log("Captain: Jump Soldier!");
		soldier.SendMessage("Jump");
	}
}

If you read carefully, you may have noticed that I said that objects could choose “IF” they respond.  Let’s test this theory.  Comment out the Jump method of the Soldier script and run the scene.  You will see another nasty error, “SendMessage Jump has no receiver!

The SendMessage function has multiple implementations, or in nerd speak, “Function overloading”.  This means that there are a few different ways you can call the same method. The first way was already demonstrated, you simply pass the name of a method you want called.  Each of the other options is like the one before it, but each time adds another parameter to pass.  See below for an example of each of the three:

soldier.SendMessage("Jump");
soldier.SendMessage("Jump", this);
soldier.SendMessage("Jump", this, SendMessageOptions.DontRequireReceiver);

Change the Captain script to use the third example, the one where we can specify that we “DontRequireReceiver”, and then when you send the message and the target chooses to ignore you there won’t be any errors.  Run the scene again to verify for yourself.  It is as if the Captain is speaking to himself.

Uncomment the “Jump” method on the soldier and then change it as follows:

void Jump (object sender)
{
	Debug.Log("Soldier: Yes, " + sender.GetType().Name);
}

Note that with Messaging the Jump method does not need to be public anymore (although it doesn’t hurt anything to leave it public).  Also, we added a parameter called sender, which the Captain script passed along as the second parameter of the “SendMessage” call.  You can pass anything you want, but will need to make sure that the method can accept it.  For example, change the Captain script to use:

soldier.SendMessage("Jump", 50, SendMessageOptions.DontRequireReceiver);

…and the soldier script to use…

void Jump (int height)
{
	Debug.Log("Soldier: I jumped " + height + " units high!");
}

Run this example, and you can see that the integer variable we passed has been correctly received and applied to the soldier’s jump implementation.

Here are a few more examples to consider, where the first line is an example of the way to trigger the message, and then I show the implementation of a method that would potentially observe the message:

SendMessage(“OnGiveMoney”, 324); // Send an integer
void OnGiveMoney (int amount){} // This is okay
SendMessage(“OnGiveMoney”, 324.5f); // Send a float
void OnGiveMoney (int amount){} void OnGiveMoney (int amount){} // This performs a conversion, even if you use the option “DontRequireReceiver"
SendMessage(“OnGiveMoney”, “324"); // Send an string but don't properly listen
void OnGiveMoney (int amount){} // This throws an MissingMethodException, even if you use the option “DontRequireReceiver"
SendMessage(“OnReceipt”, “Joe Schmoe”); // Send a string
void OnReceipt (string customerName){} // This is okay
SendMessage(“OnExit”, this); // Send an instance of an object
void OnExit (object sender){} // This works, but it could be a more specific class to avoid casting, if desired.

If you parent the soldier GameObject to the Captain GameObject, you will have another option for communication while being able to remove the need of a target on the Captain script:

public class Captain : MonoBehaviour
{
	void Start ()
	{
		Debug.Log("Captain: Jump Soldier!");
		BroadcastMessage("Jump", 50, SendMessageOptions.DontRequireReceiver);
	}
}

“BroadcastMessage” will send the message to all of its descendants, so if you cloned the Soldier multiple times, they will all respond.  Let’s try that now, create 5-10 clones of soldier and modify the Soldier script accordingly:

void Jump (int height)
{
	int myJump = UnityEngine.Random.Range(0, 100);
	if (height <= myJump)
		Debug.Log("Soldier: Yes Sir!");
	else
		Debug.Log("Soldier: I can only jump " + myJump + " high.");
}

Running the scene now should result in multiple Debug logs, where some soldiers follow the order without problem, and others need a bit more exercise.

A similar call, “SendMessageUpwards”, would allow any of the soldiers to communicate up the chain of command (to their parent objects in the scene hierarchy) without needing a direct reference.

Pros and Cons of Legacy Messaging

If you look around on forums much, you might notice that “SendMessage”, “BroadcastMessage”, and “SendMessageUpwards” are often not recommended for use in your projects.  They are not terribly efficient.  Part of the speed issue is that they do a lot of extra work in order to make the system easier to use.  “SendMessage” attempts to call the indicated method on every script attached to the target object.  “BroadcastMessage” and “SendMessageUpwards” attempt to call the method on every script of the target object and every script of every object in the hierarchy that they include. There is no way to optimize the system by restricting it to a single targeted script on the object.

For the most part, anyone reading a beginner-level tutorial on Messaging is not likely to be working at a level to push Unity to its limits.  I doubt you need to worry about every little millisecond of processing time.  Therefore, I have no hesitation in recommending these options as valid forms of communication in your projects.  In my opinion, the benefits gained in code reusability and flexibility outweigh the inefficiency of the system.  This is particularly true if you are using it as a result of sporadic events.

Other concerns are that the messaging system is heavily reliant on your object hierarchy. It requires you to make certain assumptions about the way that the object graph will be constructed in your scene. When multiple people are working together on the same project, or you work on it for long enough time to forget, some of those assumptions may not remain true.  You may spend an unfortunate amount of time working through your code trying to figure out what “logical bug” you introduced when the real problem was not code related at all.

Unity doesn’t have a messaging option for reaching other objects at the same level (siblings).  In most cases you can work around this limitation by calling transform.parent.BroadcastMessage(“MethodToCall”) which would then reach an objects siblings, but of course it would also call the original object and all of its descendants as well.  Furthermore, this solution does not work when the object is already at the root level of the scene hierarchy.  The only other workaround is to maintain a direct reference to the target and use “SendMessage”.

One final problem is that this system is restricted to working with Unity GameObjects and scripts inheriting from the “Component” class (note that your scripts will likely inherit from MonoBehaviour which is already a descendant of Component) .  This may not be an issue for beginners, but as you grow more advanced you may find yourself wanting to write scripts that don’t need to inherit from anything so you can use other language features like constructors or so that you can avoid the extra overhead a MonoBehaviour includes.

Don’t worry if you find yourself reaching any of the limitations of this system, there are several more advanced options available.  Some of these are provided by Unity and will be presented next.  Other more versatile solutions will be presented in Parts 2 and 3 of this series.

Unity’s new Messaging system (Intermediate)

Unity released a new messaging system along with their new UI (at the time of this post Unity’s version is 4.6). This system is more efficient than their legacy system at the expense of being a little more complicated to implement. To begin, your messages must be pre-defined in something called an interface.

An interface is similar to the definition of a class with a few differences. Interfaces do not provide any implementation of their methods, they only declare them. Also, a class can “implement” multiple interfaces, but can only “inherit” from one class.

For now, let’s recreate the Messaging example we used earlier with a Captain and Soldier. The interface we will use for the messaging is shown below:

using UnityEngine.EventSystems;

public interface ISuboordinate : IEventSystemHandler
{
	void Jump ();
}

Note that we needed to include a line you may not have seen before “using UnityEngine.EventSystems;” or else you will see a nasty error, “error CS0246: The type or namespace name `IEventSystemHandler’ could not be found. Are you missing a using directive or an assembly reference?

To make a class implement an interface looks pretty similar to making it inherit from a class. In the code below, our Soldier class is “inheriting” the “MonoBehaviour” class and “implementing” the “ISuboordinate” interface. Additional interfaces can be added but they each need to be separated by a comma.

public class Soldier : MonoBehaviour, ISuboordinate
{
	public void Jump ()
	{
		Debug.Log("Soldier: How High?");
	}
}

To send the message, you need a reference to a target as we did in our earlier examples. However, one benefit of this system over the legacy version is that if you forget to assign the reference, you won’t encounter an error. Below is the new implementation of the Captain script:

using UnityEngine;
using UnityEngine.EventSystems;

public class Captain : MonoBehaviour
{
	public GameObject target;

	void Start ()
	{
		ExecuteEvents.Execute<ISuboordinate>(target, null, (x,y)=>x.Jump());
	}
}

The line which triggers the message may look a little strange, but shows a powerful feature of C# called Generics that you may want to explore later. Insert the type of the interface you wish to call between the brackets “” and the system will now only send the message to components on the target which actually implement that interface. You can run the scene now and see interaction between the scripts.

You can pass parameters with this system, but keep in mind that you will need the declaration in the interface to match the implementation and the invoking call.

This Messaging system always requires a target, however, there are a few exposed methods of finding the target, just in case you don’t want to pre-assign it. For example, you can pass a root level object to the ExecuteEvents class and find the first child that implements the target interface:

public class Captain : MonoBehaviour
{
	void Start ()
	{
		GameObject target = ExecuteEvents.GetEventHandler<ISuboordinate>(gameObject);
		ExecuteEvents.Execute<ISuboordinate>(target, null, (x,y)=>x.Jump());
	}
}

Pros and Cons of Messaging

Unity’s new Messaging system resolves several of the issues found with the legacy version. It is more efficient and can target scripts that actually implement the interface needed to observe the message call. I also appreciate that it can gracefully handle null references, although not everyone may agree. Sometimes you want to know a reference is null or you may waste time figuring out why things aren’t working as anticipated. This is easily enough remedied by making your own assertion that the target is not null before using it as the target of a message.

The requirement of a target for every message is unfortunate and can complicate your architecture. The interfaces are also a bit cumbersome to have to repeatedly implement. Still, if these concerns do not stop you, it can be a nice upgrade to the previous system.

Unity Events (Intermediate)

There is one final option for messaging, the UnityEvent. Besides being the most powerful option presented so far, it also serves as a nice segue to Parts 2 and 3 of this series.

Working with UnityEvents is very similar to what you may have already experienced while working with the new UI, such as when attaching an event handler to a button press. However, you can define your own events and are by no means restricted to the events defined by the UI system such as taps and drags.

Modify the Captain script as follows:

using UnityEngine;
using UnityEngine.Events;

public class Captain : MonoBehaviour
{
	public UnityEvent jumpEvent;

	void Start ()
	{
		Debug.Log("Captain: Jump!");
		jumpEvent.Invoke();
	}
}

As was the case with Unity’s new Messaging system, the event system does not actually require any other object to listen. You can run the scene without a soldier to command and no ugly errors will present themselves. Of course without a listener this example is pretty lame. Here is the soldier implementation:

using UnityEngine;
using System.Collections;

public class Soldier : MonoBehaviour
{
	public void Jump ()
	{
		Debug.Log("Soldier: How High?");
	}
}

If you run the scene right now, the soldier method will not actually be triggered. You have two options to get the event to know about its listener. One option is to link it up through the inspector. If you select the “Captain” object you will see that it has a “Jump Event ()” exposed. Click the “+” button to add a listener. Drag the Soldier object into the field and then choose the “Jump” function. Now if you run the scene everything will be linked up and the two objects will communicate properly.

Another option for adding listeners is through script. If you can get a reference to the event, you can use the command “AddListener” to add a method or “RemoveListener” to remove it. For example I could start the scene where we did not have any connections specified in the inspector, but where the Soldier script used the following:

using UnityEngine;
using UnityEngine.Events;
using System.Collections;

public class Soldier : MonoBehaviour
{
	void OnEnable ()
	{
		UnityEvent e = GetComponentInParent<Captain>().jumpEvent;
		e.AddListener(Jump);
	}

	public void Jump ()
	{
		Debug.Log("Soldier: How High?");
	}
}

In this example, the soldier knew how to find the event and add itself as a listener. Many such objects could do the following and the event will be performed on each of them.

Just like with the Messaging architecture, you can pass along parameters in the EventSystem. It also uses Generics, so if you haven’t looked into them yet it would be a good time to do so. Below is an example that uses two parameters, an object sender and an integer value. Note that I created a subclass of the Generic UnityEvent with the parameter types pre-defined.

using UnityEngine;
using UnityEngine.Events;

public class JumpEvent : UnityEvent<object, int> {}

public class Captain : MonoBehaviour
{
	public JumpEvent jumpEvent;

	void Start ()
	{
		Debug.Log("Captain: Jump!");
		jumpEvent.Invoke(this, 50);
	}
}
public class Soldier : MonoBehaviour
{
	void OnEnable ()
	{
		JumpEvent e = GetComponentInParent<Captain>().jumpEvent;
		e.AddListener(Jump);
	}

	public void Jump (object sender, int value)
	{
		Debug.Log("Soldier: How High?");
	}
}

Pros and Cons of UnityEvents

I actually really like Unity’s Event system. It is flexible and fast and has every feature I would need to use in most projects as well as most of the features I want, including the ability to work with non Unity based classes and objects. Because it is flexible enough to connect through an inspector it will appeal to many people for quick prototyping and ease of use while also being robust enough to work in a purely scripted environment.

The only complaint I have at the moment, and it is very minor, is that you need to implement your own subclass of the UnityEvent in order to pass an event with parameters. For this I would probably create a very standard EventHandler, much like the native one you will see in Part 2, that merely includes a system.object parameter to represent a sender and an EventArgs parameter to pass along any other information I might wish to include.

Advertisements

5 thoughts on “Social Scripting Part 1

  1. Jon,
    Great post only correction is that SendMessage as one more override that you didn’t include:
    public void SendMessage(string methodName, SendMessageOptions options)

    Like

  2. Jon,

    I was experimenting and realized that you can make generic methods from the unity events that make it where you don’t have to write a custom class for every variation.

    public class UnityGenericEvent : UnityEvent { }
    public class UnityGenericEvent : UnityEvent { }
    public class UnityGenericEvent : UnityEvent { }
    public class UnityGenericEvent : UnityEvent { }

    Then all you have to do is create events like:
    public UnityGenericEvent onChange = new UnityGenericEvent();

    It appears that unity made there methods abstract which is the only reason you can’t do this already.

    Like

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s