Another another solution for GetStat()

I already did this as part of the challenge a couple of videos ago.

You can use indexers to get an array element from a class, using this method does still use the nested for each loops but it is hidden.

[System.Serializable]
public SomeClassWithAnArray
{
    [SerializeField] private float[] myArray;
    public this[index] => levels[index]; 
}

For More details see:
Using indexers (C# Programming Guide)

To implement this in the Code from the course will look something like this:

Progression.cs

using UnityEngine;

namespace RPG.Stats
{
    [CreateAssetMenu(fileName = "Progression", menuName = "Stats/New Progression", order = 0)]
    public class Progression : ScriptableObject
    {
        [SerializeField] private ProgressionCharacterClass[] characterClasses;

        public float this[CharacterClass cc, Stat stat, int level] => FindProgressionCharacterClassWithStat(cc, stat, level);

        private float FindProgressionCharacterClassWithStat(CharacterClass cc, Stat stat, int level)
        {
            var ccFound = false;
            var error = "";
            foreach (ProgressionCharacterClass pcc in characterClasses)
            {
                if (pcc.CharacterClass != cc) continue;
                try
                {
                    return pcc[stat, level];
                }
                catch (System.ArgumentOutOfRangeException e)
                {
                    error =
                        $"{(ccFound ? $"Multiple progressions for {cc} found:\n{error}\n" : "")}No {stat} found in {cc}\n{e}";
                }
                    
                ccFound = true;
            }
            
            if (!ccFound)
                throw new System.ArgumentOutOfRangeException(
                    nameof(characterClasses), $"No Progression {cc} found");

            throw new System.ArgumentOutOfRangeException(nameof(characterClasses), error);
        }

        [System.Serializable]
        class ProgressionCharacterClass
        {
            [SerializeField] private CharacterClass characterClass;
            [SerializeField] private ProgressionStat[] stats;
            
            public CharacterClass CharacterClass => characterClass;
            public float this[Stat stat, int level] => FindStat(stat, level);
            
            private float FindStat(Stat stat, int level)
            {
                foreach (ProgressionStat s in stats)
                {
                    if (s.Stat == stat) return s[level];
                }

                throw new System.ArgumentOutOfRangeException(
                    nameof(stat), $"Progression Character Class does not contain a Stat of {stat}");
            }
        }

        [System.Serializable]
        class ProgressionStat
        {
            [SerializeField] private Stat stat;
            [SerializeField] private float[] levels;

           public Stat => Stat;
           public int Length => temps.Length;
           public this[index] => levels[index]; 
        }
    }
}

BaseStats.cs

using UnityEngine;

namespace RPG.Stats
{
    public class BaseStats : MonoBehaviour
    {
        [SerializeField, Range(1, 99)] private int startingLevel = 1;
        [SerializeField] private CharacterClass characterClass;
        [SerializeField] private Progression progression;

        public float GetStatValue(Stat stat)
        {
            try
            {
                return progression[characterClass, stat, int startingLevel - 1];
            }
            catch (System.Exception e)
            {
                Debug.LogWarning(e);
                //TODO: add checks for different stats here to return a different values
                return 0;
            }
        }
    }
}

Any place that a stat is need use

GetComponent<BaseStats>().GetStatValue(Stat.Health)
GetComponent<BaseStats>().GetStatValue(Stat.ExperienceReward)

I am not using Levels for my progression in my code, I am using a Hybrid of the Progression Linear Formula and Progression Curve Formula concept that was introduced My take on the Progression SO

I love custom indexers! This look like a great use for them!

1 Like

Whats great about doing it this way when I got to the Section Performant Lookups With Dictionaries it was easy to use this with the performant dictionary method.

using System.Collections.Generic;
using System.Linq;
using UnityEngine;

namespace RPG.Stats
{
    [CreateAssetMenu(fileName = "Progression", menuName = "Stats/New Progression", order = 0)]
    public class Progression : ScriptableObject
    {
        [SerializeField] private ProgressionCharacterClass[] characterClasses;

        private Dictionary<CharacterClass, ProgressionCharacterClass> _lookupTable;

        public float this[CharacterClass cc, Stat stat, int level] => FindProgressionCharacterClassWithStat(cc, stat, level);

        private float FindProgressionCharacterClassWithStat(CharacterClass cc, Stat stat, int level)
        { 
            try
            {
                BuildLookupTable();
                return _lookupTable[cc][stat, level];
            }
            catch (System.ArgumentOutOfRangeException e)
            {
                throw new System.ArgumentOutOfRangeException(nameof(characterClasses),
                    $"{name} {e}");
            }
            catch (System.Exception e)
            {
                if(!_lookupTable.ContainsKey(cc))
                {
                    throw new System.ArgumentOutOfRangeException(nameof(characterClasses),
                        $"{name} does not contain an entry for characterClass {cc}");
                }

                throw new System.Exception($"{name}: Something went Horribly wrong!\n{e}");
            }
        }

        private void BuildLookupTable()
        {
            if (_lookupTable != null) return;

            _lookupTable = characterClasses.ToDictionary(pcc => pcc.CharacterClass, pcc => pcc);
        }

        [System.Serializable]
        class ProgressionCharacterClass
        {
            [SerializeField] private CharacterClass characterClass;
            [SerializeField] private ProgressionStat[] stats;
            
            private Dictionary<Stat, float[]> _lookupTable;
            
            public CharacterClass CharacterClass => characterClass;
            public float this[Stat stat, int level] => FindStat(stat, level);
            
            private float FindStat(Stat stat, int level)
            {
                foreach (ProgressionStat s in stats)
                {
                    if (s.Stat == stat) return s[level];
                }

                BuildLookupTable();
                if (!_lookupTable.ContainsKey(stat))
                    throw new System.ArgumentOutOfRangeException(nameof(stat),
                        $"Progression does not contain an entry for {stat} in the class {characterClass}");

                if (lookupTable[characterClass][stat].Length < level)
                {
                    Debug.LogWarning($"Progression does not nave a level {level} for {stat} in the class {characterClass}");
                    return lookupTable[characterClass][stat][lookupTable[characterClass][stat].Length-1];
                }

                return _lookupTable[stat][level];
            }

            private void BuildLookupTable()
            {
                if (_lookupTable != null) return;
                _lookupTable = stats.ToDictionary(pStat => pStat.Stat, pStat => pStat.Levels);
            }
        }

        [System.Serializable]
        class ProgressionStat
        {
            [SerializeField] private Stat stat;
            [SerializeField] private float[] levels;

           public Stat => stat;
           public int Length => temps.Length;
           public this[index] => levels[index]; 
           public Levels[] => levels
        }
    }
}

Privacy & Terms