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): public abstract class TowerBuff : MonoBehaviour { Tower tower; public float amount = 0f; public float multiplier = 1f; public float duration; float lifeTime; public abstract void ApplyBuff(Tower tower); public abstract void RemoveBuff(Tower tower); } public class AttackRangeBuff : TowerBuff { public override void ApplyBuff(Tower tower) { tower.attackRange *= multiplier; tower.attackRange += amount; } public override void RemoveBuff(Tower tower) { // IDK how to set old values, when buff goes off tower.attackRange = tower.StartAttackRange; } }
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).
You could keep count the buffs on the tower.... Code (csharp): public abstract class TowerBuff : MonoBehaviour { Tower tower; public float amount = 0f; public float multiplier = 1f; public float duration; private int buffCount = 0; float lifeTime; public abstract void ApplyBuff(Tower tower); public abstract void RemoveBuff(Tower tower); } public class AttackRangeBuff : TowerBuff { public override void ApplyBuff(Tower tower) { buffCount++; if (buffCount==1) { tower.attackRange *= multiplier; tower.attackRange += amount; } } public override void RemoveBuff(Tower tower) { // IDK how to set old values, when buff goes off buffCount--; if (buffCount < 0) Debug.Log("Error: No buff to remove!"); if (buffCount==0) tower.attackRange = tower.StartAttackRange; } }
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.
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.
How about this? Code (CSharp): public void AddBuff(TowerBuff buff) { activeBuffs.Add(buff); if (buff.canStack) buff.ApplyBuff(this); else { activeBuffs.Where(x => x.Equals(buff)) .OrderByDescending(x => x.value) .First() .ApplyBuff(this); } } 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.
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.
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?
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.
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...)
Overiding the equals method is common practice on C#. You could alternatively do the comparison manually. But that strikes me as inefficient.
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.
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
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.