Time Related State Effects (Buffs/Debuffs) - What Are Manageable (Code) Designs?

Started by
7 comments, last by smasherprog 12 years, 1 month ago
I'm currently writing code for a game that has some basic spell effects and attacks. In the course of prototyping up some of the classes, I've come to realize that I'm spending an inordinate amount of time because I don't have a good system for keeping track of effects that have a time duration. I'm not doing anything fancy, it's very much in the vein of most RPGs, such as World of Warcraft with their buff/debuff system.

For example, right now, I have an ability that can be applied to multiple people within a certain radius of the player. When they receive the effect, they retain that effect for 5 seconds. Not too hard. But say now I want to prevent the attack from stacking, but if someone else applies the effect again, the effect simply lasts longer.

These are relatively simple things for me to describe, but I've found to be very difficult to get the code just right for all cases as the number of effects like these grows. My scheme for these effects has basically been explicitly coding the start and end times and doing everything based off of that. This is just not scalable for me in terms of programmer time as the number of effects grow (and as the effects themselves grow in complexity).

So the question:

What other ways are there to implement these time based effects?

I've given it some thought and I know that just about all of the effects I can think of can be described as some interval on a number line, so I could build some system that utilizes that to help me figure out all the effects that are currently active at any given time. My issue with this is that the number line is just way too simple, in my opinion, to help me figure out these effects in some complicated situations. I could easily end up with doing O(n^2) operations to make sure no effect stomps some other effect. I could minimize this by binning effects to avoid comparing effects that have no relation with each other. But I'm not convinced this solution is the best.

Any others out there? Performance, although important, is not the top priority. Ease of programming (both of the system itself and programming the effects so that they achieve their correct intended behavior) and iteration speed is top goal here.
Advertisement
add a stack of states. the checks are only performed when you add/remove things from the stack, while only few elements of the stack are used every frame by each entity to do the correct job (walking firing etc).

Peace and love, now I understand really what it means! Guardian Angels exist! Thanks!

The stack structure seems totally inappropriate to me. Perhaps I'm not understanding some detail implied in your suggestion?

What are the states? What if I need to remove something from the stack that isn't at the top of the stack? Then we are totally violating the stack.
This should be easy. Just use an array of flags identifying the buffs/debuffs with associated countdown timers. Have each timer decrease with each frame elapsed or poll the internal clock. Once the timers reaches zero, turn the effects off. When the buffs/debuffs are reapplied, increment the timers. Activate the effects only when the timers are greater than zero.
It sounded fun so I tried coming up with a structure. Here's the result:

public interface Effect{

//Different instances of same effect have same ID.
int getEffectID();

int getDuration();

//Do something to target when effect is applied
void applyToTarget(Entity target);

//Do something to target each update-phase when effect is on.
void continous(Entity target);

//Do something to target when effect expires.
void expire(Entity target);

//Effect can be applied to an already affected target.
//Whatever this effect causes, will be caused twice.
boolean stackable();

//Effect can be applied to an already affected target,
//but that will simply reset the timer.
boolean prolongable();
}

public class Entity{

//Single instances of effects
private ArrayList<Effect> activeEffects;

//IDs of effects. Two stacking "fireball-effects" will be represented as one FIREBALL_ID
private ArrayList<Integer> activeEffectsIDs;

//Mapping between effect-instance and time until expiration.
private HashMap<Effect, Integer> timeRemaining;


public void recieveEffect(Effect e){

//Previously unaffected, or effect is stackable
if(!activeEffectsIDs.contains(e.getEffectID()) || e.stackable()){

//Add effect
activeEffects.add(e);
activeEffectsIDs.add(e.getEffectID());
timeRemaining.put(e, e.getDuration());
e.applyToTarget(this);

//Affected already, not stackable, but prolongable
}else if( e.prolongable()){

//Prolong effect
for(Effect effect : timeRemaining.keySet()){
if(effect.getEffectID() == e.getEffectID()){
timeRemaining.put(effect, effect.getDuration());
break;
}
}

}
}

public void update(int timePassed){

//Countdown durations
for(Integer i : timeRemaining.values()){
i = new Integer(i - timePassed);
}

//If some effect has expired, remove it and stop tracking the remaining time.
Iterator<Effect> activeEffectsIt = activeEffects.iterator();
while(activeEffectsIt.hasNext()){
Effect e = activeEffectsIt.next();
if(timeRemaining.get(e) <= 0){
activeEffectsIDs.remove(e.getEffectID());
activeEffectsIt.remove();
timeRemaining.remove(e);
e.expire(this);
}
}

//Keep doing whatever the effect is supposed to do
for(Effect e : activeEffects){
e.continous(this);
}

}
}


I haven't tried the code, so obviously I don't know how well it works in a real game, but I think the structure is pretty clear and easy to extend.

As you can see all the effect handling is already in place in the Entity class, so the only thing you need when coming up with new effects is to let them implement the Effect interface. I'm not sure I understood your question correctly but I tried to make it easy to define stacking properties and such without having to hard-code.


Btw, the "code editor" is a pain in the ass! Is there some trick to get proper indentation right away?

This should be easy. Just use an array of flags identifying the buffs/debuffs with associated countdown timers. Have each timer decrease with each frame elapsed or poll the internal clock. Once the timers reaches zero, turn the effects off. When the buffs/debuffs are reapplied, increment the timers. Activate the effects only when the timers are greater than zero.


This is a very simple system (which I like, and I've already considered), but I feel that it is too simple for some of my use cases. Stackable effects are not possible here. A simple alteration would be to allow every effect to have a counter and a list of times so you can know when each stacked effect wears off (assuming each stacked effect remains individual, each expiring at their own time). Even further, I could eliminate the counter altogether since the list of times itself contains the count (presumably). Based on my needs, I feel like this simple solution is most likely for me to use.


It sounded fun so I tried coming up with a structure. Here's the result:

public interface Effect{

//Different instances of same effect have same ID.
int getEffectID();

int getDuration();

//Do something to target when effect is applied
void applyToTarget(Entity target);

//Do something to target each update-phase when effect is on.
void continous(Entity target);

//Do something to target when effect expires.
void expire(Entity target);

//Effect can be applied to an already affected target.
//Whatever this effect causes, will be caused twice.
boolean stackable();

//Effect can be applied to an already affected target,
//but that will simply reset the timer.
boolean prolongable();
}

public class Entity{

//Single instances of effects
private ArrayList<Effect> activeEffects;

//IDs of effects. Two stacking "fireball-effects" will be represented as one FIREBALL_ID
private ArrayList<Integer> activeEffectsIDs;

//Mapping between effect-instance and time until expiration.
private HashMap<Effect, Integer> timeRemaining;


public void recieveEffect(Effect e){

//Previously unaffected, or effect is stackable
if(!activeEffectsIDs.contains(e.getEffectID()) || e.stackable()){

//Add effect
activeEffects.add(e);
activeEffectsIDs.add(e.getEffectID());
timeRemaining.put(e, e.getDuration());
e.applyToTarget(this);

//Affected already, not stackable, but prolongable
}else if( e.prolongable()){

//Prolong effect
for(Effect effect : timeRemaining.keySet()){
if(effect.getEffectID() == e.getEffectID()){
timeRemaining.put(effect, effect.getDuration());
break;
}
}

}
}

public void update(int timePassed){

//Countdown durations
for(Integer i : timeRemaining.values()){
i = new Integer(i - timePassed);
}

//If some effect has expired, remove it and stop tracking the remaining time.
Iterator<Effect> activeEffectsIt = activeEffects.iterator();
while(activeEffectsIt.hasNext()){
Effect e = activeEffectsIt.next();
if(timeRemaining.get(e) <= 0){
activeEffectsIDs.remove(e.getEffectID());
activeEffectsIt.remove();
timeRemaining.remove(e);
e.expire(this);
}
}

//Keep doing whatever the effect is supposed to do
for(Effect e : activeEffects){
e.continous(this);
}

}
}


I haven't tried the code, so obviously I don't know how well it works in a real game, but I think the structure is pretty clear and easy to extend.

As you can see all the effect handling is already in place in the Entity class, so the only thing you need when coming up with new effects is to let them implement the Effect interface. I'm not sure I understood your question correctly but I tried to make it easy to define stacking properties and such without having to hard-code.


Btw, the "code editor" is a pain in the ass! Is there some trick to get proper indentation right away?


Seems reasonable, I guess. The main things I've been working on are things like, allowing players to get a speed boost for a certain duration of time or increasing their damage output for some amount of time. Pretty simple stuff. But future effects I anticipate allowing effects to stack and having actual effects that must be maintained over time and not simply a value change upon application and removal.

There are other things such as effects that will remove other effects or be reduced drastically in effectiveness by the presence of others. These kinds of effects, if I had to search, would force me to perform a quadratic time algorithm to maintain all effects. But a hash table could alleviate that, so I'm starting to think this problem is simpler than I had first anticipated. My main issue now is looking more like how to let this system be general enough so that a designer can create the effects that they want and have it "just work" without me helping them along the way for every effect... But even that is starting to look really simple to me now if I can categorize effects, have unique identifiers for each effect, and provide proper interfaces for those effects to be applied.
One way to keep track of which effects are present, without constantly having to poll might be to have some kind of "listener-system". When you apply an effect that depends on other effects being present, you do one check, and then listens for any change. Any time you add or remove some effect you check if someone is "listening", and in that case informs the listener about the change.

So, let's say there's an effect "Burn" that only works if target is not "Frozen". Fireball is cast on a clean target and Burn is applied. Now you store the mapping [Frozen -> Burn] in some HashMap, to reflect that Burn is "listening to" Frozen. When Frostbolt is cast on the target, Frozen is applied, and since Burn is listening, it is informed about the new effect. From now on burn does no damage, as Frozen is present. It's still listening, though, and as soon as Frozen expires, Burn will start doing dmg again.

This may be overkill, but atleast all checks are made only when adding/removing effects. That should be quite effective if you have long-lasting buffs, at least (?)
I'm probably do (C#):

interface IBuffEffect
{
void Apply(IDictionary<Type,IBuffEffect> targetEffectContainer);
void Update(GameTime gameTime);
}

  • Code that wants to access an existing effect use the dictionary methods, such as: activeEffects.Contains(typeof(DesiredEffectType));
  • Effects that expire hold their own expiration counter and check it in their Update implementation.
  • Applying the same effect could look up the existing effect in the dictionary and manipulate the existing effect if it exists.
  • Updating all active effects would simply be: foreach (var effect in activeEffect.Values) { effect.Update(gametime); }
if you are using c++ . . . .

class MasterEffect{
//in here is what the actual effect does,

unsigned int StrModifier;
unsigned int IntModifier;
// what other information you need should be stored here
float radius;

};
Enum Effect_Names{ FireBall, Ice_Blast, MAX_EFFECTS };


MasterEffect MasterEffectList[EFFECTS];

class effect{
public:

unsigned int TimeLeft;
MasterEffect* mastereffect;

};

each player has a std::vector<effect> Effects;

The masterlist should be created when your application starts with all the information for each effect. Then, if a player gets an effect, you pass the pointer of the specific effect in the MasterEffectsList. Then, each iteration of your game, check each effect to see if its duration is <= 0, when it is, remove it. You can have players contain several buffers for Short, Mid, and Long term effects so that you are not doing work which you know doesnt need to be done. For example if an effect has more than 1 hour worth of duration, it starts in the LongTerm effect buffer. Once its duration goes below that, it moves to the Mid term, and the Short term can be for effect which have a duration of less than 1 minute. Then, you only check specific buffers at certain intervals.
Wisdom is knowing when to shut up, so try it.
--Game Development http://nolimitsdesigns.com: Reliable UDP library, Threading library, Math Library, UI Library. Take a look, its all free.

This topic is closed to new replies.

Advertisement