Search Unity

Buff script for Tower Defense

Discussion in 'Scripting' started by vulpes93, May 24, 2017.

  1. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    Hello! May we get straight to the problem I have? I've read several topics but didn't find an answer.

    It is a tower defense game and I have some towers that increase AttackRange and AttackSpeed of nearby towers. The buff lasts constantly until the buff tower is removed or sold (It's more like an aura).

    With circle overlap I'm dealing perfectly fine at start. But the problem appears when I try to maintain stacking effect. I need to prevent attributes from stacking - even if there is many buffs of same type, only one has an effect.

    I can see some ways of implementing that feature, but as always I'm not sure which is effective or maybe there is something I'm missing.

    So far I have an abstract class TowerBuff and derrived classes AttackRangeBuff, AttackSpeedBuff (they have reference to a Tower).
    I'm thinking of adding these scripts to a List<TowerBuff> in my towers. Here come my questions:
    1. Is it a good solution to have that List<TowerBuff> in my towers?
    2. If so, how and where do I use it?
    3. How to prevent the buffs from stacking?


    Please, put me in a right direction.

    Code (CSharp):
    1. public abstract class TowerBuff : MonoBehaviour
    2. {
    3.     Tower tower;
    4.     public float amount = 0f;
    5.     public float multiplier = 1f;
    6.     public float duration;
    7.     float lifeTime;
    8.  
    9.     public abstract void ApplyBuff(Tower tower);
    10.     public abstract void RemoveBuff(Tower tower);
    11. }
    12. public class AttackRangeBuff : TowerBuff
    13. {
    14.     public override void ApplyBuff(Tower tower)
    15.     {
    16.         tower.attackRange *= multiplier;
    17.         tower.attackRange += amount;
    18.     }
    19.  
    20.     public override void RemoveBuff(Tower tower)
    21.     {
    22.         // IDK how to set old values, when buff goes off
    23.         tower.attackRange = tower.StartAttackRange;
    24.     }
    25. }
     
    Last edited: May 24, 2017
  2. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    Just thinking out loud here, but if you had a towerbuff list on the tower, that sounds reasonable to me. When a buff is placed, you find anything in range and send the buff to them. That tower then checks its list to see if it already has a buff (if it can't stack) and acts accordingly.
    As for removing buffs, you could remove the buff from the list, and then recalculate the stats (based on starting stats + adding up all of the buffs).
     
    vulpes93 likes this.
  3. Donay

    Donay

    Joined:
    Apr 28, 2017
    Posts:
    70
    You could keep count the buffs on the tower....
    Code (csharp):
    1.  
    2. public abstract class TowerBuff : MonoBehaviour
    3. {
    4.     Tower tower;
    5.     public float amount = 0f;
    6.     public float multiplier = 1f;
    7.     public float duration;
    8.     private int buffCount = 0;
    9.     float lifeTime;
    10.  
    11.     public abstract void ApplyBuff(Tower tower);
    12.     public abstract void RemoveBuff(Tower tower);
    13. }
    14. public class AttackRangeBuff : TowerBuff
    15. {
    16.     public override void ApplyBuff(Tower tower)
    17.     {
    18.         buffCount++;
    19.         if (buffCount==1)
    20.         {
    21.             tower.attackRange *= multiplier;
    22.             tower.attackRange += amount;
    23.         }
    24.     }
    25.  
    26.     public override void RemoveBuff(Tower tower)
    27.     {
    28.         // IDK how to set old values, when buff goes off
    29.         buffCount--;
    30.         if (buffCount < 0) Debug.Log("Error: No buff to remove!");
    31.         if (buffCount==0) tower.attackRange = tower.StartAttackRange;
    32.     }
    33. }
    34.  
     
  4. Donay

    Donay

    Joined:
    Apr 28, 2017
    Posts:
    70
    Above code will not work as the buffCount needs to be on the tower! but hopfully you get the idea.
     
  5. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    But what if a had 10 different buffs? Will I need 10 buff counters?
    __________
    methos5k, thank you, I will see what I can do about it.
     
  6. Donay

    Donay

    Joined:
    Apr 28, 2017
    Posts:
    70
    Depends on how you want the buffs to work.
    You could do it based on stats. E.G the tower could have ...

    tower.attackRangeNormal
    tower.attackRangeBuffCount

    When you buff the tower range just add to the attackRangeBuffCount, dont add to the attackRange.
    within the tower you can then do...

    attackRange=attackRangeNormal+(attackRangeNormal * attackRangeBuffCount/10)

    which would add 10% range per attackRangeBuffCount, or

    if (attackRangeBuffCount>0) {
    attackRange=attackRangeNormal*1.1
    } else
    {
    attackRange=attackRangeNormal
    {

    which would be non stacking buffs.
     
    Last edited: May 24, 2017
  7. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    How about this?

    Code (CSharp):
    1.     public void AddBuff(TowerBuff buff)
    2.     {
    3.         activeBuffs.Add(buff);
    4.         if (buff.canStack)
    5.             buff.ApplyBuff(this);
    6.         else
    7.         {
    8.             activeBuffs.Where(x => x.Equals(buff))
    9.                                 .OrderByDescending(x => x.value)
    10.                                 .First()
    11.                                 .ApplyBuff(this);
    12.         }      
    13.     }
    I have bool variable canStack and regarding to this I do stuff.
    if can't stack, I find all instances of the buff in the list and find only one the strongest buff (considering different values for different towers)
    and apply it to the tower.
     
    Last edited: May 24, 2017
  8. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I would ditch the lists and use a collection that forces unique items. Since you only need to know presence or absence of the buff, a hashset will work well.

    For convenience in similar systems I tend to use a method that recalculates all the buffs in the map. Then when a tower is added or removed I call RecalculateAllBuffs. It's slightly more expensive performance wise, but it's much simpler to manage from code.
     
  9. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    Isnt 2 instances of same class even with equal values are considered as unique elements in hashset? That won't do, those buffs can be removed and I will need to overlapcircle once again to add the buff to hashset...
    Finding better solution.
    Recalculate buffs? IDK, Is that how it's implemented in other games?
     
  10. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    I assumed that you are using are tokenising the buffs for this purpose. And you can use a value type or override the equals method to make Contains work properly.
     
  11. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    Overriding built-in methods? Geesh, there us gotta be a better solution...
     
  12. methos5k

    methos5k

    Joined:
    Aug 3, 2015
    Posts:
    8,712
    He meant overriding the equals so the hashset can compare properly. When you hash/dictionary, overriding equals and gethashcode is important for custom classes, so they work properly (don't rely on Object's versions).

    Nothing wrong with some options given here, but honestly if all you wanted to do was get these buffs working and have (some) not stacking, just do what seems simplest from the examples here. You can also come back later (to here/your code) and modify how you're going to get it to work ;) That's my opinion. Take the simple way, and continue on.. Check later if it's really a problem, or even if you need to work on it more (you may find you don't...) :)
     
    Kiwasi likes this.
  13. vulpes93

    vulpes93

    Joined:
    Nov 29, 2016
    Posts:
    14
    I think I will need to override Equals and use Linq's Distinct for my list
     
  14. Kiwasi

    Kiwasi

    Joined:
    Dec 5, 2013
    Posts:
    16,860
    Overiding the equals method is common practice on C#. You could alternatively do the comparison manually. But that strikes me as inefficient.
     
  15. Laperen

    Laperen

    Joined:
    Feb 1, 2016
    Posts:
    1,065
    Probably an unpopular opinion at this point, but you could make the buff a component, then add the component only if GetComponent determines it's not there. From there the component does it's own thing buffing the tower it is attached to.
     
    vulpes93 and eisenpony like this.
  16. Donay

    Donay

    Joined:
    Apr 28, 2017
    Posts:
    70
    You could do it using a dictionary of buffs on each tower.
    Dictionary<int, TowerBuff> myBuffs = new Dictionary<int, TowerBuff>();
    The buffing tower would create a new buff and add it to the tower using its instanceID.
    When a buffing tower is removed you just remove the entry using the instanceID.

    The tower can then go through the dictonary and pick the top level buffs
     
    Kiwasi likes this.
  17. eisenpony

    eisenpony

    Joined:
    May 8, 2015
    Posts:
    974
    I wouldn't suggest overriding the Equals method unless your type is immutable.
    Instead create an IEqualityComparer for your type and pass it into your HashSet constructor.

    This sounds like a step towards a more flexible and maintainable solution to me.
     
    Last edited: May 26, 2017
    vulpes93 likes this.