Why not using EditorGUI.CurveField for Level?

What if, instead of using a bunch of arrays and set all levels by hand which is a pretty mess (think about having 30 different NPCs and set all HP and stats by hand) using an Inspector Graph?
image

I do exactly that! Specifically, I use an AnimationCurve for each attribute. It’s a good way to manage these things, though you have to watch out because sometimes the curves will get away from you very quickly.

Ah yes, I’ve dig a bit about it, the Idea is to set in the Progression SO the maximum level, then when you will add each character the SO will simply use the AnimationCurve to define the value for each level based on the graph. It would be easy and fast to setup each character (NPC in particluar).

But without messing to much with the code I’ve just added a value to define the maxLevel of the game, then I’ve added a For loop to fill the ProgressionStat levels array.
So I did something like this:

public class Progression : ScriptableObject
    {
        public int maxLevel = 100;
        public int minValue = 8;
        public int maxValue = 100;

        [SerializeField] ProgressionCharacterClass[] characterClasses = null;
        Dictionary<CharacterClass, Dictionary<Stat, float[]>> lookupTable = null;

        /// <summary>
        /// Return the value of a Stat from specific CharacterClass given a Level
        /// </summary>
        /// <returns></returns>
        public float GetStat(Stat stat, CharacterClass characterClass, int level)
        {
            BuildLookup();
            float[] levels = lookupTable[characterClass][stat];
            
            if (levels.Length < level) return 0;

            return levels[level - 1];
        }

        public int GetLevels(Stat stat, CharacterClass characterClass)
        {
            BuildLookup();

            float[] levels = lookupTable[characterClass][stat];
            return levels.Length;
        }

        private void BuildLookup()
        {
            if (lookupTable != null) return;
            lookupTable = new Dictionary<CharacterClass, Dictionary<Stat, float[]>>();
            foreach (ProgressionCharacterClass progressionClass in characterClasses)
            {
                var statLookupTable = new Dictionary<Stat, float[]>();
                foreach (ProgressionStat progressionStat in progressionClass.stats)
                {
                    float[] levels = new float[maxLevel];
                    for (int level = 0; level < maxLevel - 1; level++)
                    {
                        if (progressionStat.curve.length == 0)
                        {
                            Debug.LogWarning("MISSING CURVE: " + progressionClass.characterClass + " didn't have any curve in " + progressionStat.stat);
                            return;
                        }

                        if(progressionStat.curve.keys[0].value < minValue)
                        {
                            progressionStat.curve.keys[0].value = minValue;
                            Debug.LogWarning(progressionClass.characterClass + "'s " + progressionStat.stat + " could have some wrong value, minValue is too low!");
                        }

                        if (progressionStat.curve.keys[progressionStat.curve.keys.Length -1].value < minValue)
                        {
                            progressionStat.curve.keys[progressionStat.curve.keys.Length - 1].value = maxValue;
                            Debug.LogWarning(progressionClass.characterClass + "'s " + progressionStat.stat + " could have some wrong value, maxValue is too low!");
                        }

                        Debug.Log(progressionClass.characterClass + "'s " + progressionStat.stat + ": "+ progressionStat.curve.Evaluate(level));

                        levels[level] = progressionStat.curve.Evaluate(level);
                    }

                    statLookupTable[progressionStat.stat] = levels;
                }

                lookupTable[progressionClass.characterClass] = statLookupTable;
            }
        }

        [System.Serializable]
        class ProgressionCharacterClass
        {
            public CharacterClass characterClass;
            public ProgressionStat[] stats;
        }
        [System.Serializable]
        class ProgressionStat
        {
            public Stat stat;
            public AnimationCurve curve;
            //public float[] levels;
        }
    }

But at the moment the Player level-up really fast, too much indeed.
If you have better idea please let me know :slight_smile:

To be honest, I left the Turducken behind long ago.

[System.Serializable]
public class StatFormula
{
    [SerializeField] Stat stat;
    [SerializeField]  AnimationCurve curve;

    public float Evaluate(int level)
    {
        return curve.Evaluate(level);
     }
}

public class ScriptableClass: ScriptableObject
{
     [SerializeField] StatFormula[] formulas;
     Dictionary<Stat, StatFormula> statDictionary;

      void BuildLookup()
      {
            if(statDictionary!=null) return;
            statDictionary = new Dictionary(Stat, StatFormula);
            foreach(StatFormula formula in formulas)
            {
                  statDictionary[formula.stat] = formula;
             }
      }
   

     public float GetStat(Stat stat, int level)
     {
          BuildLookup();
          return statDictionary.ContainsKey(stat)?statDictionary[stat].Evaluate(level):1;
      }
}

So the BaseStats gets a ScriptableClass that represents only one class, the one belonging to the character it’s attached to. No worrying about class/stat/level, you just need stat and level at this point. The rest remains the same.

Oh, good to know, can I ask how did you manage the curve datas?
I mean, if I set the curve from (for example health) from 4 to 100… the value pops out with tons of decimans… like at level 2 the player would have Health: 4,008808😬
I was thinking about using some math formula but I’m not so good in Math :sweat_smile:

You meant Heatlh 4.008808, right?
For values like health and damage, I Mathf.Floor(stats.GetStat(stat)) to bring it down to the nearest whole value. I do this AFTER all the IModifierProvider stuff is added, to make sure that any percentage bonuses include the fractional amount.

1 Like

I dig a bit tonight and I have found interesting alternatives to have smooth increasing curve, like from this source Formulas:XP To Level | WoWWiki | Fandom :grin:

1 Like