Skills based system

It’s also a method that would go in SkillStore. It’s just a convenience method… so that you could, if you wanted, in the UI show "{skillStore.GetSkillsNeededForNextCombatLevel()} skill increases until next Combat Level" or something like that.

In Integer division, / gives you the whole number value of the division, and % gives you the remainder…

For example, suppose your character has 7 skills. If the required skills per level is 5, then the level itself is now 1 (perhaps that should be displayed as GetCombatLevel()+1 in your UI), and the remainder is 2 (7 % 5 = 2). By subtracting this number from the number of skills needed, you get 3, so level “1” with 3 skills remaining until level “2”.

The same way we were doing it before… In BaseStats
Store the current Combat level.
When a skill level increases, check the Combat Level and compare it to the stored Combat level
If the level has increased, store the new level and fire off the event in BaseStats. All you’re changing is how you calculate the level (by checking SkillStore for the GetCombatLevel())

And that’s your challenge… If only we had some way of looking up attack values based on a level…

That’s actually a very helpful feature to have. Hopefully it motivates the players to keep grinding xD

Growing up learning coding, it took me a few years to properly understand what “remainder” meant, until it hit me that I learned it at 17 when studying algebraic expression divisions (when you divide complex algebraic equations with little to no numbers by other weird equations)… Ahh maths was wild back then

I’ll probably go through this multiple times before I fully grasp it, but ok (how does 7-5=3 though? I swear I’m not playing dumb, I’m genuinely confused)

Let me take an insanely wild guess… a “Progression” asset?

Figuring this one out will trouble me a bit, but I’ll give it a try I suppose… any hints for this one specifically?

7%5 = 2, how many skills we have towards the next level = 2
5-2 = 3, how many skills we need to reach the next level = 3

Insane, isn’t it?

When you got the time, can you please give me hints about this one? I’m trying to copy ‘CalculateLevel’ in ‘BaseStats.cs’, but I’m a little confused both there and in trying to code it in ‘SkillStore.cs’ (how do I get the level of the combat when it rises, and compare it with the current one…?)

Implementing it does sound insane indeed… :sweat_smile: (I’ll try figure it out regardless)

I feel like I’m writing this skill system…
This is BaseStats.CalculateLevel from the course:

        private void UpdateLevel() 
        {
            int newLevel = CalculateLevel();
            if (newLevel > currentLevel.value)
            {
                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();
            }
        }

Only one line needs to be changed in BaseStats to set the level to the CombatLevel, while comparing it and announcing effects…
Rather than calling CalculateLevel(), one might consider calling a method on the skillStore that returns a CombatLevel…

In terms of the Progression…
You already have a progression for Attack, and a progression for Defense.
If you wanted to calculate the damage for an attack, calling baseStats.GetStat(Stat.Attack), skillStore(GetSkillLevel(skill)) would take the skill in question and use that as an index into the Attack stat. The same principle would work for Defense, GetStat(Stat.Defense), skillStore(GetSkillLevel(Skill.Defense))

private void UpdateLevel()
        {
        
            int newLevel = CalculateLevel();

            if (newLevel > currentLevel.value) {

                currentLevel.value = GetComponent<SkillStore>().GetCombatLevel() + 1;
                LevelUpEffect();
                onLevelUp();    // Action call (i.e: no function), with Health.RegenerateHealth() as a subscriber
                                // (this ensures that we regenerate partially (or all of) our health when we level up, by placing it in the place where it occurs when we level up!)

            }
        
        }

Let me guess… something like this? (This wasn’t properly working though)

CalculateLevel() looks to the Experience component…
Let’s go through the logic of the original method a bit closer…

private void UpdateLevel() 
        {
            int newLevel = CalculateLevel(); //Get the new level based on the experience component
            if (newLevel > currentLevel.value) //Compare  to the current level
            {
                currentLevel.value = newLevel; //Set the current level to the previously calculated new level
                LevelUpEffect();
                onLevelUp();
            }
        }

So… it doesn’t make a lot of sense to use CalculateLevel() in that first line, because we’re no longer interested in Experience… What could we set newLevel to that might represent the Combat level?

I’m having a hard time tonight… anyway, let me try again. Is it this one? (Again, it didn’t work):

private void UpdateLevel()
        {
        
            int newLevel = CalculateLevel();

            if (newLevel > GetComponent<SkillStore>().GetCombatLevel()) {

                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();    // Action call (i.e: no function), with Health.RegenerateHealth() as a subscriber
                                // (this ensures that we regenerate partially (or all of) our health when we level up, by placing it in the place where it occurs when we level up!)

            }
        
        }
        private void UpdateLevel() 
        {
            int newLevel = GetComponent<SkillStore>().GetCombatLevel();
            if (newLevel > currentLevel.value) //Compare  to the current level
            {
                currentLevel.value = newLevel; //Set the current level to the previously calculated new level
                LevelUpEffect();
                onLevelUp();
            }
        }

The whole point is to get the newLevel from the authoritative source, SkillStore. After that, with the new level in hand, the rest of the method is identical to the original.

Something is still off… the level up effects aren’t working, and for CombatLevel to work, the divisor of ‘GetCombatLevel()’ is divided by 4 rather than 5… How do we get around getting the player to legitimately start from level 1, but require 5 skills to level up? :sweat_smile:

Edit 1: OK so to try and combat the fact that this isn’t working, I tried doing this, in ‘BaseStats.cs’:

private void OnEnable() {

            // if (experience != null)
            if (skillExperience != null)
            {

                // experience.onExperienceGained += UpdateLevel;   // Subscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated += UpdateLevel;

            }

        }

        private void OnDisable() {

            // if (experience != null)
            if (skillExperience != null)
            {

                // experience.onExperienceGained -= UpdateLevel;   // Unsubscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated -= UpdateLevel;

            }

        }

no Syntax errors, but I still don’t get the effects of the particle system and the health regeneration. Any other solutions?

I’m not at all sure why this would not be happening, if you’re calling LevelUpEffect() and OnLevelUp() in UpdateLevel when the level increases. I would look to see who is or isn’t signed up to the OnLevelUp event…

Just to make sure, you do understand that these will only fire when the Combatlevel increases, not each individual skill level, right?

Yes I am certain of that… but I don’t think this ‘UpdateLevel()’ function is being called in any sort of Update() methods in Unity anymore either… anything else to check?

It should be being called when an event fires… such as the one you subscribed to… Are you caching skillExperience in Awake() or in Start()?

Paste in your BaseStats.cs

Yes I am, in ‘Awake()’

Voila:

using UnityEngine;
using System;
using GameDevTV.Utils;
using RPG.Skills;

namespace RPG.Stats

{
    public class BaseStats : MonoBehaviour, IPredicateEvaluator
    {
        
        [Range(1, 99)]
        [SerializeField] int startingLevel;
        [SerializeField] CharacterClass characterClass;
        [SerializeField] Progression progression = null;
        [SerializeField] GameObject levelUpParticleEffect = null;
        [SerializeField] bool shouldUseModifiers = false;   // to disable enemies from using damage boosters (modifiers) - can be useful if you want bosses to boost themselves for example...

        LazyValue<int> currentLevel;
        // Experience experience;
        SkillExperience skillExperience;

        public event Action onLevelUp;  // event, to be subscribed to in 'health.cs' (so we can regenerate partially (or all of) our health when we level up)

        private void Awake() {

            // experience = GetComponent<Experience>();
            skillExperience = GetComponent<SkillExperience>();
            currentLevel = new LazyValue<int>(CalculateLevel);

        }

        public void Start() {

            currentLevel.ForceInit();

        }

        private void OnEnable() {

            // if (experience != null)
            if (skillExperience != null)
            {

                // experience.onExperienceGained += UpdateLevel;   // Subscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated += UpdateLevel;

            }

        }

        private void OnDisable() {

            // if (experience != null)
            if (skillExperience != null)
            {

                // experience.onExperienceGained -= UpdateLevel;   // Unsubscribing to a delegate (event), just like we did in 'CinematicControlRemover.cs'
                skillExperience.skillExperienceUpdated -= UpdateLevel;

            }

        }

        private void UpdateLevel()
        {
        
            // int newLevel = CalculateLevel();
            int newLevel = GetComponent<SkillStore>().GetCombatLevel();

            if (newLevel > currentLevel.value) {

                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();    // Action call (i.e: no function), with Health.RegenerateHealth() as a subscriber
                                // (this ensures that we regenerate partially (or all of) our health when we level up, by placing it in the place where it occurs when we level up!)

            }
        
        }

        public void LevelUpEffect() {

            Instantiate(levelUpParticleEffect, transform);

        }

        public float GetStat(Stat stat)
        {
        
            // The weapon damage is added via 'GetAdditiveModifiers'
            return  (GetBaseStat(stat) + GetAdditiveModifiers(stat)) * (1 + GetPercentageModifiers(stat)/100);            
        
        }
        
        private float GetBaseStat(Stat stat) {

            return progression.GetStat(stat, characterClass, GetLevel());

        }

        public int GetLevel() {

            return currentLevel.value;

        }

        private int CalculateLevel()
        {
        
            Experience experience = GetComponent<Experience>();
        
            if (experience == null) return startingLevel;

            float currentXP = experience.GetPoints();
            int penultimateLevel = progression.GetLevels(Stat.ExperienceToLevelUp, characterClass);
        
            for (int level = 1; level <= penultimateLevel; level++)
            {
        
                float XPToLevelUp = progression.GetStat(Stat.ExperienceToLevelUp, characterClass, level);
                if (XPToLevelUp > currentXP)
                {
        
                    return level;
        
                }
        
            }

            return penultimateLevel + 1;
        
        }

        private float GetAdditiveModifiers(Stat stat) {

            // avoids enemies from using modifiers
            if (!shouldUseModifiers) return 0;

            float total = 0;

            foreach (IModifierProvider provider in GetComponents<IModifierProvider>()) {

                foreach (float modifier in provider.GetAdditiveModifiers(stat)) {

                    total += modifier;

                }

            }

            return total;

        }

        private float GetPercentageModifiers(Stat stat)
        {

            // avoids enemies from using modifiers
            if (!shouldUseModifiers) return 0;

            float total = 0;

            foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
            {

                foreach (float modifier in provider.GetPercentageModifiers(stat))
                {

                    total += modifier;

                }

            }

            return total;

        }

        // Enum-based Evaluate of 'IPredicate' function:
        public bool? Evaluate(EPredicate predicate, string[] parameters) {

            // This function checks if we have a specific combat level, so we can execute specific action based on that

            if (predicate == EPredicate.HasLevel) {

                // TryParse() TRIES to convert a value from a string to an integer:
                if (int.TryParse(parameters[0], out int testLevel)) {

                    // if you successfully converted the string to an integer, and the player level is higher than the required level, then
                    // the player level > required 'HasLevel' returns a true boolean:
                    return currentLevel.value >= testLevel;

                }

            }

            return null;

        }

    }

}

Put in a Debug in UpdateLevel()

int newLevel = GetComponent<SkillStore>().GetCombatLevel());
Debug.Log($"Current Level = {currentLevel.value}, new level = {newLevel}");
if(newLevel > currentLevel.value)
{
    Debug.Log($"Level up!");
    //rest of method

so right now my game combat level begins at zero, because of how we set this thing up (the average value is 0.8, but we floored it to 0… I’m guessing that’s what happens):

public int GetCombatLevel() {

            int combatLevel = (GetSkillLevel(Skill.Attack) + GetSkillLevel(Skill.Defence) + GetSkillLevel(Skill.Ranged) + GetSkillLevel(Skill.Magic)) / 5;
            return combatLevel;

        }

since we are rounding an integer to whatever was below it, rather than above it (my wild guess)… but what’s NOT natural, is why the newLevel is zero, and the currentLevel is 1 (the math is mething… :stuck_out_tongue_winking_eye: ):

Current Level = 1, new level = 0
UnityEngine.Debug:Log (object)
RPG.Stats.BaseStats:UpdateLevel () (at Assets/Project Backup/Scripts/Stats/BaseStats.cs:70)
RPG.Skills.SkillExperience:GainExperience (RPG.Skills.Skill,int) (at Assets/Project Backup/Scripts/Stats/Skills/SkillExperience.cs:68)
RPG.Attributes.Health:AwardExperience (UnityEngine.GameObject,RPG.Skills.Skill) (at Assets/Project Backup/Scripts/Attributes/Health.cs:239)
RPG.Attributes.Health:TakeDamage (UnityEngine.GameObject,single,RPG.Skills.Skill) (at Assets/Project Backup/Scripts/Attributes/Health.cs:112)
RPG.Combat.Projectile:OnTriggerEnter (UnityEngine.Collider) (at Assets/Project Backup/Scripts/Combat/Projectile.cs:166)

You could add 1 to the level calculation…

int newLevel = GetComponent<SkillStore>().GetCombatLevel() + 1;

OK so the way this updates now is that ‘currentLevel’ and ‘newLevel’ BOTH increment when the Combat Level rises up, so the event never fires because newLevel never exceeds currentLevel.value, so… how do we fix that?

This is my current ‘UpdateLevel()’:

private void UpdateLevel()
        {
        
            // int newLevel = CalculateLevel();
            int newLevel = GetComponent<SkillStore>().GetCombatLevel() + 1;
            Debug.Log($"Current Level = {currentLevel.value}, new level = {newLevel}");
            if (newLevel > currentLevel.value) {

                currentLevel.value = newLevel;
                LevelUpEffect();
                onLevelUp();    // Action call (i.e: no function), with Health.RegenerateHealth() as a subscriber
                                // (this ensures that we regenerate partially (or all of) our health when we level up, by placing it in the place where it occurs when we level up!)

            }
        
        }

Are you setting currentLevel somewhere else??

You may be misinterpreting the data… if the currentLevel starts at 1, then until newLevel gets to 2, the if clause will be false.

as far as the compiler search function is telling me, ‘currentLevel’ is a local variable just for ‘BaseStats.cs’

As much as I’d love to agree, they both reached level 2 and were equal

Privacy & Terms