Architecting a stat system with structural changes.

This is an architectural question that I would like some opinions on. Here’s the scenario.

I have a type called StatContainer which implements IBufferElementData. It has 2 fields, an enum for the stat id and an int for the stat value. It is used as a standard way for stats to effect an entity; items grant stats, talents grant stats, debuffs temporarily grant negative stats, you get the picture. Some stats are resources which can be spent or lost and have min, max, and current values.

A weakness is that everything is accessed through this monolithic system. Imagine you have a system that moves all the entities in the scene based on their speed stat. It has to take in the buffer of ALL stats an entity has, search for the speed stat, and then move it. This is a lot worse then the ideal of simply querying for a SpeedComponent and applying whatever value is attached to it.

There are also cases where I want other knock on effects for stats. For instance, an entity with the “GrantsLazerAttackAbility” should have a LazerAttack component on it. An entity with some number of “AugmentSlots” will have that number represented as a stat so that all of the same systems that underlie stats can interact with it. However, if an entity has StatContainer: { AugmentSlots, 5 } then it should have an accompanying AugmentSlotBuffer with 5 elements in it. Moreover, that AugmentSlotBuffer should grow or shrink if AugmentSlots changes.

One solution to the problem of accessing the entire buffer is to define something like a SpeedComponent that acts like read access to the Speed stat. There could be a system that would read the StatContainer buffer and write the value of the Speed stat to the Speed components. That way systems that frequently access that data only have to bring in 1 very small component.

The only solution to the second problem (Stats adding components to entities) that I have found is to just manually handle these cases. Where stats are collated I have a bunch of if statements that check if a stat requires special handling.

The first solution (while good enough) shares a problem with the second; they require special code to be written for a stat every time. Each variant of the SpeedComponent (HealthComponent, ManaComponent, whatever) will require its own component and systems to be written to do basically the same thing every time. Each “special” stat handling requires a new ugly if statement added to the pile. Does anyone have any recommendations? At this point I’m considering treating each entry in the StatContainer buffer as its own scriptable object.

I cannot comment on how I’d implement it in ECS - just not enough EXP to comment. :wink:

But I can share some thoughts. I’m thinking backwards: I imagine entities have a SpeedComponent. That has a default (max) speed and a current (max) speed which will usually be the same. EntityMoveSystem will use that “current” value as multiplier for the speed.

Now entity picks up an item. This item is maybe queued for another system to process the stats (I’ll disregard inventory for now). This could simply be an ApplyStatsSystem which knows about all the stats and which components it affects.

When this system gets to process Item X with a SpeedBuffStat component on it, it reads that value (could be an absolute value or a percentage) and also gets the entity’s SpeedComponent, alters the current (max) speed value of the component and done. A bit later in the same frame entity is moved but now at a different speed.

Now all you need is to register for this stat to be removed at some point, which works the same way except the stat buff is removed and the SpeedComponent’s default value is assigned to the current speed.

Is that something that helps?

Ideally you’d only need two components for every stat: the base stat, ie that the entity has or uses that stat, and the modifier stat which essentially says “okay override the speed value unless frameCount is greater than X” or something like that.

Hard for me to think in ECS without actually coding in it. :wink: