Get the FULL version

Using C# delegates in Unity3D scripts

Using C# delegates in Unity3D scripts thumbnail

When Unity3D 3.0 came out, it not only fixed a lot of bugs and added features, but it also upgraded the Mono version being used, including C# language features like namespace support, linq and delegates. This post is going to be about the latter, explaining what is a delegate and what benefits it could bring when using it to develop games on Unity3D.

Basically, in C#, a delegate is a reference to a method or to a group of methods that have the same signature (returns the same type and has the same parameters). A better explanation can be found at Microsoft’s MSDN C# documentation:

A delegate is a type that safely encapsulates a method, similar to a function pointer in C and C++. Unlike C function pointers, delegates are object-oriented, type safe, and secure. The type of a delegate is defined by the name of the delegate.

That being said, let’s see how to use a delegate with an example. Imagine a class that controls the enemies from a specific level of a game. All enemies have a simple behavior: after one sees the player’s character, it starts to chase it. On top of that, all the other enemies are warned of the player’s position and they start chasing it too.

So, the code for a class that does that would be:

using UnityEngine;
using System.Collections;

public class ReactiveEnemy : MonoBehaviour 
{
	//a variable to store this game object's Transform
	private Transform myTransform;
	//a variable to store the player's character Transform
	private Transform playerTransform;
	/*a static boolean variable to tell if the player has collided with
	any enemy trigger*/
	public static bool hasCollided = false;
	
	//Initialization code
	void Awake()
	{
		//get the game object the script is attached to
		myTransform = this.GetComponent<Transform>();
		//get the player's Transform
		playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();
	}
	
	//runs every game cycle
	void Update()
	{
		//checks if the player has collided with any trigger
		if(hasCollided)
		{
			//follow the player
			Follow();
		}
	}
	
	//this method makes the enemy follow the player
	private void Follow()
	{
		//look at the player
		myTransform.LookAt(playerTransform);
		
		/*only follow the player if this enemy is 
		4.5 units away from the player*/
		if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f)
		{
			//move the enemy
			myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);
		}
	}
}

Here’s how this code works: at the Awake() method it finds the player’s transform and gets the Transform the script is attached to. At the Update(), it checks if the player’s character has been seen. If it does, it executes the Follow() method.

Also, there is the need to attach a trigger to each enemies’ game object. It will serve as the area the enemies can detect the player. The following trigger code just changes the static hasCollided variable of the ReactiveEnemy script above to true, after the player’s character touches it:

using UnityEngine;
using System.Collections;

public class EnemyTrigger : MonoBehaviour 
{
	//if something collided with the trigger
	void OnTriggerEnter(Collider col)
	{
		//if the player collided with the trigger
		if(col.gameObject.tag=="Player")
		{
			//set hasCollided static variable to true
			ReactiveEnemy.hasCollided = true;
		}
	}
}

This script should be self explanatory. It’s all good, and it works, but for every enemy these scripts are attached to, they are going to get the player’s transform at every update of the game and check if the player’s character has been seen by any of the enemies (the 10th line of the first script). More enemies, means more of the same if statement that will be checked again, using resources at an operation that has already been completed before.

There’s another problem: what if we need to add another action to the enemies, instead of follow, like fly, for example? Sure, we could create another class or even use the same one and pass a boolean to check if the enemy is a “follower” or a “flyer”, although that would require another if statement to check that, every game cycle.

For these kind of situations we could use a delegate, to encapsulate all the methods that have the same signature, and then, check just one time if the player has been “seen”, executing the desired enemy actions based on the referenced methods. So, to explain how to do that, here’s how to code the same “ReactiveEnemy” class using delegates. This class will be broken into two: one to handle the delegates and to check if the player has been “seen” (AllEnemiesClass) and the other one that will actually contain the methods that make the enemies react to the player (EnemyActions). Here’s the code to the AllEnemies class, explained step by step:

using UnityEngine;
using System.Collections;

public class AllEnemies : MonoBehaviour 
{
	//a variable to store the player's character Transform
	private Transform playerTransform;
	/* a static boolean variable  to tell if the player has collided with
	any enemy trigger*/
	public static bool hasCollided = false;
	
	//an array of game objects, to store every single enemy in the scene
	private GameObject[] allEnemies;
	
	/*sets a delegate called 'AllEnemyActions', that returns void and takes
	a Transform as a parameter*/
	private delegate void AllEnemyActions(Transform pTransform);
	
	/*now that the 'AllEnemyActions' delegate signature is set, we
	instantiate a delegate, naming it as 'aaaDelegate'*/
	private AllEnemyActions aaaDelegate;
	
	void Awake()
	{
		//get the player's character Transform
		playerTransform = GameObject.FindWithTag("Player").GetComponent<Transform>();
		
		//get all enemies that are in this scene, and have the 'Enemy' tag
		allEnemies = GameObject.FindGameObjectsWithTag("Enemy");
		
		/* here, the delegate is instantiated. It takes a method as 
		* a parameter, meaning that the FollowPlayer method from 
		* the first enemy of the array 'allEnemies' is now being wrapped
		* by the aaaDelegate. So if we call the delegate right now,
		* by writing:
		* aaaDelegate(playerTransform); 
		* it would be the same as writing:
		* allEnemies[0].GetComponent<EnemyActions>().FollowPlayer();
		*/
		aaaDelegate = new AllEnemyActions(allEnemies[0].GetComponent<EnemyActions>().FollowPlayer);
		/* now, we add other methods that have the same signature, so
		 * the delegate can reference them, when it's called.*/
		aaaDelegate += allEnemies[1].GetComponent<EnemyActions>().FollowPlayer;
		aaaDelegate += allEnemies[2].GetComponent<EnemyActions>().FollowPlayer;
		aaaDelegate += allEnemies[3].GetComponent<EnemyActions>().FollowPlayer;
		aaaDelegate += allEnemies[4].GetComponent<EnemyActions>().FollowPlayer;
		aaaDelegate += allEnemies[5].GetComponent<EnemyActions>().FollowPlayer;
		
		/*NOTE: It is possible to use a loop to add these methods as
		* references to the delegate. To remove a reference to a method,
		you will need to use the '-=' operator. */
		
	}
	
	void Update()
	{
		//checks if the player has collided with any trigger
		if(hasCollided)
		{
			//call the delegate
			aaaDelegate(playerTransform);
			
			/*The above line is the same as calling every FollowPlayer()
			* method from every GameObject has the 'Enemy' tag. */
		}

	}
}

First, we define what kind of signature this delegate has (line 17). Than, after the delegate has been set, we initialize it at the Awake() method at line 40. It is required to add a method that will be referenced with the delegate to instantiate it, so we add one from the game objects in the allEnemies array. Then, we add the other methods in the following lines (43 to 47).

At the Update(), all we need to do is to call the delegate named aaaDelegate, passing the playerTransform as a parameter. And that’s it! All the methods added as references to the delegate will now be executed. The hasCollided variable will only be checked one single time and we only needed to get the player’s character Transform once, and not in every Awake method for each enemy in the scene.

But where the FollowPlayer() method comes from? Remember that we had broken the ReactiveEnemy into two. This way, the enemy FollowPlayer() method is in this other script, named EnemyActions:

using UnityEngine;
using System.Collections;

public class EnemyActions : MonoBehaviour 
{
	//a variable to store this game object's Transform
	private Transform myTransform;
	
	void Awake()
	{
		//get the game object the script is attached to
		myTransform = this.GetComponent<Transform>();
	}
	
	//this method makes the enemy follow the player
	public void FollowPlayer(Transform playerTransform)
	{
		//look at the player
		myTransform.LookAt(playerTransform);

		/*only follow the player if this enemy is 4.5 units away from the
		 * the player*/
		if(Vector3.Distance(myTransform.position,playerTransform.position)>=4.5f)
		{
			//move the enemy
			myTransform.Translate(Vector3.forward * Time.deltaTime* 0.5f);
		}
	}
}

If there was the need to add another action to the enemies, the only thing required to do is to add a new method to this script, like this:

	//...

	//this method makes the enemy fly
	public void Fly(Transform pTransform)
	{
		//if the player is at 4.5f units away
		if(Vector3.Distance(myTransform.position,pTransform.position)<=4.5f)
		{
			//make it fly
			myTransform.Translate(Vector3.up * Time.deltaTime);
		}
		else
		{
			//make it land
			if(myTransform.position.y >= 0.525528f)
			{
				myTransform.Translate(-Vector3.up * Time.deltaTime);
			}
		}
	}

	//...

After that, we would only need to add this method as a reference to the delegate on the “AllEnemies” script, like this:

/*Instead of (line 47):
aaaDelegate += allEnemies[5].GetComponent().Follow;*/
//Write:
aaaDelegate += allEnemies[5].GetComponent().Fly;

And we wouldn’t have to change anything else, because the delegate is just a reference to one or more methods – the way it is called would be exactly the same!

The code for the trigger will be the same as the one that was show above. Delegates gives a lot of flexibility to the code, especially when it comes to re-factoring. It allows to think on the general rules of the game, and work on the methods without knowing their exact implementation. Think of delegates as interfaces that can be implemented and de-implemented at run-time. It is also possible to remove referenced methods from it (using the ‘-=’ operator) and combine delegates (by adding them).

Here’s a Unity3D project with all the code on this post:

8 Comments to “Using C# delegates in Unity3D scripts”

  1. Tiaan says:

    Hey, thanks man for the nice and clear explanation. This is awesome.

    Tiaan

  2. Duane says:

    This was really useful for me, thanks for taking the time.

  3. Lenn Dolling says:

    Sweet stuff. Keep it up!!

  4. Shawnzxx says:

    Wonderful tutorial, sweat. It’s really made me confuse before I read this article. Thanks

  5. Jeet says:

    Very clear explanation of Delegates with example. It gives an idea where we can use them in code. Thanks a ton..

  6. Vitor says:

    Awesome stuff here! I finally understood delegates. The explanations are great, and even better: the examples are related to Unity, not just C#. Thanks a ton!

Leave a Comment

Post Comments RSS