Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Need ideas for AOE Buff / Debuff system

Discussion in 'Scripting' started by Kvart, Mar 10, 2016.

  1. Kvart

    Kvart

    Joined:
    Nov 26, 2015
    Posts:
    13
    I'm trying to make a AOE buff system for a Moba like hero, where units which are within a range of the Hero gets some stats buffed.
    The best way to do this I can think of is by doing the following:
    using OnTriggerStay too buff all units within range of hero,
    When a units leaves the range OnTriggerExit would be called and the buff taken away.

    For some reason this way of doing it seems to clumsy for me. For example we would also need to check if the Hero is dead, etc. So I'm thinking that there should be a better and more simple way of doing this, but i'm at a loss for ideas. Anyone?
     
  2. ShokeR0

    ShokeR0

    Joined:
    Feb 24, 2016
    Posts:
    112
    OnTriggerStay seems pretty simple to me, why do you think it's not simple?
     
  3. Kvart

    Kvart

    Joined:
    Nov 26, 2015
    Posts:
    13
    OnTriggerStay wouldn't remove the buff once the unit is out of range. So for this I would need OnTriggerExit + also checking if the Hero is dead, and maybe other unforseen events.

    But maybe a combination of OnTriggerStay an a coroutine timer could work. Where OnTriggerStay constantly restarts the timer on the units in range and if the timer runs out the buff gets removed. Not sure if that solution is any better though.
     
  4. ShokeR0

    ShokeR0

    Joined:
    Feb 24, 2016
    Posts:
    112
    I don't see why you need a timer if you can just use OnTriggerExit.
    If you ask me, you worry way too much about this, trial and error is your answer
     
  5. Kvart

    Kvart

    Joined:
    Nov 26, 2015
    Posts:
    13
    Problem is that there are more situations where the buff's should be removed. As I said also when the Hero dies, and if the Hero f.ex. has a teleport ability. For me it feels a bit hack'ish to do these checks all over the code. Which is why I was wondering if there where some more clean and simple solution.
    But yeah I might very well end up doing it with colliders and checks in the end.
     
  6. ericbegue

    ericbegue

    Joined:
    May 31, 2013
    Posts:
    1,353
    You need to write the rules of the game somewhere anyway.
     
  7. Kvart

    Kvart

    Joined:
    Nov 26, 2015
    Posts:
    13
    The rules are written down. But there will come additional skills and heroes later. My question was purely to see if there was some common solutions to this kind of problem that I didn't know about.
     
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    OnTriggerExit and OnTriggerEnter would be my go to.

    There are several ways to deal with teleports and death. The first would be not to teleport, but instead move the trigger over really quickly.

    The other way would be to reset the buffs when the character teleports, and use an OverlapShpere to generate the new list.
     
  9. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    I've done this two ways.

    1) I have a base StatusEffect class with abstract void OnUpdate(GameData neededData); These get added to units some how (like through an attack). Each frame, a unit will go through all their attached StatusEffects in order and run OnUpdate(data). There are also functions like OnDeath(data), OnStart(data), etc...

    2) In my current game, I give an integer id to every StatusEffect that is tracked by a StatusEffectController. A StatusEffect is just a struct of data, and I just tell the StatusEffectController when to process all status effects. A unit has an array of int[] StatusEffectIds;

    But yeah, having random status effect components on random object all running on their own is going to be almost impossible to track if you have a decent amount of status effects.
     
    Kiwasi likes this.
  10. Kvart

    Kvart

    Joined:
    Nov 26, 2015
    Posts:
    13
    Thanks a lot for the answers guys.

    Very nice to hear a whole other approach.
    Do you attach the StatusEffect as a GameObject or does your units have List of base StatusEffect's which the unit then loops through every frame? And do you know how heavy this is for performance?
    I might very well go with a variation of this.
     
  11. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
    Every Unit had it's own StatusEffectManager that contained a List<StatusEffect>. My StatusEffect base class was not a MonoBehaviour. When a StatusEffect is running, the StatusEffectManager provides a reference to the unit as part of the data. This way, the StatusEffect can access everything it needs on the unit.

    I could see a system using MonoBehaviours working.

    It's fast. You will probably be optimizing graphics, physics, load times, etc... before you ever have to optimize this. Unless you're looping through hundreds of thousands of status effects a frame (eg: MMO server) in which case all your data should be in arrays of structs or in a database.
     
    Last edited: Mar 11, 2016
  12. GarthSmith

    GarthSmith

    Joined:
    Apr 26, 2012
    Posts:
    1,240
  13. JoshuaMcKenzie

    JoshuaMcKenzie

    Joined:
    Jun 20, 2015
    Posts:
    897
    just make the special conditions independent. all the destroy conditions (leave area, source or targer dies, etc.) don't need to be tied with each other so, you can just simply call a removelistener method (as called in my code snippet, you can name it whatever you like) to remove the script for you. make sure the method is a little robust so that you don't have to worry about calling it in a specific order.
    Code (CSharp):
    1.  
    2. public class BuffSource : MonoBehaviour
    3. {
    4.  
    5.     #region MonoBehaviour methods
    6. //if the buffing source is destroyed
    7.     void OnDisable()
    8.     {
    9.         Clear();
    10.     }
    11.     #endregion
    12.  
    13.     #region Dictionary
    14.     private List<GameObject> listeners = new List<GameObject>();
    15.  
    16.     public void AddListener(GameObject target)
    17.     {
    18.         if (!listeners.Contains (target))
    19.         {
    20.             listeners.Add(target);
    21.             ApplyBuff(target);
    22.         }
    23.     }
    24.  
    25.     public void RemoveListener(GameObject target)
    26.     {
    27.         if (listeners.Contains (target))
    28.         {
    29.             RemoveBuff(target);
    30.             listeners.Remove(target);
    31.         }
    32.     }
    33.  
    34.     protected void Clear()
    35.     {
    36.         for(int i=0;i<listeners.Count;i++)
    37.         {
    38.             RemoveBuff(listeners[i]);
    39.         }
    40.  
    41.         listeners.Clear();
    42.     }
    43.     #endregion
    44.  
    45.     #region Buff Application
    46.     protected abstract void ApplyBuff(GameObject target);
    47.     protected abstract void RemoveBuff(GameObject target);
    48.     protected virtual void UpdateBuff(){}
    49.     #endregion
    50. }
    51.  
    52. public class BuffCourageAura : BuffSource
    53. {
    54.     protected override ApplyBuff(GameObject target)
    55.     {
    56.         if(target.GetComponent<CourageAuraEffect>()== null)
    57.         {
    58.             CourageAuraEffect script = target.AddComponent<CourageAuraEffect>();
    59.             script.source = this;
    60.         }
    61.     }
    62.  
    63.     protected override RemoveBuff(GameObject target)
    64.     {
    65.         CourageAuraEffect script = target.GetComponent<CourageAuraEffect>();
    66.         if(script != null)
    67.         {
    68.             Destroy(script);
    69.         }
    70.     }
    71.  
    72. //when a valid target enters the aura
    73.     void OnTriggerEnter(Collider other)
    74.     {
    75.         if(other.gameObject.tag == "Ally")
    76.         {
    77.             AddListener(other.gameObject);
    78.         }
    79.     }
    80.  
    81. //when a valid target exitthe aura
    82.     void OnTriggerExit(Collider other)
    83.     {
    84.         if(other.gameObject.tag == "Ally")
    85.         {
    86.             RemoveListener(other.gameObject);
    87.         }
    88.     }
    89. }
    90.  
    91. public class CourageAuraEffect : MonoBehaviour
    92. {
    93.     public BuffSource source;
    94.  
    95.     void OnDisable()
    96.     {
    97.         if(source==null)
    98.             return;
    99.  
    100.         //the buffed target died, remove buff
    101.         source.RemoveListener(gameObject);
    102.     }
    103.  
    104.     void Update()
    105.     {
    106.         //apply Courage Aura Effects here
    107.         // or maybe in awake, whatever
    108.     }
    109. }
    110.  

    That all aside, my favorite way of writing status effects is via the Decorator pattern. wrapping state and behavior modifications in wrapper classes with each wrapper potenitally having its own coroutine to mimic buff duration. makes it easy for combining behaviour changes (for example a fear effect that forces an enemy to flee) or by having complex status combos (maybe an ethereal effect that disable collisions and the ability to take and deal damage, but the ghostly appearance now generates a fear effect)