Designing Stats with ES

Hey guys

I want to ask about the pass I should take for implementing stats using the entity component system.

Examples of some combat stats: Energy, Stamina, Intellect, Endurance, Strength, Armor, Luck, Charisma,…

One approach is to use a different class for each stat component, for example, CharacterEnergy.java, CharacterStamina.java, …

Another approach is to use a general Stat component class and buff them to the entity.

public class Stat implements PersistentComponent {

    private int type;
    private int value;
    private int maxValue;

    public Stat() {
    }

    public Stat(int type, int value, int maxValue) {
        this.type = type;
        this.value = value;
        this.maxValue = maxValue;
    }

    public static Stat create(String type, int value, int maxValue, EntityData ed){
        Stat stat = new Stat();
        stat.type = ed.getStrings().getStringId(type, true);
        stat.value = value;
        stat.maxValue = maxValue;
        return stat;
    }
    public int getTypeId() {
        return type;
    }
    
    public String getTypeName(EntityData ed){
        return ed.getStrings().getString(type);
    }

    public int getValue() {
        return value;
    }

    public int getMaxValue() {
        return maxValue;
    }

    public Stat newAdjusted(int delta) {
        return new Stat(type, value + delta, maxValue);
    }
}

As stats are mostly just an integer value, I am thinking of using a general component which can be used in other systems out of combat also (ex crafting, foods,…).

I am not yet confident which approach is more suitable so I am interested to hear your ideas. :slightly_smiling_face:

Make Type an enum and forget about a String/int there.
Will you have buffs? if so your stats might not be int. That’s the thing I am stuck at as well actually:
Imagine you get a 9% buff on your intelligence. Then order matters: (int + 10) * 1.09 != (int * 1.09) + 10

I mean I guess you are only talking about storing the stats there though. Typically I have at least a Health and a Mana Component, other things which are only used together and not for many systems could be one Component Stats which has all.

I mean it depends on what you want to achieve. Where is Strength and Luck needed? And also: Aren’t all that stats always available but just sometimes 0? :thinking:

Yes, but have not thought about how the mechanics should be (probably with a StatChange component which is going to add or decrease X amount of stat value. There won’t be multiplication I guess) whatever it will be, I want to keep them integer based.

One major drawback with this approach is that I won’t be able to add a new stat type later, else it will ruin database table. (Note I am using Zay-ES SqlEntityData).

Have not designed the stat calculation stuff for combat system yet, but for example, I probably will use luck to decide if the enemy should first start attacking or the player. As said not very sure yet about those. :upside_down_face:

Hmm, why? :thinking:

Some notes about this approach… (which as I recall is the one I was taking with Mythruna)

It implies that each stat is also its own entity attached to the player.

In that case, “type” could also be another class if you wanted easy collecting. (Though not required as I believe I did the same thing.) The down side is that you’d still end up having Energy, Stamina, etc. classes. And really, querying by type field is not so hard anyway. Though I probably would be tempted to separate StatType from StatValue maybe just to avoid having all of the string ID shuffling in the stat value class. But maybe that’s not a big deal.

It is nice because things like buffs can also be generic and just tag the stat entity.

Allows regular things like name components, etc. to be associated with that stat entity which means you can call the same ‘type’ different things in different contexts even if they are effectively doing the same thing in the game engine. (Don’t know if this would come up but it’s free.)

enum fields are not supported by the SQL layer because they are inefficient.

1 Like

In my game I have two stat components. One holds all the more static stats such as strength, int, con, etc and one to hold ones that change frequently like health. This was to reduce unneeded network transfer but I could probably do that with a single component now that I understand the serialization better.

Internally, I use integers as ID’s and floats as values for stats with a hashmap to hold them. It worked out better for me that way because some stats are treated like integers (things like armor or strength) but other stats boost other stats based on a percentage. It also makes it really easy to add another stat. I have an internal class that holds a reference to information about a stat like what it’s default value because that information didn’t have to be stored.

public class CharStats extends EntityComponent {

  public static int ARMOR = 0;
  public static int STRENGTH = 1;
  public static int DEX = 2;
  public static int INTEL = 3;
  public static int CON = 4;
  public static int CRIT = 5;


  private HashMap<Integer, Float> stats = new HashMap<>();

  public void setStat(int id, float value) {
    stats.put(id, value);
  }

  public float getStat(int id) {
    CharStat stat = EntityFactory.getInstance().getCharacterStat(id);
    return stats.getOrDefault(id, stat.defaultValue);
  }

  public int getStr() {
    return (int) getStat(STRENGTH);
  }
  
  public void setStr(int str) {
    stats.put(STRENGTH, (float) str);
  }
}
1 Like

Thanks so much, guys, will keep going with the second approach. Going to ask for help if I get stuck somewhere. :slightly_smiling_face: