Unity Version:

  • Unity 2015.6.0f3
  • Unity 2017.3.1f1
  • Unity 2018.1.5f1

Uses:

  • To Damage an enemy
  • To damage a group of enemies (like a grenade)
  • Moba Style Games
  • FPS Games
  • Projectile Type weapons (Pistols, Bow and Arrow, grenades, etc.)

Summary

I’ve seen a few different ways of how people send messages and apply damage to an enemy unity, or a structure. Typically most people, or tutorials that I’ve seen use Raycast, and Raycast hit. Which is normally fine, however, I wanted to instantiate an actual bullet for slower paced FPS games, games where a bullet dodging is possible, and a projectile actually makes contact w/ an object. It’s a bit intensive, however, it’s flexible enough to use in most games.

Now it can differ a bit depending on what type of projectile you’re using. A Bow and Arrow vs a Grenade. There’s physics, amount of targets to consider, and so on. However, this article will go over the base implementation of it, and give the general idea of how I at least tackled this problem, and how it can be used for things such as explosions, vs single target projectiles.

This is also used in the UYD Project.

  • NOTE: This is a base implementation, that’s meant to be modifiable to a degree. I Also recommend adding an object pooling system for your projectiles as well, as it’s not covered in this tutorial.

Setup

  • Scripts:
    • DamageSystem/DamageMessage.cs
    • DamageSystem/DamageMessenger.cs
    • DamageSystem/DamageListener.cs
    • DamageSystem/Events/CollisionEvent.cs
    • DamageSystem/Interfaces/IDamageMessenger.cs
    • DamageSystem/Interfaces/IDamageListener.cs
    • DamageSystem/Interfaces/IDamageDistributor.cs
  • DamageSystem/Interfaces/ICollisionCollider.cs

Diagram & Process:

Diagram

The Goal is to add a sort of DamageMessage that can only be sent by a DamageMessanger and will collide with any object that contains a DamageListener. The cool thing here is that any object that it collides with, can be destructable in some way or form.

The problem however, is that if the colliding object does not have a DamageListener, the object will just float in space. So the DamageMessage component would need a default “… if it colliders with something, self destruct”.

Name(Type)

Description

IDamageMessage

Simply the payload information, or the ‘message’ you want to send to the target. (damage, the owner information, etc.)

IDamageMessenger

The object delivering the message (Bullet, Arrow, projectile, etc.)

IDamageListener

A Destructable object. Something that can take damage.

Now, there is also a point that states TriggerCollider. This may need to be adjustable depending on your needs. For instance, I needed to adjust using a OnCollisionEnter method to get the contactPoints for Instantiating Bullet holes. What this means is I needed the coordinates of (x,y,z) to spawn the bullet hole in the appropriate space. Otherwise, a couple box colliders should be enough to get the job done.


Interface Code

The Interface code is meant to be simple, just covering the necessities that is needed.

IDamageMessenger:

public interace IDamageMessenger
{
   DamageMessage damageMessage { get; set; }
   void DestroyObject();
}

The Damage Messenger simply holds the DamageMessage you want to send.

DamageMessenger:

public abstract class DamageMessenger : SerializedMonoBehaviour, IDamageMessenger 
{
   //[Serializable]
     [OdinSerialize]
     public DamageMessage damageMessage { get; set; }

     public virtual void Reset()
     {
         damageMessage = new DamageMessage();
     }

     public virtual void Awake()
     {
         if (damageMessage == null)
             damageMessage = new DamageMessage();
     }

     public virtual void DestroyObject()
     {
         this.DestroyObject();
     }
}

DamageMessage:

[System.Serializable]
public class DamageMessage
{
    public GameObject sender;

    public uint damageAmount;
}

This simple has the ‘sender’ or the ‘owner’ of the object. In case you want to tell the Listener ‘who’ damaged it, and a damageAmount. I left it as uint, as I only use positive amounts, but if you want to use it for healing values for instances, like a heal gun, then it may be good to add flags, or removing the uint variable, as you see fit.

IDamageListener:

/// Simply implemented for the Distributor
public interface IDamageListener: ICollisionCollider { //Tadjustable adjustableAttribute { get; } void DealDamage(DamageMessage message); }

The DamageListener is simply awaiting the Damage. Now the ICollisionCollider isn’t really that necessary, it simply contains the signatures for Collisionevents, but here’s the code if you’re interested.

ICollisionCollider:

public interface ICollisionCollider
{
    void OnCollisionEnter(Collision collision);
    void OnCollisionStay(Collision collision);
    void OnCollisionExit(Collision collision);
}

Now the Key piece is the DamageListener. The Damage Listener is meant to receive the DamageMessage, identify it, and deal damage to the object as necessary. It’s also meant to do other effects (that will be released in a separate article) to instantiate bullet hole effects, and allow the listener to do some of their own effects, like blood splatter effects, bullet holes, etc.

It also contains a CollisionEvent that helps the DamageListener send the given information to whoever may be interested, like UI Objects, perhaps a player stats system for pvp style games, challenge games, etc.

CollisionEvent:

[System.Serializable]
    public class CollisionEvent : UnityEvent<DamageMessage> { } 

DamageListener:

[RequireComponent(typeof(Collider))]
public class DamageListener : MonoBehaviour, IDamageListener
{
    [HideInInspector]public Collider col;

    public CollisionEvent OnCollision;

    public HealthSlider health;
 
    public bool doesObjectHaveParent;

    private GameObject colliderOwner;

    public virtual void Awake()
    {
        //col = this.GetComponent();
        col = (doesObjectHaveParent) ? this.GetComponentInParent() : this.GetComponent();
        
        if (health != null)
            health.Construct();
    }

    public virtual void Start()
    {
        colliderOwner = (doesObjectHaveParent) ? this.transform.parent.gameObject : this.transform.gameObject;
	
	OnCollision.AddListener(DealDamage);
    }

    public void OnCollisionEnter(Collision collision)
    {
        DamageMessenger messenger;

	//Get the DamageMessenger from the object 
        messenger = collision.gameObject.GetComponent();
        if (messenger != null)
        {
	//Check the objects InstanceID and compare it. Make sure it's not the same InstanceID.
            if (messenger.damageMessage.sender.GetInstanceID() != colliderOwner.GetInstanceID())
            {
                if (OnCollision != null)
                    OnCollision.Invoke(messenger.damageMessage);


                messenger.DestroyObject();
            }
        }
    }

    public void OnCollisionStay(Collision collision) { }

    public void OnCollisionExit(Collision collision) { }
	
    public virtual void DealDamage(DamageMessage message)
    {
        Debug.Log("Messenger Damage: " + message.damageAmount);
        health.DecreaseHealth((int)message.damageAmount);
    }
}

Hope this helps!


DamageDistributor

This piece is really only used if you need a way to distribute damage to all listeners in an area. like a grenade effect. It is simply invoking the Damage Distributor to collect a surrounding area of Listeners, and invoking based of the radius. a quick and simple way is by using Physics.OverlapSphere. This allows you to find the distance from the explosion, and choose a damage percent based on distance, as well as changing the IDamageMessenger code a bit.

public interface IDamageDistributor : IDamageMessenger
{
    List listeners { get; set; }

    Collider AreaOfEffect { get; set; }

    void DistributeDamage();
}

Happy Developing!