Simple tutorial about events in UnityScript, C# and Boo

Here is a quick tutorial/memo about using events in you scripts. Events are a flexible way to share data across scripts (calling a method with some parameters) without the sender knowing the receiver(s).

The idea

The idea is to perform action (run a method) in some script whenever some particular things happen (the event) in another script. Then, that script may share data about that event, but without any hard link/reference between those scripts.

On one hand, there are the senders that will “send events” -characterized by a name and a series of data (the parameters of a method)- usually by calling a dedicated method on an e vent manager class or object.

On the other hand, there are the receivers that will dedicate some of there methods to handle some particular events each and register them to the event manager (they tell him which method handles which event). The event manager will then call all methods that handles a particular event whenever it is send, passing along the parameters gotten from the sender script. For convenience and consistency, methods that handle a particular event will always have the same name : On[event name]. Ie : OnPlayerDied(), OnTriggerEnter() …

To be clear, there is no direct link or reference between the senders and the receivers (note that a class can be both). The sender sends data to the event manager, which possess references to all receiver’s methods for each event.

So we just need a class that holds a list of methods for each event (the event manager), a way to load/empty those lists, and a way to call the methods in those lists while passing along the parameters of an event.

We will do that first in UnityScript, because I think it’s easier at first glance, then we will do that in C# and Boo where it’s less intuitive.

UnityScript implementation

Create two classes : EventManager and EventUser. In EventManager, create a first event called PlayerTakesDamage whose methods will be stored in a static List<T>. Calling the methods in this list will be done in a static method OnPlayerTakesDamage with just one integer as parameter.

#pragma strict

import System.Collections.Generic;

class EventManager {

  // PlayerTakesDamage event
  static var playerTakesDamageMethods: List.<Function> = List.<Function> ();

  static function OnPlayerTakesDamage (data: int) {
   for (var func: Function in playerTakesDamageMethods)
      func (data);
  }
}

The EventUser class has an OnPlayerTakesDamage method which handles (receives) the event, and prints the amount of damage received, for instance. The class also adds this method to the EventManager’s list then calls the OnPlayerTakesDamage method on EventManager in order to send the event.

#pragma strict

function Start () {
  // Registration of the method to be called
  EventManager.playerTakesDamageMethods.Add (OnPlayerTakesDamage);

  // sending the event...
 EventManager.OnPlayerTakesDamage (10);
}

function OnPlayerTakesDamage (amount: int) {
 print ("The player takes "+amount+" damages.");
}

Drop EventUser on the MainCamera then run the game. “The player takes 10 damages.” should be written in the console whereas the EventUser.OnPlayerTakesDamage method has been called directly from within EventUser. If multiple scripts had registered a similar method, as many lines would have been displayed in the console. This also works with private methods since the EventManager has a reference to the method itself, not to the instance of the script.

You are free to send whatever you need with the event, it can be anything like a class instance or a reference to the gameObject involved, for instance (that’s what is done with OnTriggerEnter which receive a reference to the collider component that just entered the trigger).

C# implementation

The idea stays the same but the implementation changes a bit because C# has no “Function” type. On the other hand, C# has a powerful built-in event system that makes storing and calling methods easier.

C# has no global “Function” type but delegates allows to create types that will match methods signatures. A signature is the return type as well as the type, number and order of the parameters.

Here is how to create a delegate :

delegate void VoidInt (int data);
// VoidInt is now a data type representing all methods
// that returns nothing and has only one parameter of type int

Now instead of using a List<VoidInt>, we will use a special kind of list. A variable whose type is a delegate is actually a list that can contains references to methods of the same type (signature). All methods in that list are called synchronously without looping on it :

using UnityEngine;

public class Test : MonoBehaviour {

  // the delegate
 delegate void VoidInt (int data);
 // the list
 VoidInt methodList;

 void Start () {
   // add two methods in the list :
    methodList += Method1;
    methodList += Method2;

    // call the methods in the list while passing a parameter
   methodList (5);
   // will print in the console :
    // "From Method1 : 5"
   // "From Method2 : 5"

   // you can also remove methods from the list
    methodList -= Method1;
    methodList (10); // will print "From Method2 : 10"
  }

 // two methods
  void Method1 (int whatever) {
   print ("From Method1 : "+whatever);
 }

 void Method2 (int data) {
   print ("From Method2 : "+data);
 }
}

You have all you need to code EventManager.cs :

public class EventManager   {

 // the delegate
 public delegate void VoidInt (int param);

 // PlayerTakesDamage event 
 public static VoidInt playerTakesDamageMethods;
}

As well as EventUser :

using UnityEngine;

public class EventUser : MonoBehaviour {

 void Start () {
   // Registering a method
   EventManager.playerTakesDamageMethods += OnPlayerTakesDamage;
   // Sending the event
    EventManager.playerTakesDamageMethods (10);

   // Unregistering a method, the list is now empty
    EventManager.playerTakesDamageMethods -= OnPlayerTakesDamage;
   // Sending the event
    EventManager.playerTakesDamageMethods  (30); // Will print a "NullReferenceException" error in the console
  }

 void OnPlayerTakesDamage (int amount) {
   print ("The player takes "+amount+" damages.");
 }
}

To prevent the error, we just need to check if the list is empty (is null). So instead of using the list directly, a method EventManager.OnPlayerTakesDamage on will be used.

public class EventManager   {

 // the delegate
 public delegate void VoidInt (int param);

 // The list. Note the "event" keyword
 public static event VoidInt playerTakesDamageMethods;

 public static void OnPlayerTakesDamage (int data) {
   if (playerTakesDamageMethods != null)
     playerTakesDamageMethods (data);
  }

}
using UnityEngine;

public class EventUser : MonoBehaviour {

 void Start () {
   // Registering a method
   EventManager.playerTakesDamageMethods += OnPlayerTakesDamage;
   // Sending the event
    EventManager.OnPlayerTakesDamage (10);

    // Unregistering a method, the lis is now empty
   EventManager.playerTakesDamageMethods -= OnPlayerTakesDamage ;
    // Sending the event does not raise exception anymore
   EventManager.OnPlayerTakesDamage (30);
  }

 void OnPlayerTakesDamage (int amount) {
   print ("The player take "+amount+" damages.");
  }
}

I added the event keyword in the playerTakesDamageMethods declaration. It has two uses : a visual reminder that the variable is a list of method and to prevent to call the methods from outside the class even if the variable is public and static.

Boo implementation

Boo allows you to use events the UnityScript’s or the C#’s way. You can use callable type as a generic “Function” type or to create types that match method signatures, like with delegates.

import UnityEngine

class Test (MonoBehaviour): 

  callable VoidInt (data as int)
  // is a shortcut for : callable VoidInt (data as int) as void

 def Start ():
   test as callable
    test = OnPlayerTakesDamage
    test (5) // print 5
   test = BoolToString
   print(test (false)) // print "true"

   // you can be more precise
    test2 as callable(bool) as string = BoolToString
    print (test2 (false)) // print "false"

    // But test2 may only contain methods that returns a string and take only one boolean as parameter
    // test2 = OnPlayerTakesDamage // would print the error "Cannot convert 'callable(int) as void' to 'callable(bool) as string'."

   // We can also store that type is a variable like what we did with the delegates
    // see VoidInt above
    test3 as VoidInt = OnPlayerTakesDamage 
   // ...

  def OnPlayerTakesDamage (amount as int):
    print ("The player takes "+amount+" damages.");

 def BoolToString (data as bool) as string:
    if data:
      return "true"
   else:
     return "false"

The implementation of the two classes should not be a problem :

import System.Collections.Generic

class EventManager:

    // As in UnityScript :
    // List[of callable] is the Boo equivalent to List.<Fonction>
    public static playerTakesDamageMethods as List[of callable] = List[of callable] ()

    public static def OnPlayerTakesDamage (amount as int):
     for func in playerTakesDamageMethods:
       func (amount)

    // As in C# :
    public static event playerTakesDamageMethods as callable (int)

    public static def OnPlayerTakesDamage (amount as int):
        if playerTakesDamageMethods != null:
            playerTakesDamageMethods (amount)
import UnityEngine

class EventUser (MonoBehaviour): 

 def Start ():
   // Like in UnityScript
    EventManager.playerTakesDamageMethods.Add (OnPlayerScore)
   EventManager.OnPlayerTakesDamage (20)

   // Like in C#
   EventManager.playerTakesDamageMethods += OnPlayerTakesDamage
    EventManager.OnPlayerTakesDamage (10)

 def OnPlayerTakesDamage (amount as int):
    print ("The player take "+amount+" damages.");

To go further

Using a List.<Function> in UnityScript is a choice of my own. I think it’s the best compromise between the ease of implementation and the performance. A non generic collection that store values as Object like System.Collections.ArrayList or UnityEngine.Array and even built-in collections like Boo.Lang.List would have a bigger memory footprint than the List<T>.

The built-in arrays have the best performances, but can’t be resized. This may not necessarily be an issue since you will usually be able to count how many methods to register for each event, allowing you to set the correct size to each array.

You can use built-in arrays of delegates or callables in C# or Boo, but I have no idea of how they perform compared to built-in events.

Instead of creating a delegate as VoidInt in C#, you can also use the generic delegates System.Action et System.Func. You just need to specify the type of the parameter (up to 8 parameters are supported with Mono) and the return type. Action has to be used for methods that returns void, Func for all others methods.

public static event Action<int> playerTakesDamageMethods;

Edit on 31th of November 2012 :

I discovered only recently that UnityScript lets you be as specific as callables in Boo. If “Function” is a global callable type equivalent to “callable” without parameter type or return type, you can use “function” with parameter type and/or return type in the exact same way as callable :

function(int, float)
// or
function(int, float): void
// is equivalent to
callable(int, float)
callable(int, float) as void
delegate void TheDelegate(int p1, float p2)

function(): String
// is equivalent to
callable() as Strng
delegate string TheDelegate()

// you can obviously mix both and have multiple parameters :
function(String, boolean, float): int

// ie :
var myVariable: function(int): String;
var myList = List.<function(int):String>();

Comments are closed