
In this tutorial, I will show you how to create a flexible buff system for Unity using scriptable objects. We will use scriptable objects as a way to quickly create and manage buffs without having to use external data types such as xml or a text file. This lets us separate our buff data such as duration and stat changes from the game logic. As you will see soon, this will also allow us to bind our scriptable objects as assets, using the CreateAssetMenu attribute.
Update: The buff system has been updated with effect and duration extending flags. The original interface for
TimedBuff
has been updated to reflect that. Tutorial and github repository has been updated to reflect the changes.
The premise of our design will revolve around three classes. The player class, a buff class, and a buff scriptable object. The player class serves as our generic player model, it can have multiples buffs at the same time, as well as multiple buffs of the same type (stacking). The buff scriptable object acts as our data and the buff class is our bridge between the two, handling the logic of each individual buff.
Buff System Implementation
First we’ll create an abstract version of our buff scriptable object to build on. Every buff should have a duration, and we need a way to create this buff from our data.
ScriptableBuff.cs
public abstract class ScriptableBuff : ScriptableObject { /** * Time duration of the buff in seconds. */ public float Duration; /** * Duration is increased each time the buff is applied. */ public bool IsDurationStacked; /** * Effect value is increased each time the buff is applied. */ public bool IsEffectStacked; public abstract TimedBuff InitializeBuff(GameObject obj); }
ScriptableBuff contains buff fields and values that can be used across multiple buffs. These are the duration, and two flags that determine whether the duration and effects re stacked. The TimedBuff is the bridge we need to link the data and played model. We will implement that next.
TimedBuff.cs
public abstract class TimedBuff { protected float Duration; protected int EffectStacks; public ScriptableBuff Buff { get; } protected readonly GameObject Obj; public bool IsFinished; public TimedBuff(ScriptableBuff buff, GameObject obj) { Buff = buff; Obj = obj; } public void Tick(float delta) { Duration -= delta; if (Duration <= 0) { End(); IsFinished = true; } } /** * Activates buff or extends duration if ScriptableBuff has IsDurationStacked or IsEffectStacked set to true. */ public void Activate() { if (Buff.IsEffectStacked || Duration <= 0) { ApplyEffect(); EffectStacks++; } if (Buff.IsDurationStacked || Duration <= 0) { Duration += Buff.Duration; } } protected abstract void ApplyEffect(); public abstract void End(); }
Another abstraction! There’s a little more code for this class, so I will explain what’s going on. For our example, we assume all buffs have a duration, however you can change that pretty easily. We want all TimedBuff objects to store reference to the duration, the buff data, and the game object that is receiving the buff. There’s also the following methods:
void Tick(float delta) – call this within the player’s update loop. Used to keep a timer on the duration remaining and calls End() when completed.
void Activate() – call this after initialization to activate the buff logic.void End() – called when duration is finished. May also be called early to ‘end’ the buff.
Now we just need the player class. For this, we will create a MonoBehaviour component called BuffableEntity.
BuffableEntity.cs
public class BuffableEntity: MonoBehaviour { private readonly Dictionary<ScriptableBuff, TimedBuff> _buffs = new Dictionary<ScriptableBuff, TimedBuff>(); void Update() { //OPTIONAL, return before updating each buff if game is paused //if (Game.isPaused) // return; foreach (var buff in _buffs.Values.ToList()) { buff.Tick(Time.deltaTime); if (buff.IsFinished) { _buffs.Remove(buff.Buff); } } } public void AddBuff(TimedBuff buff) { if (_buffs.ContainsKey(buff.Buff)) { _buffs[buff.Buff].Activate(); } else { _buffs.Add(buff.Buff, buff); buff.Activate(); } } }
BuffableEntity keeps a dictionary of all its current buffs. Each update updates existing buffs and removes completed ones. Updating the individual buffs gives more flexibility and control on their lifecycle. For example: buff duration can be frozen when the game is paused or player is phased out.
Speed Buff Example
So how do we use this buff system? Let’s make a quick ‘speed boost’ buff together.
SpeedBuff.cs
[CreateAssetMenu(menuName = "Buffs/SpeedBuff")] public class SpeedBuff: ScriptableBuff { public float SpeedIncrease; public override TimedBuff InitializeBuff(GameObject obj) { return new TimedSpeedBuff(Duration, this, obj); } }
This extends our ScriptableBuff to hold data on the SpeedIncrease. In addition, by calling the InitializeBuff, we can create a TimedSpeedBuff, which we will implement next. The CreateAssetMenu attribute allows the scriptable object to be created as an asset in the project menu.
TimedSpeedBuff.cs
public class TimedSpeedBuff : TimedBuff { private readonly MovementComponent _movementComponent; public TimedSpeedBuff(ScriptableBuff buff, GameObject obj) : base(buff, obj) { //Getting MovementComponent, replace with your own implementation _movementComponent = obj.GetComponent(); } protected override void ApplyEffect() { //Add speed increase to MovementComponent ScriptableSpeedBuff speedBuff = (ScriptableSpeedBuff) Buff; _movementComponent.MovementSpeed += speedBuff.SpeedIncrease; } public override void End() { //Revert speed increase ScriptableSpeedBuff speedBuff = (ScriptableSpeedBuff) Buff; _movementComponent.MovementSpeed -= speedBuff.SpeedIncrease * EffectStacks; EffectStacks = 0; } }
This is just a simple buff, but as you can see, in the ApplyEffect() method, the speed of the MovementComponent is increased. End() reverts the buff effect. This is all you need to implement your speed buff.
You should now be able to create an asset labeled Buffs/SpeedBuff. You’ll be able to create different versions of the speed buff while using the same implementation! This is one of the benefits of using Scriptable Objects.
Conclusion
So to recap, if you want to implement different types of buffs, the steps you would take are:
- Implement a new version of TimedBuff. This is where all the logic goes, if you have any stat changes, effects to generate, they all go here.
- Implement a new version of ScriptableBuff. This is where all the logic goes, so the values of stat changes, art resources, sounds, etc should go here.
Hope this tutorial for a buff system in Unity helps, you can find the source on github. Feel free to leave a comment with any suggestions or questions you may have!
Interested in learning more about Unity? Check out my other Unity tutorials!
Change Log
- 12/30/16 – Added github repository
- 12/7/19 – Updated buff system with effect and duration stacking flags
Thanks man! This is really useful!
Very useful!! Will try it for my game 🙂
Thanks, Glad you found it helpful!
Hi, very interesting stuff. But I have a problem with understanding something – do you add a buff using those scripts?
I mean, I’d like to be able to add this Speedbuff via code (without assset) and via asset created in Unity (a ScriptableObject). How can I do that?
The problem is, the ScriptableObject is of type SpeedBuff, but buff is of type TimedBuff. So I can’t figure out how to add it.
And I would love to use if both ways – from assets, and via creating and instance of script, just like normal instance class.
Hey Lothar, thanks for checking out tutorial!
To add the buff to a player/enemy/mob you would give it the BuffableEntity component. From there you can call AddBuff(TimedBuff buff) anytime you want to give it a buff. You can pass any type of timed buff there, so speed buff, etc would be acceptable.
So you can pass buffs that are created as assets, or at runtime.
Can you show an actual example of this, by any chance? Maybe I’m not getting it, but nothing I put into any script in AddBuff(VALUEHERE) seems to work in any context.
AddBuff(speedBuff.InitializeBuff(player.gameObject));
Great stuff! I have implemented your system to replace my old one with Coroutines. With your approach I can also track every buff currently on the player, which can also help for implementing buff icons in the UI. Thank you so much! There’s one thing that has been troubling me for a couple days, though. Say I want to implement a non-stackable buff, for example a Speed=15 buff. So if the buff ends, I can return the speed value to default (say 10), so if I cast the same buff on the player, he will maintain the buffed values (15)… Read more »
Hey Kevin, thanks for checking out the tutorial! There’s a couple ways to tackle this, I think the first thing would be to add data to the ScriptableBuff, so in addition to the duration, have a bool to designate if the buff is unique (non-stackable). Then when you call AddBuff in the BuffableEntity component, you can check the unique field, and if it is, you can check if the buff already exists. If it’s already existing, you can either extend the duration, override the existing buff, or how ever you wish to implement your system. In regards to the Berserk… Read more »
That makes sense. I will try it out, thank you!
Hello, Jonathan. Could you please explain how do you actually achieve that? I’ve been trying to solve this problem for 4 hours already and no result….
@Andrew, could you explain what’s the problem you are trying to solve? 🙂
I’m trying to make the same buff, let’s say, speed increase to be non-stackable. My default speed is 10. When i pick up the buff, the speed goes to 15. When i pick up the same buff, my speed goes 20 which is not acceptable. I want the speed to remain the same, but at the same time i want to update the duration (duration which is left from the first buff + duration of a new buff). Well, I’m trying to achieve what Kevin said except the Berserk thing.
I see what you mean 🙂 I would add two boolean fields to ScriptableBuff. They would be “IsDurationStacked” and “IsEffectStacked”. Then for timed buff the following changes can be made: public void Activate() { if (Buff.IsDurationStacked) { Duration += Buff.Duration; } if (Buff.IsEffectStacked || Duration <= 0) { ApplyEffect(); } } protected abstract void ApplyEffect(); This checks if Duration should be stacked and also if the Effect should be stacked before applying them. I will push an update to the github repository as well. Hope this helps. Feel free to let me know if you would like to see any… Read more »
public void Activate()
{
if (Buff.IsDurationStacked)
{
Duration += Buff.Duration;
}
Above stuff increases the duration by 2 even though it was the 1st item that was picked up.
Below stuff won’t trigger ApplyEffect(); when Buff.IsEffectStacked is set to false
if (Buff.IsEffectStacked || Duration <= 0)
{
ApplyEffect();
}
Maybe I'm calling the AddBuff() wrong?
This is how i do it now:
public ScriptableSpeedBuff buff;
protected override void Pick()
{
_pickingCollider.GetComponent().AddBuff(buff.InitializeBuff(_pickingCollider.gameObject));
}
Sorry Andrew, small bug in the previous update. Here is a fix: https://github.com/xjjon/unity-flexible-buff-system/commit/2b7a30a27cc36ec7769ed6b4e589689399dea85a
Basically for the two new flags to work, BuffableEntity should call ApplyEffect on the same instance of
TimedBuff
. Previously it was adding a new instance to the list each timeAddBuff
was called.Updated to use a Dictionary so that only one instance of each buff is active at a time. This way the duration and effect stacking should work as expected.
Thanks for pointing this out!
Also, the way you AddBuff looks correct to me.
@Jonathan, foreach loop in BuffableEntity throws an error when the buff has ended: foreach (var buff in _buffs.Values) { buff.Tick(Time.deltaTime); if (buff.IsFinished) { //Here it throws an error _buffs.Remove(buff.Buff); } } I’ve changed the first line to this: foreach (var buff in _buffs.Values.ToList()) and it seems to work, but I don’t think this is a good solution. Also, extending the duration works properly now, but the effect still doesn’t apply. It’s either extend the duration or apply the effect. Is it possible to apply the effect and extend the duration at the same time? I’ve tried to put ApplyEffect() in… Read more »
Oops, sorry! I should have caught that in my tests.
.Values().ToList()
is the correct way to do it as the collection can’t be mutated if we are using it in the loop.Fixed the issue with the effect not being applied:
https://github.com/xjjon/unity-flexible-buff-system/commit/37ebec0473baf4b6231004194b89b55ae50963b9
The issue was the ordering of the two conditions. Since the effect checks for duration or the flag. Applying the effect before duration will fix the issue.
Thanks again for pointing these out!
Oh yes, now it’s working!!! Thank you, man!
Hey man awesome tutorial. I get the gist of it and have the structure working in my code but what I don’t understand is
– public override TimedBuff InitializeBuff(GameObject obj);
We don’t ever call this, yet the code inside seems to run? Do overidden abstract methods automatically get called? Is this some kind of special constructor?
Thanks for reading, Nico -public override TimedBuff InitializeBuff(GameObject obj) This is contained inside a Scriptable Object which is primarily used to store data. This is where we store the values of the buff. So when we want to apply the buff to a player, we need an instance of TimedBuff, which is when we would call InitializeBuff with the GameObject, and in return we get the TimedBuff object. The reason we do this is because Scriptable Objects are used to store data, hence each only has one instance of it. We need to keep track of values that change constantly,… Read more »
Hello, may I ask if there is a complete demonstration of the case, thank you!
Hi Jack, you can find a full example on the github repository for the project: https://github.com/xjjon/unity-flexible-buff-system
Hello i was adding your system today. and cant figure out the 2 overloads for Activate.. private TimedSpeedBuff tsbData; void OnTriggerEnter(Collider other) { //Debug.Log(“speed ” + tsbData.runSpeed “”); tpcmData.Activate(); //Destroy(other.gameObject); } // as is is null private SpeedBuff speedBuff; //Charmotor call private MoveBehaviour movementComponent; public TimedSpeedBuff(float duration, ScriptableBuff buff, GameObject obj) : base(duration, buff, obj) { movementComponent = obj.GetComponent(); speedBuff = (SpeedBuff)buff; } public override void Activate() { SpeedBuff speedBuff = (SpeedBuff)buff; movementComponent.runSpeed += speedBuff.SpeedIncrease; }
I’m not sure what you’re asking – what are you having problems with? Everything looks fine
Hi, @ first thx for this.
But im totally confused…
Im call BuffableEntity.AddBuff( what here) ; ?
How do i AddBuff ?
and why public List CurrentBuffs = new List(); is public ? its not shown in Inspector because TimedBuff does not inherit from MonoBehaviour
Hey Oliver,
To add the buff, you should call AddBuff with whatever TimedBuff you wish to add.
Usually that would be something like from a OnTriggerEnter when the player collides with a powerup or through some other method.
should TimedBuff.cs inherit from Monobehaviour ? because i cant asign TImedSpeedBuff.cs wich inherit from TimedBuff
No, it shouldn’t inherit MonoBehavior.
You can assign SpeedBuff, which is a ScriptableBuff, which can be assigned via inspector to some GameObject.
Then you can call something like: buffableEntity.addBuff(speedBuff.InitializeBuff());
Works great, thx.
so i used the CreateAssetMenu to create a new buff, but i do not know how to add it to a buffableentity via .addbuff(no idea what to put here);
also how would i go about changing variables of the buff at runtime, before applying the buff to an object.
may learn something from your blog, thanks.
My game has around 2,000 different buffs, seems like to make that work I’d need 4,000 scripts. Is there a more streamlined approach in your opinion?
Hi Bradley, thanks for visiting. What type of buffs are you looking to make? You should only need 1 buff script per type of buff. (i.e. if the buff has different effects. For buffs with different stats or values, you can share the same script. So if you had SpeedBuff DamageBuff those would both use different scripts – but if you had multiple speed buffs (at varying speeds) then you could just share that script and configure the values in the scriptable object. —- Another improvement you can make: Are you using anything to define your Attributes? For example, Speed,… Read more »
you know actually that could work. If I showed you what I’m doing maybe it would help I’m remaking the Final Fantasy XIV battle system inside Unity. So none of the MMO stuff (gear, items etc) just the jobs of the game and enemies to fight in scaling difficulty in a battle arena. The game uses MANY buffs to work. Ranging from stat increases to complicated effects and combo actions that are technically buffs till the next action. From what you just said I can already see that making a combo buff abstract class and then generating object from there… Read more »
Yeah that’s a lot of buffs – I think for something of that scale you are better off defining your data in a database or json type of file. Scriptable object is nice for prototyping or games with less data, but for larger scale stuff I find it’s not as easy to manage and it’s not easy to update. The scriptable object in this tutorial is basically used as the datastore, so if you replace that with a json/yaml object then you can just programatically load the data for the buffs. buffs.yaml - buff1: type: timedAttributeBuff icon: speedBoost.png data: attributeType:… Read more »
I had a prototype xml that was working well enough for the buffs before. I think I should probably go back to that and add functionality. There doesn’t seem to be a super great catchall solution to the fact that the buffs all do such vastly different things. You’ve been a great help!
hey i was wondering if you can help me out ima bit of a noob but ive been following this tutorial on how to make an inventory with scriptable objects, i created an inventory and a small ui on the bottom of screen to hold like 5 items that i want to use to give me health for a certain time then stop. or just give me health.. how would i go about implementing it into my current script
This tutorial is simply fantastic, thank you so much! You are a life saver
I realize this is an older post, but I wanted to respond just incase you still check this. Thank you so much. This really helped me to get my buff system up and running. I’m in the process of developing a Turn Based Idle RPG and thus far I’m having great luck implementing a variety of buffs/skills based on your code. I turned the duration into an integer, and I manually call the Update function of BuffableEntity each round. Unfortunately, I hit a snag. I’m trying to implement some Aura type buffs. These will be applied to every party member… Read more »
I’d still be curious about your response, but I found a solution to the problem I was having. Basically, I needed to separate the activation of Auras into a separate foreach party member loop, and then Update buffs in another foreach party member loop. I had them in the same loop initially, which meant some party members were updating auras from other party members that should have been turned off.
Thanks again for your wonderful example!
I think that would be a good solution. Basically each `entity` that can be buffed should have it’s own update/loop to manage it’s own buffs. It has the added benefit that when the `entity` is killed/destroyed, the buffs are cleaned up automatically with it.
Thanks for checking out this tutorial and I’m glad it could help!
This is really cool, I’m just trying to figure out why in Activate() in TimedBuff.cs we check if the duration is >= 0 in both cases?
Wouldn’t the case be that if the duration was zero and the buff had ended, IsFinished, it would be removed from the BuffableEntity? When would the case be that there would be a buff with duration 0? Just trying to wrap my head around it! Probably missed something.
Cheers!
Thank you for making this; it is very useful. I read what I could find about this topic and one of the challenges I had was restoration. A safe implementation may be to have an Enemy method GetMobDataWithBuffsByType(MobDataType mobDataType) that adds the base MobData like Defense, Damage or MoveSpeed to a call UnitBuffManager.GetTotalBuffByType(MobDataType buffType) which calculates the total of all the current Buffs of that stat type via GetBuffByType(buffType). There is no restoration needed. And you can also cleanly clamp the current mobdata stat against negatives and extremes. I’d be happy to share my code if interested. Again great job.
Thank you for making this; it is very useful. I read what I could find about this topic and one of the challenges I had was restoration. A safe implementation may be to have an Enemy method GetMobDataWithBuffsByType(MobDataType mobDataType) that adds the base MobData like Defense, Damage or MoveSpeed to a call UnitBuffManager.GetTotalBuffByType(MobDataType buffType) which calculates the total of all the current Buffs of that stat type via GetBuffByType(buffType). There is no restoration needed. And you can also cleanly clamp the current mobdata stat against negatives and extremes. I’d be happy to share my code if interested. Again great job.
Thank you for this post. It helped me a lot to imlement my buff system. I now would like to implement debuffs like ingnited or poisoned. Do you have a hint on how to implement that? My idea was to start a coroutine for the damage but since the TimedBuff class does not inherit from MonoBehaviour it can not do that. Big thanks!
Hi, simple way would be to implement another timer in the buff to do some effect each ‘tick’ (such as damage from poison and bleed).
Here’s a small example:
https://gist.github.com/xjjon/6bbdeaaf2c6d31dc8284b087ed8c88f1
You’ll notice I added “TickTime” to ScriptableBuff to customize how often the tick effect should be applied.
Then in the TimedBuff class there is a TickTime that updates and when it hits 0 it will ApplyTickEffect()
The underlying buffs (such as poison and bleed) can implement the ApplyTickEffect() method to deal damage with each tick.
First of all big thanks for enlightening me. I still have a lot to learn. One quick note: when the enemy keeps standing in the fire and the debuff is constantly reapplied and therefore the debuff is readded and reactivated ApplyTickEffect is never called because TickTime is resetted. How would you avoid that? For now i just check TickTime is 0 in the Activate method and then set TickTime to Buff.TickTime.
Again thank you very much!
Hi, sorry for the late reply. I think you can just keep the existing TickTime if the buff is still active.
See: https://gist.github.com/xjjon/f7f51598d535728de68af840c603e648
hope this helps!
Thank you very much, this tutorial was quite useful for a buff system for our game. I still have a problem with removing buffs manually, without timeout. Do you know a simple way to remove a single instance of a buff from an entity only knowing that buff’s name?
Hi, you could add a
string Name
field to the ScriptableBuff object if you want to remove by name. Then create a RemoveBuff(string name) method that checks each entry in the dictionary for the buff name and remove it if it matches.Otherwise I would suggest just removing it using the scriptable object reference. See example:
https://gist.github.com/xjjon/33def9a56c3a319fd22f685db906a284