Stats not loading/saving correctly

Hi everyone, I’ve been going through the RPG course and whilst in the ‘Character Stats’ section, my saving/loading functionality started to forget the exp and levels that my character gained. I’m not sure exactly when this started happening as it was working at the start, but there were some lectures that were making small tweaks to the code halfway through and when I had a thorough test of the progress halfway through the section, this is when I noticed the problem.

I actually refrained from posting the problem earlier as the last time I came across a bug, I was informed that the issue I encountered at that time was solved in a later section. I’ve now reached the end of the course and for the most part everything else is working fine for me to continue to the next courses, except this saving/loading problem which is a big game-breaker.

I’ve enclosed a couple of pictures that outlines what happens when playing, but to briefly describe it anyway: My character will gain exp and level as normal, and I can save as per normal, but as soon as I load the game or if I teleport to another scene, the level and exp gets reset. I remain with the current health points, but my max health gets reset.

I’m using Unity version 2020.3.10f. I’m not sure if this may have something to do with the issue or not, but in this version, the layout of Scriptable objects was slightly different then what was shown in the video lectures, and it wasn’t possible for me to write character stats in Visual Code script, meaning I had to manually input them in Inspector. It may not have any baring on the problem I’m experiencing, but I feel I needed to mention it, incase this disconnect is in fact an issue with this version of Unity?


This is how the layout of the Scriptable Objects appear in this version of Unity.

I’ve also enclosed my SavingWrapper.cs script below just incase this may be where the issue lays. Any help would be really appreciated, as I feel I can’t move onto one of the sister RPG courses with my project until this is working (unless this is actually an issue that is addressed in one of the other courses?)

SavingWrapper.cs

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using RPG.Saving;

namespace RPG.SceneManagement
{
public class SavingWrapper : MonoBehaviour
{
const string defaultSaveFile = “save”;
[SerializeField] float fadeInTime = 0.2f;

    void Awake()
    {
        StartCoroutine(LoadLastScene());
    }

    private IEnumerator LoadLastScene()
    {
        //Debug.Log("SavingWrapper LoadLastScene() executed");
        yield return GetComponent<SavingSystem>().LoadLastScene(defaultSaveFile);

        Fader fader = FindObjectOfType<Fader>();
        fader.FadeOutImmediate();
        
        yield return fader.FadeIn(fadeInTime);
    }

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.L))
        {
            Load();
        }
        if (Input.GetKeyDown(KeyCode.S))
        {
            Save();
        }
        if (Input.GetKeyDown(KeyCode.Delete))
        {
            Delete();
        }
    }

    public void Save()
    {
        GetComponent<SavingSystem>().Save(defaultSaveFile);
    }

    public void Load()
    {
        GetComponent<SavingSystem>().Load(defaultSaveFile);
    }

    public void Delete()
    {
        GetComponent<SavingSystem>().Delete(defaultSaveFile);
    }
}

}

1 Like

Hi sfChris, where is your level stored and which script restores it?

1 Like

Actually, I think the problem lies either in the Experience.cs script or the BaseStats.cs script. Can you post these and we’ll take a look?

1 Like

Thank you for replying Riley_Waldo and Brian_Trotter.

The levels are stored using the saving asset pack that Sam provides in the saving section of the course, which if I remember correctly, we don’t modify. The levels save to the project folder, and SavingWrapper.cs contains the key-presses that save and load the level, calling upon the SavingSystem script that Sam provides. The functionality for loading seems to work as it will remember if the main character has killed enemies, which room it last passed into, and current health it has. It just seems to forget if the character has levelled up and max health, current experience points.

I have provided the two scripts for my Experience.cs and BaseStats.cs below.

Experience.cs
using UnityEngine;
using RPG.Saving;
using System;

namespace RPG.Stats
{
    public class Experience : MonoBehaviour, ISaveable
    {
        [SerializeField] float experiencePoints = 0;

        public event Action onExperienceGained;

        public void GainExperience(float experience)
        {
            experiencePoints += experience;
            onExperienceGained();
        }

        public float GetPoints()
        {
            return experiencePoints;
        }

        public object CaptureState()
        {
            return experiencePoints;
        }

        public void RestoreState(object state)
        {
            experiencePoints = (float)state;
        }
    }
}
BaseStats.cs
using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using GameDevTV.Utils;

namespace RPG.Stats
{
    public class BaseStats : MonoBehaviour
    {
        [Range(1, 99)]
        [SerializeField] int startingLevel = 1;
        [SerializeField] CharacterClass characterClass;
        [SerializeField] Progression progression = null;
        [SerializeField] GameObject levelUpParticleEffect = null;
        [SerializeField] bool shouldUseModifiers = false;

        public event Action onLevelUp;

        LazyValue<int> currentLevel;

        Experience experience;

        private void Awake()
        {
            experience = GetComponent<Experience>();
            currentLevel = new LazyValue<int>(CalculateLevel);
        }

        void Start()
        {
            currentLevel.ForceInit();
        }

        private void OnEnable()
        {
            if (experience != null)
            {
                experience.onExperienceGained += UpdateLevel;
            }
        }

        private void OnDisable()
        {
            if (experience != null)
            {
                experience.onExperienceGained -= UpdateLevel;
            }
        }

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

        void LevelUpEffect()
        {
            Instantiate(levelUpParticleEffect, transform);
        }

        public float GetStat(Stat stat)
        {
            return (GetBaseStat(stat) + GetAdditiveModifier(stat)) * (1 + GetPercentageModifier(stat) / 100);
        }

        float GetBaseStat(Stat stat)
        {
            return progression.GetStat(stat, characterClass, GetLevel());
        }

        public int GetLevel()
        {
            return currentLevel.value;
        }

        float GetAdditiveModifier(Stat stat)
        {
            if (!shouldUseModifiers) return 0;

            float total = 0;
            foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
            {
                foreach (float modifier in provider.GetAdditiveModifiers(stat))
                {
                    total += modifier;
                }
            }
            return total;
        }

        float GetPercentageModifier(Stat stat)
        {
            if (!shouldUseModifiers) return 0;

            float total = 0;
            foreach (IModifierProvider provider in GetComponents<IModifierProvider>())
            {
                foreach (float modifier in provider.GetPercentageModifiers(stat))
                {
                    total += modifier;
                }
            }
            return total;
        }

        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;
        }
    }
}

I see what’s going on.
Try adding the following line to the end of Experience.RestoreState()

     onExperienceGained?.Invoke();

Hi Brian, I added the line of code, but there was no change when I tested.

It’s possible that something is causing the LazyValue to get evaluated during the Awake() method or a RestoreState that is being run before the Experience. Let’s add one more line, this time in BaseStats.OnEnable()

private void OnEnable()
{
    if (experience!=null)
    {
          experience.onExperienceGained+=UpdateLevel;
          UpdateLevel();
    }
}

There still isn’t any change with the added line. I added a Debug.Log to check that it reaches the new code, and it does, but sadly there isn’t any change to the results.

That seems very odd. I think I’ll need to look at it directly to see what might be going on.

Zip up your project and upload it to https://gdev.tv/projectupload (please remove the Library folder from the zip) and I’ll take a look at it. Reference this post in the form.

Hmm, when I click the link, it just brings me to the gamedev.tv homepage. I’ve got the zip ready, but I’ve never uploaded on here before, so I’m not that familiar with the process.

I believe that’s because I typed out the link on my phone, and it decided to dutifly split project and upload even though I was typing a URL.
Try the link now

1 Like

After adding debugs almost everywhere, I eventually discovered that Player experience was capturing twice! Once for the correct amount, then again for 0… A quick glance at the Player component revealed the cause:
image
Remove the extra Experience component and you should be good to go.

1 Like

Oh wow… I have no idea how it ended up with a second experience component, but thanks for finding that out for me. I had been going around in circles trying to figure out what the issue was. The experience stats work as expected now. Thank you soo much for finding out that mistake!

This topic was automatically closed 24 hours after the last reply. New replies are no longer allowed.

Privacy & Terms