Search Unity

Feedback Events triggered from script, how to set up.

Discussion in 'Scripting' started by Penkine, Feb 16, 2020.

  1. Penkine

    Penkine

    Joined:
    Jun 5, 2019
    Posts:
    15
    I have an Enemy script which has some functions, and I would like them to trigger a corresponding event:
    • TakeDamageByAmount, triggers: OnDamage
    • TakeDamageToMax, triggers: OnDamage
    • HealByAmount, triggers: OnHeal
    • HealToMax, triggers: OnHeal
    • Die, triggers: OnDeath
    I would then like other scripts on the same game object to recognize the trigger and do an action from it.

    Enemy script:

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class Enemy : MonoBehaviour
    6. {
    7.     [Header("Debug")]
    8.     [SerializeField] private bool debug;
    9.  
    10.     [Header("Information")]
    11.     [SerializeField] private int damage;
    12.     [SerializeField] private int maxHealth;
    13.     [SerializeField] private int curHealth;
    14.     [SerializeField] private float speed;
    15.  
    16.     [Header("Attacking")]
    17.     [SerializeField] private Transform attackPosition;
    18.     [SerializeField] private LayerMask whatIsPlayer;
    19.     [SerializeField] private float attackRange;
    20.     [SerializeField] [Range(0, 10)] private float timeBtwAttack;
    21.     private float curTimeBtwAttack;
    22.  
    23.     [Header("Effects")]
    24.     [SerializeField] private GameObject _BloodEffect;
    25.     [SerializeField] private GameObject _DeathEffect;
    26.     [SerializeField] private GameObject _HealEffect;
    27.  
    28.     ///Events
    29.     ///
    30.     ///On damage
    31.     ///On heal
    32.     ///On death
    33.     ///
    34.  
    35.     #region Public Functions
    36.     public void HealByAmount(GameObject source, int amount)
    37.     {
    38.         int newHealth;
    39.         int overHeal;
    40.  
    41.         newHealth = curHealth + amount;
    42.  
    43.         Log("Healed " + amount + " health, to " + newHealth + " HP, from " + curHealth + " HP, by " + source + ".");
    44.         Instantiate(_HealEffect, transform.transform.position, Quaternion.identity);
    45.  
    46.         if (newHealth > maxHealth)
    47.         {
    48.             overHeal = newHealth - maxHealth;
    49.             Log("Overhealed by " + overHeal + ".");
    50.         }
    51.  
    52.         curHealth = newHealth;
    53.     }
    54.     public void HealToMax(GameObject source)
    55.     {
    56.         Log("Healed to full HP, by " + source + ".");
    57.         Instantiate(_HealEffect, transform.transform.position, Quaternion.identity);
    58.         curHealth = maxHealth;
    59.     }
    60.     public void TakeDamageByAmount(GameObject source, int amount)
    61.     {
    62.         int newHealth;
    63.         int overKill;
    64.  
    65.         newHealth = curHealth - amount;
    66.  
    67.         Log("Taken " + amount + " damage, to " + newHealth + " HP, from " + curHealth + " HP, by " + source + ".");
    68.         Instantiate(_BloodEffect, transform.transform.position, Quaternion.identity);
    69.  
    70.         if (newHealth <= 0)
    71.         {
    72.             Die(source);
    73.             if (newHealth < 0)
    74.             {
    75.                 overKill = newHealth * -1;
    76.                 Log("Overkilled by " + overKill + ".");
    77.             }
    78.         }
    79.  
    80.         curHealth = newHealth;
    81.     }
    82.     public void TakeDamageToMax(GameObject source)
    83.     {
    84.         Log("Taken max damage, by " + source + ".");
    85.         Instantiate(_BloodEffect, transform.transform.position, Quaternion.identity);
    86.         curHealth = 0;
    87.         Die(source);
    88.     }
    89.     public void Die(GameObject source)
    90.     {
    91.         Log("Died by " + source + ".");
    92.         Destroy(gameObject);
    93.         Instantiate(_DeathEffect, transform.transform.position, Quaternion.identity);
    94.     }
    95.     #endregion
    96.     #region Private Functions
    97.     private void MoveCharacter()
    98.     {
    99.         transform.Translate(Vector2.left * speed * Time.deltaTime);
    100.     }
    101.     private bool CheckForPlayer(Transform pos, float range, LayerMask target)
    102.     {
    103.         bool result;
    104.         result = false;
    105.  
    106.         Collider2D[] colliders = Physics2D.OverlapCircleAll(pos.position, range, target);
    107.         if (colliders.Length > 0)
    108.         {
    109.             result = true;
    110.         }
    111.  
    112.         return result;
    113.     }
    114.     private void Attack(Transform pos, float range, LayerMask target)
    115.     {
    116.         Collider2D[] colliders = Physics2D.OverlapCircleAll(pos.position, range, target);
    117.         for (int i = 0; i < colliders.Length; i++)
    118.         {
    119.             colliders[i].GetComponent<Player>().TakeDamageByAmount(gameObject, damage);
    120.         }
    121.     }
    122.     private void TryAttack()
    123.     {
    124.         if (curTimeBtwAttack <= 0)
    125.         {
    126.             Attack(attackPosition, attackRange, whatIsPlayer);
    127.             curTimeBtwAttack = timeBtwAttack;
    128.         }
    129.         if (curTimeBtwAttack > 0)
    130.         {
    131.             curTimeBtwAttack -= Time.deltaTime;
    132.         }
    133.     }
    134.     private void Log(string _msg)
    135.     {
    136.         if (!debug) return;
    137.         Debug.Log("[Enemy]: " + _msg);
    138.     }
    139.     private void LogWarning(string _msg)
    140.     {
    141.         if (!debug) return;
    142.         Debug.LogWarning("[Enemy]: " + _msg);
    143.     }
    144.     #endregion
    145.     #region Unity Functions
    146.     private void Start()
    147.     {
    148.         curHealth = maxHealth;
    149.     }
    150.     private void Update()
    151.     {
    152.         if (CheckForPlayer(attackPosition, attackRange, whatIsPlayer))
    153.         {
    154.             TryAttack();
    155.         }
    156.         else
    157.         {
    158.             MoveCharacter();
    159.         }
    160.     }
    161.     private void OnDrawGizmosSelected()
    162.     {
    163.         Gizmos.color = Color.red;
    164.         Gizmos.DrawWireSphere(attackPosition.position, attackRange);
    165.     }
    166.     #endregion
    167. }
    168.  
     
    Last edited: Feb 17, 2020
  2. Chris-Trueman

    Chris-Trueman

    Joined:
    Oct 10, 2014
    Posts:
    1,261
    I use UnityEvents. More importantly custom UnityEvents.

    So what you would do is make an event for each trigger.

    Example for death event
    Code (CSharp):
    1. using UnityEngine.Events;
    2.  
    3. [System.Serializable]
    4. public class DeathEvent : UnityEvent<Enemy> { }
    Once you have that add to the enemy class
    Code (CSharp):
    1. public DeathEvent OnDeath;
    Then in each method that would need to trigger the event.
    Code (CSharp):
    1.  public void Die(GameObject source)
    2. {
    3.     OnDeath.Invoke(this);
    4.     Log("Died by " + source + ".");
    5.     Destroy(gameObject);
    6.     Instantiate(_DeathEffect, transform.transform.position, Quaternion.identity);
    7. }
     
  3. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    @Chris-Trueman has probably the best Suggestion, because it also has integrated support from Unity's Editor.

    Another approach is using delegates, which is very similar, but is less easy to set up.

    Or you could use a generalized Notification Manager like SIP (from the Asset store, free)
     
  4. Penkine

    Penkine

    Joined:
    Jun 5, 2019
    Posts:
    15
    This helps a lot! Thanks for the advice, any pointers on how to then use the OnDeath event to do something?
     
  5. csofranz

    csofranz

    Joined:
    Apr 29, 2017
    Posts:
    1,556
    Well, that rather depends on you. Objects should not sign up for events for the sake of it, but because the event is meaningful TO THE OBJECT. An enemy might sign up for the death of an ally so it can adapt the strategy and turn towards a new target. Allies might get notified of the death of their friend and you might want to determine if they Panic. The game manager will definitely be interested in the death of anyone, so it can keep score, and determine that the game is over (all allies dead) or won (all enemies dead) or a new scene/level needs to start. "The possibilities are endless..."

    When an NPC gets hurt, it definitely wants to know about it, and react accordingly. Event-based scripting has many advantages with regards to flexibility; but they can be more difficult to debug. Make sure you write down your events before you implement them, and define painstakingly what they mean, and who would want to know when an event happens.