UnityLearn – Beginner Programming – Delegates, Events, and Actions – Pt. 04

October 25, 2019

Beginner Programming: Unity Game Dev Courses

Beginner Programming: Unity Game Dev Courses

Unity Learn Course – Beginner Programming

Delegates, Events, and Actions

Delegates

Delegate: in C#, a type designed to hold a reference to a method in a delegate object

  • Delgates are created using the “delegate” keyword.
  • They are defined by their signature, meaning their return type.
  • Finally they have parameters which they take in, similar to methods.

Using a delegate allowed us to parameterize a method. Using the delegate as a parameter for a method also allows us to use any method which matches that delegate’s signature to satisfy the parameter.

These are some snippets from two scripts, GameSceneController and EnemyController, that show some basics of utilizing delegates:

– In GameSceneController script
public delegate void TextOutputHandler(string text);

public void OutputText (string output)
{
Debug.LogFormat(“{0} output by GameSceneController”, output);
}

– In EnemyController script
void Update()
{
MoveEnemy(gameSceneController.OutputText);
}

private void MoveEnemy(TextOutputHandler outputHandler)
{
transform.Translate(Vector2.down * Time.deltaTime, Space.World);

float bottom = transform.position.y – halfHeight;

if(bottom <= -gameSceneController.screenBounds.y)
{
outputHandler(“Enemy at bottom”);
gameSceneController.KillObject(this);
}
}

For example, in our case we created a public void delegate with a string parameter called TextOutputHandler. Then another one of our methods, MoveEnemy, took a TextOutputHandler as a parameter, named outputHandler. Any method matching the signature of the delegate (in this case, public void with input parameter string) can satisfy the input parameter for the MoveEnemy method. As can be seen in the example, whatever method is passed in will be given the string “Enemy at bottom”.

A delegate used this way is commonly known as a “Callback”.

Events

C# Events: enable a class or object to notify other classes or objects when something of interest occurs.
Publisher: class that sends the event
Subscriber: class that receives/handles the event

Things like Unity’s UI elements use events inherently. For example, the Button script uses events to tell scripts when to activate when a button is clicked. This is different from a basic way of doing inputs which checks every frame if a button is being pressed (which is called “polling”). This is also why creating UI elements in Unity automatically creates an EventSystem object for you.

In C#, events are declared using the “event” keyword, and all events have an underlying delegate type.
C# events are multicast delegates.
Multicast delegate: delegate that can reference multiple methods

To help with my understanding, I tried testing the setup without having an EnemyController parameter to see why it was needed. I discovered it was necessary to pass along the reference so the small event system knew which object to destroy when calling the EnemyAtBottom method. Using Destroy(this.gameObject) or Destroy(gameObject) both just destroyed the SceneController as opposed to the individual enemies. This also helped me understand that adding a method to an event does not pass the method over as an equivalent, it simply means that when that event is called, that any methods assigned to it are also called in their current location in a class. So even though the event was being called in the EnemyController script, the method I added to it was still called within the GameSceneController script, which makes sense.

Example:

In EnemyController script


public delegate void EnemyEscapedHandler(EnemyController enemy);

public class EnemyController : Shape, IKillable
{
public event EnemyEscapedHandler EnemyEscaped;

void Update()
{
MoveEnemy();
}

private void MoveEnemy()
{
transform.Translate(Vector2.down * Time.deltaTime, Space.World);

float bottom = transform.position.y – halfHeight;

if(bottom <= -gameSceneController.screenBounds.y)
{
if(EnemyEscaped != null)
{
EnemyEscaped(this);
}
// Can be simplified to:
// EnemyEscaped?.Invoke(this);
}
}
}

In GameSceneController script:


public class GameSceneController : MonoBehaviour
{
private IEnumerator SpawnEnemies()
{
WaitForSeconds wait = new WaitForSeconds(2);

while (true)
{
float horizontalPosition = Random.Range(-screenBounds.x, screenBounds.x);
Vector2 spawnPosition = new Vector2(horizontalPosition, screenBounds.y);

EnemyController enemy = Instantiate(enemyPrefab, spawnPosition, Quaternion.identity);

enemy.EnemyEscaped += EnemyAtBottom;

yield return wait;
}
}

private void EnemyAtBottom(EnemyController enemy)
{
Destroy(enemy.gameObject);
Debug.Log(“Enemy escaped”);
}
}

I’ve simplified the scripts down to just the parts dealing with the events to make it easier to follow. As I understand it, we create the delegate: public delegate void EnemyEscapedHandler(EnemyController enemy) in the EnemyController script (but outside of the EnemyController class). Within the EnemyController class, we create an event of the type EnemyEscapedHandler, so this event can take on methods with the same signature as EnemyEscapedHandler. Within the MoveEnemy method, we invoke the EnemyEscaped event and satisfy its parameters by passing in this, which is the unique instance of the EnemyController script (after checking that there is a method assigned to this event).

Then in the GameSceneController script, we see that when we instantiate an enemy, we keep a reference to its EnemyController script. This is to assign the EnemyAtBottom method to each one’s EnemyEscaped event. Now anytime EnemyEscaped is called in the EnemyController script, it will then call the EnemyAtBottom script here, passing whatever parameter it (EnemyEscaped) has to the parameter for EnemyAtBottom. In this case, passing this in EnemyEscaped ensures that EnemyAtBottom knows which enemy to destroy.

Actions

Actions (C#): types in the System namespace that allow you to encapsulate methods without explicitly defining a delegate
In fact, they are delegates
Actions can be generic

An Action is just an event delegate that does not need another delegate to be created first to be used as a reference. The Action itself determines what parameters are necessary for the passed methods.