Saving System Isue

Hello friends.
I have a little problem whit saving the objects that are spown or activate/disactivate aftter the game starts.
I will explain better in the video i attach.
Basically some objects are trigger to become active and spown by dialogs,NPC,box collider…etc. The game work perfectly but aftter i save the game , the player location,helth,inventory are save and works but the objects that are inactive and are trigger to become active along the game after saving and loading they are unactive.What is the solution to save those objects to have the state was at the saving point.
I have to save +store the objects are active in that moment when the game are saved and load .
The video i will put its whit Json system saving, but the same problem its with .sav system.
All objects have the necessary components to be saved regularly
If anyone has managed to solve or has any suggestions I am grateful
PS: escuse me for my bad english
Thank you !

Items that are activated/deactivated at runtime will need some sort of manager outside of the object (usually a container holding the items)…
For example, you could make the NPC and Enemies GameObject that have SaveableEntities under them a SaveableEntity in it’s own right…

Then add a component to those top level GameObjects to manage the enabled/disabled state.
The tricky part is that those inactive items are still not going to show up in a Capture/RestoreState, so the most logical thing to do is to modify the SaveableEntity component from them and let the top level manage it completely.

First, create a new SaveableEntity class which will be a copy of the SaveableEntity class, but with a new name. Here’s how:

  • Duplicate the SaveableEntity script and rename it to ManagedSaveableEntity.cs
  • Edit ManageSaveableEntity and change the class name to ManagedSaveableEntity
  • Replace the SaveableEntity components on the entities under your Enemies GO

Put a regular SaveableEntity component on the Enemies GO
Add this component to the Enemies GO

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

public class SaveableEntiityManager : MonoBehaviour, ISaveable
{
    private List<ManagedSaveableEntity> manageableEntities = new List<ManagedSaveableEntity>();

    private void Awake()
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            if (transform.GetChild(i).TryGetComponent(out ManagedSaveableEntity entity))
            {
                manageableEntities.Add(entity);
            }
        }
    }

    [System.Serializable]
    public struct ManagedEntity
    {
        public object state;
        public bool isActive;
    }
    
    public object CaptureState()
    {
        Dictionary<string, ManagedEntity> entities = new Dictionary<string, ManagedEntity>();
        foreach (ManagedSaveableEntity saveableEntity in manageableEntities)
        {
            ManagedEntity managedEntity = new ManagedEntity();
            managedEntity.state = saveableEntity.CaptureState();
            managedEntity.isActive = saveableEntity.gameObject.activeSelf;
            entities[saveableEntity.GetUniqueIdentifier()] = managedEntity;
        }

        return entities;
    }

    public void RestoreState(object state)
    {
        if (state is Dictionary<string, ManagedEntity> entities)
        {
            foreach (ManagedSaveableEntity saveableEntity in manageableEntities)
            {
                if (entities.ContainsKey(saveableEntity.GetUniqueIdentifier()))
                {
                    saveableEntity.RestoreState(entities[saveableEntity.GetUniqueIdentifier()].state);
                    saveableEntity.gameObject.SetActive(entities[saveableEntity.GetUniqueIdentifier()].isActive);
                }
            }
        }
    }
}
1 Like

Brian you are my HERO !!!
It works perfectly. I can’t thank you enough.
Keep up the good work.
Thank you very much.

I’m glad I could help

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

Hello again
Affter a serias off testing i found a Bug/problem.
If i start the game and whit out activate the Entites under Enemy GO and try to save the game i have this error :

NullReferenceException: Object reference not set to an instance of an object
RPG.Attributes.Health.CaptureState () (at Assets/Scripts/Attributes/Health.cs:137)
GameDevTV.Saving.ManagedSaveableEntity.CaptureState () (at Assets/Scripts/Saving/ManagedSaveableEntity.cs:45)
SaveableEntiityManager.CaptureState () (at Assets/Scripts/Saving/SaveableEntiityManager.cs:33)
GameDevTV.Saving.SaveableEntity.CaptureState () (at Assets/Scripts/Saving/SaveableEntity.cs:45)
GameDevTV.Saving.SavingSystem.CaptureState (System.Collections.Generic.Dictionary`2[TKey,TValue] state) (at Assets/Scripts/Saving/SavingSystem.cs:106)
GameDevTV.Saving.SavingSystem.Save (System.String saveFile) (at Assets/Scripts/Saving/SavingSystem.cs:43)
RPG.SceneManagement.SavingWrapper.Save () (at Assets/Scripts/SceneManagement/SavingWrapper.cs:100)
RPG.UI.PauseMenu.SaveAndQuit () (at Assets/Scripts/UI/PauseMenu.cs:40)
UnityEngine.Events.InvokableCall.Invoke () (at <13e6546058e340ada820e34dce3b245e>:0)
UnityEngine.Events.UnityEvent.Invoke () (at <13e6546058e340ada820e34dce3b245e>:0)
UnityEngine.UI.Button.Press () (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:70)
UnityEngine.UI.Button.OnPointerClick (UnityEngine.EventSystems.PointerEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/UI/Core/Button.cs:114)
UnityEngine.EventSystems.ExecuteEvents.Execute (UnityEngine.EventSystems.IPointerClickHandler handler, UnityEngine.EventSystems.BaseEventData eventData) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:57)
UnityEngine.EventSystems.ExecuteEvents.Execute[T] (UnityEngine.GameObject target, UnityEngine.EventSystems.BaseEventData eventData, UnityEngine.EventSystems.ExecuteEvents+EventFunction`1[T1] functor) (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/ExecuteEvents.cs:272)
UnityEngine.EventSystems.EventSystem:Update() (at Library/PackageCache/com.unity.ugui@1.0.0/Runtime/EventSystem/EventSystem.cs:501)

After a series off reserch (i’m a at starting level of learning) i understand … the system try to save the Health from the enttites and they are not active in hierachy and give the null exception.
If i give a default value and save the game, all the enemys have the same value , the value from base.stats its ignore , but the saving works fine.
I try to skip the saving helth from the enemys when they are not active hierachy but i recive an error when the system try to Restore the value.
This is my Health.cs

using System;
using GameDevTV.Utils;
using RPG.Core;
using GameDevTV.Saving;
using RPG.Stats;
using UnityEngine;
using UnityEngine.Events;
using System.Collections;

namespace RPG.Attributes
{
    public class Health : MonoBehaviour, ISaveable
    {
        [SerializeField] float regenerationPercentage = 70;
        [SerializeField] TakeDamageEvent takeDamage;
      
        public UnityEvent onDie;

        [System.Serializable]
        public class TakeDamageEvent : UnityEvent<float>
        {
        }
        

        LazyValue<float> healthPoints;

        bool wasDeadLastFrame = false;

        private void Awake() {
            healthPoints = new LazyValue<float>(GetInitialHealth);
        }

        private float GetInitialHealth()
        {
            return GetComponent<BaseStats>().GetStat(Stat.Health);
        }

        private void Start()
        {
            healthPoints.ForceInit();
           
        }

        private void OnEnable() {
            GetComponent<BaseStats>().onLevelUp += RegenerateHealth;
        }

        private void OnDisable() {
            GetComponent<BaseStats>().onLevelUp -= RegenerateHealth;
        }

        public bool IsDead()
        {
            return healthPoints.value <= 0;

        }

        public void TakeDamage(GameObject instigator, float damage)
        {
            healthPoints.value = Mathf.Max(healthPoints.value - damage, 0);
            DestroyEnemy destroy = FindObjectOfType<DestroyEnemy>();
            
            if(IsDead())
            {
                onDie.Invoke();
               
                AwardExperience(instigator);
            } 
            else
            {
                takeDamage.Invoke(damage);
            }
            UpdateState();
        }

        

        public void Heal(float healthToRestore)
        {
            healthPoints.value = Mathf.Min(healthPoints.value + healthToRestore, GetMaxHealthPoints());
            UpdateState();
        }

        public float GetHealthPoints()
        {
            return healthPoints.value;
        }

        public float GetMaxHealthPoints()
        {
            return GetComponent<BaseStats>().GetStat(Stat.Health);
        }

        public float GetPercentage()
        {
            return 100 * GetFraction();
        }

        public float GetFraction()
        {
            return healthPoints.value / GetComponent<BaseStats>().GetStat(Stat.Health);
        }

        private void UpdateState()
        {
            Animator animator = GetComponent<Animator>();

            if (!wasDeadLastFrame && IsDead())
            {
                animator.SetTrigger("die");
                GetComponent<ActionScheduler>().CancelCurrentAction();
            }
            if (wasDeadLastFrame && !IsDead())
            {
                animator.Rebind();
            }

                wasDeadLastFrame = IsDead();
        }

        private void AwardExperience(GameObject instigator)
        {
            Experience experience = instigator.GetComponent<Experience>();
            if (experience == null) return;

            experience.GainExperience(GetComponent<BaseStats>().GetStat(Stat.ExperienceReward));
        }

        private void RegenerateHealth()
        {
            float regenHealthPoints = GetComponent<BaseStats>().GetStat(Stat.Health) * (regenerationPercentage / 100);
            healthPoints.value = Mathf.Max(healthPoints.value, regenHealthPoints);
        }

        public object CaptureState()
        {
            return healthPoints.value;
        }

        public void RestoreState(object state)
        {
            healthPoints.value = (float) state;
            UpdateState();
        }
    }
}

Any suggestion’s ?

I believe this is because the objects are inactive to begin with, so the initialization (Awake, Start, OnEnable) is never un on them.

This complicates things a bit, and we’re going to have to come up with a mechanism where the GameObjects get initialized, but are off once the scene begins… This is uncharted territory, so we’re going to have some trial and error to get things right.

Let’s make a few modifications to SaveableEntityManager

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

public class SaveableEntiityManager : MonoBehaviour, ISaveable
{
    //private List<ManagedSaveableEntity> manageableEntities = new List<ManagedSaveableEntity>();
   //Change this to a Dictionary so we can save the initial state of the entities
   private Dictionary<ManagedSaveableEntity, bool> manageableEntities = new Dictionary<ManagedSaveableEntity, bool>();
    private bool stateWasRestored=false;

    private void Awake()
    {
        for (int i = 0; i < transform.childCount; i++)
        {
            if (transform.GetChild(i).TryGetComponent(out ManagedSaveableEntity entity))
            {
                manageableEntities[entity] = entity.gameObject.activeSelf;
                entity.gameObject.SetActive(true);  //Set active to ensure Awake() runs!
            }
        }
    }

    void OnEnable() 
    {
          if(stateWasRestored) return;
          foreach(KeyValuePair<ManagedEntity, bool> pair in manageableEntities)
          {
                pair.key.gameObject.SetActive(pair.value);
          }
          stateWasRestored=true;
    }

    [System.Serializable]
    public struct ManagedEntity
    {
        public object state;
        public bool isActive;
    }
    
    public object CaptureState()
    {
        Dictionary<string, ManagedEntity> entities = new Dictionary<string, ManagedEntity>();
        foreach (KeyValuePair<ManagedSaveableEntity, bool> pair in manageableEntities)
        {
            ManagedEntity managedEntity = new ManagedEntity();
            managedEntity.state = pair.Key.CaptureState();
            managedEntity.isActive = pair.Key.gameObject.activeSelf;
            entities[pair.Key.GetUniqueIdentifier()] = managedEntity;
        }

        return entities;
    }

    public void RestoreState(object state)
    {
        if (state is Dictionary<string, ManagedEntity> entities)
        {
            stateWasRestored=true;
            foreach (KeyValuePair<ManagedSaveableEntity, bool> pair in manageableEntities)
            {
                if (entities.ContainsKey(pair.Key.GetUniqueIdentifier()))
                {
                    pair.Key.RestoreState(entities[pair.Key.GetUniqueIdentifier()].state);
                    pair.Key.gameObject.SetActive(entities[saveableEntity.GetUniqueIdentifier()].isActive);
                }
            }
        }
    }
}

So what this will do is save a Dictionary of all fo the ManagedSaveableEntities under the GameObject. This Dictionary’s entries will be true if the object is active to start, and false if it is inactive. Then the ManagedSaveableEntity’s GameObject is activated, allowing Awake() to run. It is imperative that initialization code happens in Awake(), not in Start() or this still will not work. In other words, LazyValues must be created with their init codes assigned, references to external components must be assigned, etc, all in Awake()

In the very next frame, in OnEnable, we’re going to go back through that list and deactivate any objects where inactive at the start of the game UNLESS RestoreState has been run, in which case the objects will have already been assigned their proper on/off state.

Before checking this topic off as closed, be sure to test this in your system and let me know how it turned out.

Hi Brian,
I have right from begining an error compilation OnEnable()






7
6
I look up on documentation on unity docs :
https://docs.unity3d.com/Packages/com.unity.collections@0.15/api/Unity.Collections.LowLevel.Unsafe.KeyValue-2.html
I didn’t find the solution to make it work
I use unity 2021.2.16f that may be the reason ?

Ignore the post above,I made a few minor code changes and i’ts look like this :

public class SaveableEntiityManager : MonoBehaviour, ISaveable
    {
        //private List<ManagedSaveableEntity> manageableEntities = new List<ManagedSaveableEntity>();
        //Change this to a Dictionary so we can save the initial state of the entities
        private Dictionary<ManagedSaveableEntity, bool> manageableEntities = new Dictionary<ManagedSaveableEntity, bool>();
        private bool stateWasRestored = false;

        private void Awake()
        {
            for (int i = 0; i < transform.childCount; i++)
            {
                if (transform.GetChild(i).TryGetComponent(out ManagedSaveableEntity entity))
                {
                    manageableEntities[entity] = entity.gameObject.activeSelf;
                    entity.gameObject.SetActive(true);  //Set active to ensure Awake() runs!
                }
            }
        }

        void OnEnable()
        {
            if (stateWasRestored) return;
            foreach (KeyValuePair<ManagedSaveableEntity, bool> pair in manageableEntities)
            {
                pair.Key.gameObject.SetActive(pair.Value);
            }
            stateWasRestored = true;
        }

        [System.Serializable]
        public struct ManagedEntity
        {
            public object state;
            public bool isActive;
        }

        public object CaptureState()
        {
            Dictionary<string, ManagedEntity> entities = new Dictionary<string, ManagedEntity>();
            foreach (KeyValuePair<ManagedSaveableEntity, bool> pair in manageableEntities)
            {
                ManagedEntity managedEntity = new ManagedEntity();
                managedEntity.state = pair.Key.CaptureState();
                managedEntity.isActive = pair.Key.gameObject.activeSelf;
                entities[pair.Key.GetUniqueIdentifier()] = managedEntity;
            }

            return entities;
        }

        public void RestoreState(object state)
        {
            if (state is Dictionary<string, ManagedEntity> entities)
            {
                stateWasRestored = true;
                foreach (KeyValuePair<ManagedSaveableEntity, bool> pair in manageableEntities)
                {
                    if (entities.ContainsKey(pair.Key.GetUniqueIdentifier()))
                    {
                        pair.Key.RestoreState(entities[pair.Key.GetUniqueIdentifier()].state);
                        pair.Key.gameObject.SetActive(entities[pair.Key.GetUniqueIdentifier()].isActive);
                    }
                }
            }
        }
    }

It seems to work properly, save and restore properly.
I have other errors from other scripts introduced by me, I will take care of fixing them.
The tests I have done so far are positive and the results. Please leave the active post for the next 24 hours in case I discover other problems related to the rescue system. Thank you very much for your help and promptness

The public in the ManagedEntity struct was in my code… generally speaking in structs, life is just easier if they’re public.

The first error, the saveableEntity.GetUniqueIdentifier was definitely my bad, though… I was copying and replacing from the original code and I forgot to change that entry to pair.Key.GetUniqueIdentifier(). Sorry about that.

The topic will stay open until you mark a Solution. Other Moderators, please do not close this topic until mihai has had a chance to test this custom solution and report back

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

Privacy & Terms