hi i am trying to figure out proper use of delegates. i made three scripts, first declares and instantiats delegate
using UnityEngine;
using System.Collections;
public class Delegates : MonoBehaviour {
public delegate void Health(int amount);
public static Health health;
}
second script attaches to object that encapsulate function into delegate
using UnityEngine;
using System.Collections;
public class HealthScript : MonoBehaviour {
int health;
void OnEnable(){
Delegates.health+=Health;
}
void OnDisable(){
Delegates.health-=Health;
}
void Start(){
health=100;
}
void Health(int amount){
health+=amount;
Debug.Log("Health: " + health);
}
}
third script is gui script where delegate is invoked
using UnityEngine;
using System.Collections;
public class GUIScript : MonoBehaviour {
void OnGUI(){
//increase
if(GUI.Button(new Rect(0,0,150,30),"Increase Health")){
if(Delegates.health!=null){
Delegates.health(10);
}
}
//decrease
if(GUI.Button(new Rect(0,35,150,30),"Decrease Health")){
if(Delegates.health!=null){
Delegates.health(-10);
}
}
}
}
so, everything works well, i am just wondering is this proper use, and how to extend this to accept more parameters for example different Game Objects. also i think i think naming of delegate is wrong, i still did not get the big picture. can somebody with more experience help me out with this? thanks!
I’m not sure what you mean by that. Please elaborate.
I don’t know what you’re doing, exactly, so I didn’t really try to restructure, just add clarity. (My first inclination would be to create Increase and Decrease events, and handle how much is increased or decreased in other scripts.)
Declaring the delegate in the GUI class makes more sense to me:
using System;
using UnityEngine;
public class HealthGUI : MonoBehaviour {
public static event Action<int> Update;
[SerializeField] int amount;
void OnGUI() {
if (GUI.Button(new Rect(0, 0, 150, 30), "Increase Health") Update != null)
Update(amount);
else if (GUI.Button(new Rect(0,35,150,30),"Decrease Health") Update != null)
Update(-amount);
}
}
This class doesn’t require the Health property or setHealth to be a field, but I figured I’d show you those options. I don’t know what this class should be called. I used SomethingThatHasHealth.
[SerializeField] int health;
Action<int> setHealth;
void Awake() {
setHealth = amount => Health += amount;
}
int Health {
get {return health;}
set {
health = value;
Debug.Log("Health: " + health);
}
}
void OnEnable() {
HealthGUI.Update += setHealth;
}
void OnDisable() {
HealthGUI.Update -= setHealth;
}
I think you have some wrong understanding of delegates. A delegate is basically an anonymous method. You can use it to create and register events.
using UnityEngine;
using System.Collections;
public class PlayerScript : MonoBehaviour {
// create a delegate we use later as event signature
public delegate void PlayerHealthEventHandler(GameObject go, PlayerScript player);
// create the event
public event PlayerHealthEventHandler PlayerHealthUpdate;
void Update () {
// do something
if(oldHealth != newHealth PlayerHealthUpdate!=null)
PlayerHealthUpdate(gameObject, this);
}
}
Then in some other script you could register to this event:
public class SomeOtherScript: MonoBehaviour {
void Awake() {
PlayerScript ps = ... // obtain the PlayerScript
ps.PlayerHealthUpdate += delegate(GameObject go, PlayerScript playerScript) {
Debug.Log(string.Format("Player '{0}' health has been updated. New Health is: {1}", ps.name, ps.health));
}
}
}
Edit:
Just as a side note: The event can be static too
public static event PlayerHealthEventHandler PlayerHealthUpdate;
but you have to be very careful with static events! And they don’t work pretty well anonymous classes. You can subscribe to the event normally and it works, but if you don’t unsubscribe the event, the object which subscribed can’t be collected by the garbage collection, causing a memory leak.
But anonymous methods can’t be unsubscribed easily. You either have to hold the delegate in a variable, or make a method and subscribe/unsubscribe it instead.
public class SomeOtherScript: MonoBehaviour {
void Awake() {
// subscribe to the event
PlayerScript.PlayerHealthUpdate += new PlayerHealthEventHandler(OnHealthUpdate);
}
void OnDestroy() {
// VERY IMPORTANT: Unsubscribe. If you don't do, your game will have a memory leak and won't free memory when object is destroyed!
PlayerScript.PlayerHealthUpdate -= new PlayerHealthEventHandler(OnHealthUpdate);
}
private void OnHealthUpdate(GameObject go, PlayerScript player) {
Debug.Log(string.Format("Player '{0}' health has been updated. New Health is: {1}", ps.name, ps.health));
}
}
i understand that delegates can be used in combination with events, i do not know if that is the way they are supposed to be used? i know they are powerfull so i want to understand them. the reason for this is that i want to produce loosly coupled code, right now i am not so good at this!
No, events/delegates are there to allows people to execute code on a certain event, without knowing when the original class triggers this event.
Let’s say you have a “Player” class and it handles all the health, mana, ammo etc. and you have an UI class. How everytime players health lowers, you want to trigger a red flash effect.
This red flash effect doesn’t belong to the Player class code, because it’s to specific. So you make a “PlayerHealthUpdate” event. Other classes can register to this events and the delegate you pass (playerScript.PlayerHealthUpdate += delegat(…)"; will be executed. This way you have your “red flash” code added in some other class then the player health.
Because player health is the only class which know when HP changes, there is no (clean) way to find that out from outside of it (you’d have save variables with old and new HP values in that other script, compare it etc.) which is very unclean, as HP should be handled by the class it relates to (in this case the Player) and not by the GUI.
But as i said in my edited post, you can also use static Events, but be VERY VERY CAREFUL. Used improperly it can cause serious memory leaks. Even normal “class member events” can cause Memory leaks, if the subscriber is destroyed but the event class is still active and used. But in most cases on scene load, the event class will be destroyed and memory released (from both: event class and subscribers).
edit:
You use events to notify other (interested) classes when a certain event happens. The classical examples are WinForm buttons (not Unity GUI Buttons, they work differently). A certain class don’t know when a button is being pressed and a button don’t know whom to notify, because Button is pretty generic.
When you start to “hardcode” stuff into the button class, you can’t reuse it anymore. So the button defines an event and everyone interested subscribe to it. When the user clicks the button, the button looks if someone is subscribed and sends an event to everyone who subscribed and can execute a certain code (i.e. open a window, start a calculation etc.).
the main reason why i want to use this, for starters, is to have separated gui code from everything else. so if i have a gui that changes, for example, player’s weapon i will have to add listener in the gui script for that event - weapon change, which is in the player script?
similiar can be done for shooting and other things that are triggered by the gui?
is there any proper use for just delegates out there?
Seems like a proper use of events, rather. That you have to use delegates is a side effect if using an event. Something like this would probably work well:
public class Weapon
{
/* ... */
}
public class Player : MonoBehaviour
{
public Weapon Weapon { get; private set; }
public event System.Action<Weapon> WeaponChanged;
public void ChangeWeapon(Weapon weapon)
{
Weapon = weapon;
WeaponChanged(Weapon);
}
}
Edit #1:
And then, somewhere else in the code where you want to get a callback when the weapon changes:
Player player = (Player)GameObject.FindObjectOfType(typeof(Player));
player.WeaponChanged += (newWeapon) =>
{
// Code to run when the weapon changes.
};
public class Weapon
{
/* ... */
}
public class Player : MonoBehaviour
{
public Weapon Weapon { get; private set; }
public delegate void WeaponChangeEventHandler(Player player, Weapon weapon);
public static event WeaponChangeEventHandler WeaponChange; // Static, because Op asked for it, but works as member too
public void ChangeWeapon(Weapon weapon)
{
// only call event/delegate, when the weapon REALLY has been changed!
if(Weapon != weapon) {
Weapon = weapon;
// Has anyone subscribed to this event? If no one subscribed, WeaponChange is null
if(WeaponChange!=null)
WeaponChange(this, Weapon);
}
}
}
public class GUICode: MonoBehaviour
{
public Weapon player1Weapon;
public Weapon player2Weapon;
Awake() {
Player.WeaponChange += new WeaponChangeEventHandler(OnWeaponChange);
}
void OnWeaponChanged(Player player, Weapon weapon) {
if(player.name=="player1")
player1Weapon = weapon;
if(player.name=="player2")
player2Weapon = weapon;
}
OnDestroy() {
// unsubscribe before the object is destroyed, so GC can free the memory
Player.WeaponChange -= new WeaponChangeEventHandler(OnWeaponChange);
}
void OnGUI() {
GUI.Label(new Rect(0,0, 300, 50), "Player 1 Weapon: "+player1Weapon.name);
GUI.Label(new Rect(0,60, 300, 50), "Player 2 Weapon: "+player2Weapon.name);
}
}
in the “OnWeaponChanged” method you do stuff like updating your variables or whatever you like. The “delegate” keyword in this case is only used to define the method signature which must be used with the event. So only methods can be subscribed which have “(Player xx, Weapon yy)” as signature.
“void OnWeaponChanged(Weapon weapon, Player player)” for example wouldn’t work, because it has a different signature than “public delegate void WeaponChangeEventHandler(Player player, Weapon weapon);”
fholm: You need it in order to unsubscribe to the event. Anonymous delegates can’t be subscribed
Player player = (Player)GameObject.FindObjectOfType(typeof(Player));
player.WeaponChanged += (newWeapon) =>
{
// Code to run when the weapon changes.
};
If you do this, you can’t unsubscribe the event anymore. Now if this game object gets destroyed, but “Player” remains active in memory, the memory of the instance above, won’t be collected by the garbage collection and you have a memory leak. If you create and destroy many of such objects you will eventually run out of memory and the game will crash.
The memory will only be released if the Player object AND the object which subscribed are not referenced anymore. Only then GC will be able to free the memory. And if you have a static event, unsubscribed events will NEVER be released and the memory will NEVER be claimed back (until restart of the game).
And the link you added in edit2 is exactly what I’ve posted.
someObject.SomeEvent += (s, e) => { // code here }; works fine if someObject is a member of of the class you put this line in, i.e. A WinForm/Dialog where you’re 100% sure that when the form gets destroyed the object which holds the event will get destroyed to (because it’s a member).
It DOES NOT work great, if its two different objects who can exist independently of each other. In this case you MUST unsubscribe the event when the object (where you registered it) gets destroyed.
@fholm: The way I used it is the way it’s been done, since .NET and events/delegates was introduced back in 2001. It’s the way Microsoft has propagated it for a decade (if you even cared read the article you linked above)
Also it’s easier to refactor it. You only need to change the declaration of
public delegate void WeaponChangeEventHandler(Player player, Weapon weapon);
and the methods itself, while on yours you also have to change every single Action<?,?,?..> (subscribes and unsubscribes)
edit:
The second advantage of defining your own delegate is the tool-tips your IDE offers.
With Action<Weapon, Weapon> your IDE will suggest: you this as auto-competition/tool-tip:
WeaponUpgrade += delegate(BaseWeapon arg1, BaseWeapon arg2) { // arg1, arg2 doesn't tell you much
};
but if you define it as
public delegate void WeaponUpgradeEventHandler(Weapon newWeapon, Weapon oldWeapon);
your auto-completition and tooltips will look like
Yes, because originally .NET did not have generics and the Action-delegates were not possible. Now a days the recommendation is to use System.Action and System.Func wherever possible.
Not true, writing out the whole “new DelegateType(method)” is not needed, just register them like this:
.
Player player = (Player)GameObject.FindObjectOfType(typeof(Player));
player.WeaponChanged += WeaponChanged;
// ...
void WeaponChanged(Weapon w)
{
}
I don’t know which IDE you use, but that is not the auto-complete I get in VS2010 (not even close). And even if it was true, it’s a small benefit.
Years ago I used to work with Visual Studio too, but currently more MonoDevelop (even though is sucks really hard compared to VS), but it’s what Unity comes with and Debugging etc is only possible within it… and Unity doesn’t work with 2011 yet