ECS components and inharitance

I have a question is it a good idea to use inheritance for components?

For example:

public struct Buff :IComponentData {

public float Time;

}

And some buffs like:

public struct HealingBuff :Buff {

public float HealRate;

}
public struct SpeedBuff :Buff {

public float SpeedIncrease;

}

I could have one system that counts down a time for all buffs, another that removes all buffs for which has expired and one system per buff that performs buffs action. Is it a good idea?

Well I believe you can do that.

But be aware of using something like that:

EntityManager.GetComponent<Buff>(entity);

or

struct Data {
      ComponentDataArray<Buff> data;
}

this won’t work at all!

You cannot inherit from another struct in C#

Your second two structs should implement IComponentData (cannot inherit from Buff). Then you can add Buff and the buff component that you want (speed, health, etc) to the entity. This is composition, inheritance doesn’t work in ECS implementations.

1 Like

Let me see, you are suggesting a separate BuffTime component, am I right?

I thought of that too, but then how could you remove the buff component when the buff time runs out? You would need some kind of a reference inside the BuffTime component that refers to the actual buff component. Otherwise the system wouldn’t know which component type to query for.

your buff could be a separate Entity:

public struct Buff : IComponentData {
    Entity target;
    float time;
}

then your systems applies buffs to the targets (plus, this way a single entity can have multiple buffs)
to remove a buff, destroy the entire entity.

you could also try to separate the components and have

public struct BuffTarget : ISharedComponentData {
    Entity target;
}
public struct BuffTime : IComponentData {
    float time;
}

if you need to get all the buffs that an entity have

1 Like

But you cant filter using ComponentData field value. But this solution is OK and alternative is to create more complex components with own time field and effect field, but it would require creating timer system for each buff. A separate entity with a target is more flexible.

An solution would be simply having HealingBuff and SpeedBuff be regular IComponentDatas, and then you’d have a HealingBuffSystem that operates on entities that have Buff + HealingBuff components, and a SpeedBuffSystem for Buff + SpeedBuff

On top of that, you’d also have a regular BuffSystem that operates only on Buff, and handles the timing logic

edit: wait nvm, it would cause problems if you want to have multiple different buffs with multiple timers on the same entity. Separate entity per buff or buff components that handle time on their own seems to be the solution

Yes, that is the case, and the first solution is better because you can have separate components for buff timer and buff effect and have just one system that updates time and one that removes all buffs.

Assuming it’s multiplayer then you likely need an instance per buff on the server to tick them correctly. But what you send to the client can always be at least partially collapsed if not completely.

What you will find is that if you make the system data driven you will wind up with almost no logic specific to a single buff/debuff. So I normally go with something like an Effect and EffectType. I actually use that for anything that can happen in combat. Damage, damage over time, buffs, debufs, are all effects of various types. The types I categorize by the mechanic, not what you call them in game.

For instance here a partial list of effects from my game.

DirectDamage,
DamageOverTime,
DirectHealing,
HealingOverTime,
DamageTakenIncrease,
DamageTakenDecrease,
DamageDecrease,
HealingTakenDecrease,
HealingIncrease,
HealingDecrease,
SpeedIncrease,
SpeedDecrease,
CriticalIncrease,
CriticalDecrease,
PhysicalResistIncrease,
PhysicalResistDecrease,
DamageReflect

DamageOverTime/HealingOverTime for example is actually a bug. I still had it in my list and put it here to illustrate how not to do it. Healing over time should be though of as DirectHealing with a certain duration/number of ticks.

So the hard part here is identifying the base mechanics.

So what you will end up with is a system that is ticking all effects using a single global timer. One second is usually the simplest. From there I would probably then have systems designed around the various base effect mechanics. Like your tick system marks an effect as this effect should fire. And then your other systems that handle mechanic specific logic filter by is the effect marked as should tick, and is it an effect type that system deals with. So kind of like a pipeline flow.

You could just start with one system and then expand as you grow, that is most likely what I would do starting out. Single system that abstracts out the handling into methods which can later move to systems of their own.

Stacking should be abstracted out. It’s potentially the most complex part of all this and it should be handled uniformly over all effects. That’s not to say you can’t have multiple stacking types. But make them effect specific and be prepared for some pain down the road.