C# Creating my own custom Unity Messages

Unity has a bunch of “Messages” that are functions you can write implementation for - In example Awake, OnBecameVisible, Update, and LateUpdate.

Say I had a class Character that inherits from Monobehaviour

and three classes that inherit from Character such as NPC, Enemy and Hero

I’d like to have the Character Class have a message it sends called “OnTakeDamage”
I’d like to implement it in the Enemy and Hero class but not the NPC class. (Just like you don’t have to use all messages from Monobehaviour)

Is it possible to write my own class that has messages sent for classes to inherit from (like a OnTakeDamage)? Also how would I do it?

(Note: I am aware of the SendMessage() function of Monobehaviour. SendMessage however does “Calls the method named methodName on every MonoBehaviour in this game object.”)

Thanks

There’s a few ways, but if you want to follow object oriented programming logic, then I’d suggest making base class from which all damagable objects extend. Eg:

In DamageSource.cs (the class you’d use to give damage to )

using UnityEngine;
using System.Collections;

public class DamageSource : MonoBehaviour {
	protected float damageAmount = 10f;
	
	//not 100% necessary, but handy for an example of how to 
	//handle damage based on the attacker (which is 
	//relevant for info sent in the OnTakeDamage() method
	protected ElementType elementType = ElementType.Normal;
	
	//we use a function for getting the damage this 
	//DamageSource can do because it lets us overwrite it.
	//Eg, if the enemy is weakened, you can factor that 
	//in and return a lesser amount of damage.
	public float GetDamageAmount() {
		return damageAmount;
	}
	
	public ElementType GetElementType() {
		return elementType;
	}
}

//kinds of elements available for damage / resistance calculations
public enum ElementType {
	Normal,
	Fire,
	Ice,
	Lightning
}

In DamageableObject.cs (the base class from which all damageable objects inherit):

using UnityEngine;
using System.Collections;

public class DamageableObject : MonoBehaviour {
	protected bool wasRecentlyHit;
	protected float health;
	protected float maxHealth;
	
	public void Awake() {
		health = maxHealth;
	}
	
	//Creating a virtual void method lets you choose whether 
	//or not you want to set it in a derived class.
	//Here, we track the amount of damage and the source 
	//the damage came from. This can sometimes be handy for 
	//context-sensitive reactions to being damaged. Eg, play 
	//a particular sound in damaging the player, when 
	//successfully damaged by a particular attack.
	//Note that this base implementation does nothing - you 
	//override it in an inheriting class, very similar to using Update() etc.
	protected virtual void OnTakeDamage(float damageAmount, DamageSource damageSource) {}

	//An example of how you'd check whether damage is incoming. 
	//You can alternatively just call 
	//someDamageableObject.TryDoDamage() from another script.
	public void OnTriggerEnter(Collider other) {
		DamageSource damageGiver = other.GetComponent<DamageSource>();
		if (damageGiver) {
			TryDoDamage(damageGiver.GetDamageAmount(),damageGiver.gameObject);
		}
	}

	public void TryDoDamage(float damageAmount, GameObject damageGiver) {
		//early out, this DamageableObject was damaged a very 
		//short time ago and shouldn't be damaged again so soon
		if (wasRecentlyHit) return;
		
		//optionally perform any damage calculations here based
		//on the damageGiver, eg more damage from the player 
		//being weakened somehow, or less damage from type 
		//resistances... etc.
		damageAmount = CalculateDamage(damageAmount,damageGiver);
		
		//if after our damage calculations we still have an 
		//amount of damage greater than 0, we do the damage and 
		//send the OnTakeDamage() message.
		if (damageAmount>0f) {
			health -= damageAmount;
			
			//optional handling of dying (uncomment this and the OnDeath() function to enable)
			//if (health<0f) {
			//	OnDeath(damageAmount,damageGiver);
			//}
			//else {
			OnTakeDamage(damageAmount,damageGiver);
			//}
		}
	}
	
	//Uncomment this and the (healtn<0f) if statement above 
	//if you want to handle dying as well as being damaged
	//protected virtual void OnDeath(float damageAmount, DamageSource damageSource);
	
	//Default implementation for calculating damage, 
	//given some amount of damage, and some source of damage.
	//Override this in an inheriting class if you want to do 
	//different damage, eg based on the damage source (2x 
	//damage from fire attacks, 0.5x damage from ice 
	//attacks... etc) or based on the DamageableObject's 
	//current state (eg, player is weakened, so takes 1.5x damage)
	protected float CalculateDamage(float damageAmount, DamageSource damageSource) {
		return damageAmount;
	}
}

In PlayerDamageReceiver.cs:

using UnityEngine;
using System.Collections;

public class PlayerDamageReceiver : DamageableObject {
	
	//override the OnTakeDamage() method to make a 
	//different implementation of it for this class
	protected override void OnTakeDamage(float damageAmount, DamageSource damageSource) {
		Debug.Log("Ouch, the player was damaged!");
	}
	
	//Uncomment this to override the OnDeath() function 
	//in DamageableObject (if you've uncommented that, that is)
	//protected override void OnDeath(float damageAmount, DamageSource damageSource) {
	//	Debug.Log("Uhoh... The player died. :(");
	//}
	
	//override the CalculateDamage() function to 
	//determine how damage applies to the player
	protected override float CalculateDamage(float damageAmount, DamageSource damageSource) {
		//Example: give the player a 2x weakness to fire damage, and immunity to ice damage
		switch (damageSource.GetElementType()) {
			case (ElementType.Fire):
				damageAmount *= 2f;
				break;
			case (ElementType.Ice):
				damageAmount = 0f;
				break;
		}
		return damageAmount;
	}
}

SendMessage is the exact counter part to the built-in messages. They are also called on every MonoBehaviour on the gameobject. For example if you have two scripts attached to the same gameobject which both implement OnCollisionEnter, then OnCollisionEnter will be called on both scripts when a collision occurs.

Keep in mind that SendMessage has an additional SendMessageOptions paramete. Using SendMessageOptions.DontRequireReceiver doesn’t force that the message has to be implemented by any script.