Event messaging in Unity – Creating a simple HUD

We can use delegates and events to create a messaging or broadcasting system that is much more robust and versatile than direct script communication via references.

Think of an event as a variable that is called like a static method. As soon as an event is raised from one script, we can listen to it from other scripts and perform corresponding actions. To do this, we want to subscribe another method to our event, which in turn is called. This functionality is given to us with delegates. A delegate holds a list of methods and when the delegate is called, all subscribing methods are called as well.

Best, we look at an example:

Creating a health display with events

This section will show you how to set up a HUD that displays the player’s health whenever it changes. Since there might be more objects that are interested in the player’s health, we will build an event system, that fires whenever the health changes and can be received by any number of other scripts easily.

Create a Player C# script and attach it to a GameObject in the scene.

Create a UI Text object on a Canvas in the scene. I’m using the Unity 4.6 upwards GUI system, but the old system or plain debug log messages will work fine for testing purposes.

Start with the player script:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

usingUnityEngine;

publicclassPlayer:MonoBehaviour

{

publicinthealth;

voidStart()

{

UpdateHealth(15);

}

voidUpdateHealth(intamount)

{

health+=amount;

//Raise OnHealthUpdate event

}

}

Our player has a health variable that is going to be initialized in the Start() method. The UpdateHealth() method will be called to add or subtract from health and then call the appropriate event, which can be received by different listeners in the scene.

Next, add in a delegate and an event before line 7:

Delegate and event for HealthUpdate

C#

1

2

publicdelegatevoidHealthUpdate();

publicstaticeventHealthUpdate OnHealthUpdated;

This is a typical delegate and event pair. Watch the official Unity tutorials on delegates and events for a detailed explanation.

In short: The event OnHealthUpdated is declared as static, so that we can call it from anywhere. It needs a type, which we declare with the delegate HealthUpdate. Note, that the type is HealthUpdate, but the actual delegate is a method, so it needs a return type of void and the parenthesis.

Let’s raise the OnHealthUpdated event in our player script:

Raising the OnHealthUpdated event

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

usingUnityEngine;

publicclassPlayer:MonoBehaviour

{

publicinthealth;

publicdelegatevoidHealthUpdate();

publicstaticeventHealthUpdate OnHealthUpdated;

voidStart()

{

UpdateHealth(15);

}

voidUpdateHealth(intamount)

{

health+=amount;

if(OnHealthUpdated!=null)

OnHealthUpdated();

}

}

If an event is raised without having subscribers, it will throw an error.

Before we call our event like a method we should include a null-check to avoid errors.

An event without subscribers returns null.

Be mindful of the parenthesis: the event itself is called OnHealthUpdated, but when we want to raise it, we use it like a method OnHealthUpdated().

Now that we have a player that raises an event as soon as its health is initialized, we can build the receiving end, the HUD script:

Subscribing to an event

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

usingUnityEngine;

publicclassHUD:MonoBehaviour

{

voidOnEnable()

{

//Subscribe to HealthUpdate event

Player.OnHealthUpdated+=HandleOnHealthUpdated;;

}

voidHandleOnHealthUpdated()

{

Debug.Log("HealthUpdated was received.");

}

}

It is a good idea to subscribe to events only when an object is enabled, so we place our code in the OnEnable() method from MonoBehaviour. We can access our static event and assign a method from HUD to it with the += operator. This method can be any custom function that is already declared in your script, but it’s also very convenient to use auto completion and let MonoDevelop suggest an implementation (often called an EventHandler).

MonoDevelop auto implementation of event handlers.

It’s time for our first test:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

usingUnityEngine;

publicclassHUD:MonoBehaviour

{

voidOnEnable()

{

Player.OnHealthUpdated+=HandleOnHealthUpdated;;

}

voidHandleOnHealthUpdated()

{

Debug.Log("HealthUpdated was received.");

}

}

If you’ve done everything correctly (and both scripts are attached to GameObjects in the scene), the debug message will be printed to the console as soon as the player calls the HealthUpdated event.

Great, now there’s only one thing left to figure out: How to send parameters with events, so that our HUD knows what the player’s health is.

Change line 7 of the player script from:

C#

1

publicdelegatevoidHealthUpdate();

to:

C#

1

publicdelegatevoidHealthUpdate(intnewHealth);

Now our delegate expects all subscribing methods to receive an integer parameter and we can send one from our event:

Event with parameter

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

usingUnityEngine;

publicclassPlayer:MonoBehaviour

{

publicinthealth;

publicdelegatevoidHealthUpdate(intnewHealth);

publicstaticeventHealthUpdate OnHealthUpdated;

voidStart()

{

UpdateHealth(15);

}

voidUpdateHealth(intamount)

{

health+=amount;

if(OnHealthUpdated!=null)

OnHealthUpdated(health);

}

}

Adjust the HUD script to accept the incoming newHealth parameter:

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

usingUnityEngine;

publicclassHUD:MonoBehaviour

{

voidOnEnable()

{

Player.OnHealthUpdated+=HandleOnHealthUpdated;;

}

voidHandleOnHealthUpdated(intnewHealth)

{

Debug.Log("HealthUpdated was received. New Health is: "+newHealth);

}

}

One more important thing:

Whenever you subscribe a method to an event, be sure to unsubscribe it as well to prevent memory leaks and possibly errors.

Unsubscribing an event

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

usingUnityEngine;

publicclassHUD:MonoBehaviour

{

voidOnEnable()

{

Player.OnHealthUpdated+=HandleOnHealthUpdated;;

}

voidHandleOnHealthUpdated(intnewHealth)

{

Debug.Log("HealthUpdated was received. New Health is: "+newHealth);

}

voidOnDisable()

{

Player.OnHealthUpdated-=HandleOnHealthUpdated;

}

}

That’s all there is to a basic messaging system! Events can be used for anything from updating stats over input to artificial intelligence. They can also include more interesting parameters like a reference to the event raising object, but that goes beyond this introduction.

Here are some more ideas how to continue:

Complete Player script with HealthUpdate events

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

usingUnityEngine;

publicclassPlayer:MonoBehaviour

{

//Variable to set in the inspector

publicintmaxHealth;

//Readonly property

publicintHealth{get;privateset;}

//Create delegate and event pair

publicdelegatevoidHealthUpdate(intnewHealth);

publicstaticeventHealthUpdate OnHealthUpdated;

voidStart()

{

//Initialize Health, but be sure that Health is currently at zero

Health=0;

UpdateHealth(maxHealth);

//Test the event system by decreasing Health every second

InvokeRepeating("TestHealthChange",1f,1f);

}

voidTestHealthChange()

{

UpdateHealth(-1);

}

voidUpdateHealth(intamount)

{

//Cancel this method if the incoming amount is zero

if(amount==0)

return;

//If its not zero, then add or subtract from Health

Health+=amount;

//Raise OnHealthUpdated if it is not null

if(OnHealthUpdated!=null)

OnHealthUpdated(Health);

Debug.Log("OnHealthUpdate was fired.");

}

}

Complete HUD script receiving HealthUpdate events

C#

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

usingUnityEngine;

usingUnityEngine.UI;//important!

//You need to be using namespace UI to have access to the Text component!

publicclassHUD:MonoBehaviour

{

//Populate healthText with a GUI Text in the inspector

publicText healthText;

voidOnEnable()

{

//Subscribe to OnHealthUpdated event

Player.OnHealthUpdated+=HandleOnHealthUpdated;

}

//Event handler

voidHandleOnHealthUpdated(intnewHealth)

{

//Update text or graphics and give debug feeedback

healthText.text="Player health: "+newHealth;

Debug.Log("Health update was received. New health: "+newHealth);

}

voidOnDisable()

{

//Unsubscribe from HealthUpdate

Player.OnHealthUpdated-=HandleOnHealthUpdated;

}

}

Additional info: If you pay close attention to the console output from the last two scripts, you might notice, that the messages seem to come in the wrong order:

Debug messages are displayed in an incorrect order.

How can the event be received before it was fired? Of course, it can’t. Printing to the console is a very slow operation and due to many things going on at the same time, Debug.Log() might be too slow to accurately depict what is going on. Don’t worry, just be aware that it’s only the console messages that are slow, your event system is still working correctly.