Friday, 13 March 2015

How to use Delegates in Unity. Like a BOSS.



Delegates are a programming language feature that allows to invert the dependencies in the classes of your program. Delegates are also referred as Events or Callbacks. The idea behind them is exactly the same as a subscription magazine. Anyone can subscribe to the service and they will receive the update at the right time automatically. This is a super powerful way of designing the architecture of your game to achieve the next level in code quality.

If you enjoyed my previous article about how to use Lerp, this is the second of many simple tutorials to learn how to use the full potential of Unity following what I found out are the best practises. Follow this easy examples you will have projects that are not a nightmare to maintain after a couple of weeks of development. :)

If you started to program in Unity as your first serious programming job, it may be even possible that you don't know what delegates, callbacks or observers are. Don't be scared, we will talk about everything in detail. Anyway I recommend you to take a look at the official tutorials here and here if you haven't done it already and come back here to answer the rest of your questions. :D

One call at the right time is better that 60 calls a second until they pick up.

Why do I need delegates? Imagine a complex game with a PlayerController script and a bunch UI elements with the following conditions:
  • Your player controller script cannot be responsible of updating every of the UI elements because that will prevent artists from changing the UI 
  • The UI elements cannot afford to ask for Updates every frame because that will ruin your efficiency.
We need to find a way for the player to notify changes to the different anonymous UI elements. This is what delegates do. This is how every good UI system like NGUI or uGUI out there works. In this case we will take the chance to learn a little bit about unity 4.6 uGUI aswell :)

In a nutshell, delegates are like a list of functions. You can call your delegate like a normal function and whenever this happens the delegate it will automatically call his functions for you. Like any dynamic list of elements you can add and remove new methods and this is called subscription.


How does a delegate looks like?

// This is a delegate, allows other people to subscribe
// And be notified automatically
// Allows to return values and input paramenters
public delegate void OnSomethingHappened();

// This is an event, allows to protect delegates
// from external modification making them safer
public event OnSomethingHappened onSomethingHappened;


How does a delegate works?

// 1. Some method is subscribed to the delegate
void OnEnable() {
 // We subscribe when we want
 onSomethingHappened += CallMeLater;
}

// 2. The delegate is eventually called
void TimeToCall() {
 // Make sure we there is anyone subscribed
 if (onSomethingHappened != null)
  onSomethingHappened();
}

// 3. This method will be called
// It matches "OnSomethingHappened()" with
// the exact same return value and input parameters
public void CallMeLater(){
 DoTheStuff();
}

// 4. We unsubscibe when we don't want more calls
void OnDisable () {
 // We remember to unsubscribe to avoid memory leaks!
 onSomethingHappened -= CallMeLater;
}

This is all pretty theoretic stuff and may look to confusing if you don't know what is going on or what are we trying to achieve. I hope it will be easier to grasp with this example:



Coins, coins COINS! Strangely mesmerizing :)

Creadits for the art: Kenney.

In this example there is CoinSpawner that spawn 7 Coins at the beginning of the game. The goal of this spawner is to have always 7 coins in the game. The problem is that this coins have a random life spawn and we don't want to constantly check if the coin I spawned is alive or dead. That's why at the moment of creation we subscribe for the event of death of the coin.

// Spawns so there is always the same amount of prefabs
public class CoinSpawner : MonoBehaviour {

 // The prefab we will spawn
 public CoinController prefab;
 // The number of prefabs we will have at all time
 public int prefabsCount = 1;

 // Delegates to notify the UI
 public delegate void OnSpawn();
 public event OnSpawn onSpawn;

 // Create the first coins so there is always
 // the same amount on the screen
 void Start () {
  for (int i = 0; i < prefabsCount; i++){
   SpawnPrefab();
  }
 }

 // Helper function to spawn prefabs and subscribe
 public void SpawnPrefab (){
  // Spawn the coin
  CoinController newCoin;
newCoin = (CoinController)Instantiate(prefab,
                                      transform.position,
                                      transform.rotation);
  // Subscribe so the other class handles notification automatically
  newCoin.onDied += OnCoinDied;

  // Notify if any UI is listening
  if (onSpawn != null)
   onSpawn();
 }
	
 // Function that will be called automatically when the coin dies
 public void OnCoinDied (CoinController coinController){
  // We unsubscribe to avoid memory leaks
  coinController.onDied -= OnCoinDied;

  // Spawn a new coin
  SpawnPrefab();
 }
}


When the Coin is created it will automatically assign itself a random lifespan and when the time comes it will notify to anyone listening. It's important to note that in this approach the coin doesn't know anything about the CoinSpawner, this is a very good result of the use of delegates. This kind of code with few dependencies is called modular and greatly increases the chances that you can reuse this exact code behaviour in a different area of your game or even in another game.

// Destroy self after a random time and notify
public class CoinController : MonoBehaviour {

 // Life variables
 public float minLifeTime = 1.0f;
 public float maxLifeTime = 2.0f;

 // Physics variables
 public Vector2 minForce;
 public Vector2 maxForce;

 // Delegates to notify
 public delegate void OnDied(CoinController coinController);
 public event OnDied onDied;
	
 // Called automatically thanks to MonoBehaviour
 void Start () {
  // Automatic destroy after random time
  Invoke ("Died", Random.Range(minLifeTime, maxLifeTime));

  Rigidbody2D myRigidbody = GetComponent< rigidbody2d>();
  // Apply random force if possible
  if (myRigidbody != null){
   // Random force at the position of the coin
   Vector2 randomForce;
   randomForce = new Vector2(Random.Range(minForce.x, maxForce.x),
 			     Random.Range(minForce.y, maxForce.y));
   myRigidbody.AddForceAtPosition(randomForce, transform.position);
  } 
 }

 // Called automatically when time is up thanks to Invoke
 void Died () {
  // Notify if anyone is listening
  if (onDied != null)
   onDied(this); 
  // Destroy self
  Destroy(gameObject);
 } 
} 

Additionally UI elements should be always using delegates whenever possible. Updating UI values every frame can be acceptable for your small game but do you think any of the great game you know was ever programmed in such a way? It's always good to learn from the best examples :)

// Label that shows the amount of coins spawned
public class LabelCountController : MonoBehaviour {

 // The coin spawner that we will be counting
 public CoinSpawner coinSpawner;

 // Label that we are controlling
 public Text label;

 void Awake () {
  // If not assigned we find a random one
  if (coinSpawner == null)
   coinSpawner = GameObject.FindObjectOfType< coinspawner>();

  // If we have an spawner we subscribe
  if (coinSpawner != null) 
   // Subscribe for spawn events
   coinSpawner.onSpawn += IncreaseCount;

  // Get the label reference
  if (label == null)
   label = GetComponent< text>();
 }

 void OnDestroy () {
  // We unsubscribe to avoid memory leaks
  coinSpawner.onSpawn -= IncreaseCount;
 }

 // Called automatically when a new coin is spawned
 public void IncreaseCount() {
  // Turn the string into a int, add and reconvert to string
  label.text = (int.Parse(label.text) + 1).ToString();
 }
}

This is the end of the tutorial. As you can see, using delegates and events can have a high learning curve, but once you will be used them you will see them everywhere. There are many creative ways of using them as well, like subscribing many times the same method to a delegate to create a sort of loop or using them to notify about animation or sound events. Additionally delegates can be static as well but I don't recommend it since it can easily lead to memory leaks.

Whenever you are using delegates remember!  
  • Use events together with delegates for safety.
  • Always unsubscribe to avoid memory leaks and null references
  • Always check before calling if anyone is subscribed to the event.
  • Thanks to Vectrex for this tip: instead of the null check (and the potential bugs from forgetting it) you can initialize it like this: "event SimpleDelegate OnTestEvent = delegate {};"
  • Last point is not recommended by SubZeroGaming as it will cause problems when you try to serialize/deserealize it.


King Aerys II was not good at delegating and you see what happened...

Here is the link to the complete example posted on github.

Here are the links to the official documentation: delegates, events and Invoke.

30 comments:

  1. Great tutorial on delegates, well explained and easy to follow.

    ReplyDelete
  2. Thats some handy information! It was a little confusing to process until I actually implemented it, then it was like dude... why dont i do this *everywhere*?

    ReplyDelete
    Replies
    1. Yeah! I remember the exact same feeling! ^^

      However don´t fall into the trap of delegates who call second delegates who call first delegates again... it gets evil... :D

      Cheers! :)

      Delete
  3. Thanks for providing the code/examples from this tutorial on GitHub. I always appreciate being able to comb over someone else's code and (hopefully) snatch the treasures of learning from others. :)

    ReplyDelete
    Replies
    1. Thanks to you!
      More tutorials with code repositories are on the way! :)

      Delete
  4. Hi, Thanks for sharing. Let me add something, please correct me if I'm wrong or anything as I'm just trying to be constructive here :)

    Using delegates this way seems like a good way of "communication" for components within the same game object or for really small scale games/app. Or when there is an obvious parent/children relationship between 2 objects, but the need for callee to have a reference of the caller is not ideal (or am I missing something?). If 50 different objects of different type are supposed to generate events that affect the UI you woudn't want 50 explicits references to these objects in your UI scripts.

    I personally use the Observer pattern or a more global event system when required.

    Anyway, please share your thoughts. Really like your blog!

    ReplyDelete
  5. Hi, it is so great tutorial, thanks so much

    ReplyDelete
  6. I became a delegate to the UN after reading this post..
    :) :)
    Great post bro! Thanks!

    ReplyDelete
  7. excellent tutorial, easy to read & understand. Thx man!

    ReplyDelete
  8. I have read this post. Collection of post is a nice one ios swift online training

    ReplyDelete
  9. If "delegates" were simply called "callback lists", all would be clear from the get-go.

    ReplyDelete
  10. labelText.text = (int.Parse(labelText.text) + 1).ToString();
    cannot work. I have compare many times

    ReplyDelete
  11. Great Share! Thank you so much for sharing this wonderful information. Learn Database Online Courses at FolksIT.

    ReplyDelete