Can anyone think of a better way to handle this problem?

I’m struggling to put my problem into technical terms that I can search for, so here’s an example of a hypothetical situation that might illustrate it. Apologies if it turns out to be rediculously simple, I’m just trying to figure out an elegant way to do this now, rather than causing myself a ton of work later if I do it in an extremely suboptimal way.

Imagine a game in which the player can turn into other creatures. Every creature has their own set of base stats that determine how they work.

For example;
Human - Strength 50 / Speed 50 / Constitution 50
Rabbit - 5 / 65 / 10
Horse - 130 / 70 / 80
etc.

There are many species, and there are many stats for each species, with each stat representing the same thing as its counterpart for any other species, except with a different value assigned. These stats will remain a constant that will never change outside of the game editor, but which will need to be easily and individually tweaked whilst testing game balance.

Simultaneously, every creature in the game also has their own, variable set of stats, based on levelling, experience of whatever. These will not change when you turn into a different animal, but are used to modify the base stats of whatever animal you turn into. This results in a really strong player turning into a really strong rabbit, relatively speaking (ie. compared to the average rabbit), even if being a strong rabbit isn’t terribly impressive.

So, the question is - how can I lay out my code to best handle a situation such as this (at least without it turning into a bloated mess once there are very many ‘stats’ and ‘species’)?

The nearest thing I’ve currently got to a solution is still messy and a pain in the arse to work with, but so far I’ve essentially got a couple of enumerations that would look sort of like this;

public enum Species { Human, Sheep, Horse, Rabbit, Tardigrade, etc }
public enum Stats { Strength, Speed, Constitution }

I also have a function that basically acts as an intermediary, and uses the two above overloads. Whenever you need to look at anything related to a stat, you’d chuch them in this;

GetBaseStat(Stat inputStat, Species inputSpecies)

and after far too many if or switch statements it’d return the variable you’re looking for from a sprawling mega-class. It started out small and easy to modify, but now it’s a massive mess and I hate working with it. It’s just got me thinking that, being informally self taught, surely there’s some super easy way to do this without all the heart ache, that I’ve naively gone and overlooked?

this is what polymorphism is all about

So, I’d create a ‘PlayerStat’ class, this acts as a facade to the current configuration. It stores all stats that are specific to the player, as well as properties that can be impacted by the underlying ‘species type’.

Then we have a ‘IStatModifier’ class, this is the type that each specie will uncover. The PlayerStat class will filter through this modifier.

So something like this:

public class PlayerStats
{
 
    public float baseStrength;
    public float baseSpeed;
    public float baseConstitution;
 
    public IStatModifier modifier;
 
    public float Strength
    {
        get
        {
            if(modifier == null) return baseStrength;
            else return modifier.GetStrength(baseStrength);
        }
    }
 
    public float Speed
    {
        get
        {
            if(modifier == null) return baseSpeed;
            else return modifier.GetSpeed(baseSpeed);
        }
    }
 
    public float Constitution
    {
        get
        {
            if(modifier == null) return baseConstitution;
            else return modifier.GetConsitution(baseConstitution);
        }
    }
 
}

public interface IStatModifier
{
 
    float GetStrength(float baseStrength);
    float GetSpeed(float baseSpeed);
    float GetConsitution(float baseConstitution);
 
}

public class RabbitModifier : IStatModifier
{
 
    public float strengthModifier = 0.1f;
    public float speedModifier = 2f;
    public float constitutionModifier = 100f;
 
    public float GetStrength(float baseStrength)
    {
        //rabbits scale strength
        return baseStrength * strengthModifier;
    }
 
    public float GetSpeed(float baseSpeed)
    {
        //speed is constant for a rabbit... doesn't change no matter what
        return speedModifier;
    }
 
    public float GetConsitution(float baseConstitution)
    {
        //constitution is reduced to a minimum of 10
        return Mathf.Max(baseConstitution - constitutionModifier, 10f);
    }
 
}

So this way each species can modify however it wants. Customize the stat in some manner. I put in arbitrary customizations here as an example.

Then whenever you change species, you just swap out the modifier.

1 Like

Thank you so much for the excellent, clear response, and for taking the time to illustrate it with a perfect example - it was exactly what I needed. I now know that for one, I’ve been doing interfaces completely wrong, so this has been a fantastic eureka moment for me.

Oh, and I noticed your footer;
“If you expect to program, make sure you know how to concisely describe your problem. If you can’t describe it here on the forums, how do you expect to describe it to your computer?”

Ahem, sorry about that then, and thanks again! :smile:

Nah man, you described your problem just fine.

You just didn’t know how to convey your issue to the computer.

You’ve got step one of learning to program covered. Now it’s learning to describe your problem using only the tools available to you in a specific programming language.