Forum begins after the advertisement:


[Part 8] MaxHealth increases from heart shards not saved

Home Forums Video Game Tutorial Series Creating a Metroidvania in Unity [Part 8] MaxHealth increases from heart shards not saved

Viewing 4 posts - 1 through 4 (of 4 total)
  • Author
    Posts
  • #14503
    Aria
    Former Patron

    This is probably a stupid question but how exactly would i go about saving the health increase from heart shards, but also making heart shards 1 time use.

    #14504
    Joseph Tang
    Moderator

    1. Saving Max Health

    I’ve recently went and done some updates and improvements to the codes for the Metroidvania series, and will be putting up some posts about the bugfixes and improvements.

    So, up to Article Part 9, and soon Part 10, there should be new improved codes with slightly updated instructions and notes.
    There is a new code for SaveData.cs in Part 8 that will take into account the maxhealth of the player and save that data.

    [System.Serializable]
    public struct SaveData
    {
        public static SaveData Instance;
    
        //player stuff
        public int playerHealth;
        public int playerMaxHealth;
        public int playerHeartShards;
        public float playerMana;
        public bool playerHalfMana;
        public Vector2 playerPosition;
        public string lastScene;
    
        public bool playerUnlockedWallJump, playerUnlockedDash, playerUnlockedVarJump;
        public bool playerUnlockedSideCast, playerUnlockedUpCast, playerUnlockedDownCast;
    
        #region Player stuff
        public void SavePlayerData()
        {
            using(BinaryWriter writer = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "/save.player.data")))
            {
                playerHealth = PlayerController.Instance.Health;
                writer.Write(playerHealth);
                playerMaxHealth = PlayerController.Instance.maxHealth;
                writer.Write(playerMaxHealth);
                playerHeartShards = PlayerController.Instance.heartShards;
                writer.Write(playerHeartShards);
    
                playerMana = PlayerController.Instance.Mana;
                writer.Write(playerMana);
                playerHalfMana = PlayerController.Instance.halfMana;
                writer.Write(playerHalfMana);
    
                playerUnlockedWallJump = PlayerController.Instance.unlockedWallJump;
                writer.Write(playerUnlockedWallJump);
                playerUnlockedDash = PlayerController.Instance.unlockedDash;
                writer.Write(playerUnlockedDash);
                playerUnlockedVarJump = PlayerController.Instance.unlockedVarJump;
                writer.Write(playerUnlockedVarJump);
    
                playerUnlockedSideCast = PlayerController.Instance.unlockedSideCast;
                writer.Write(playerUnlockedSideCast);
                playerUnlockedUpCast = PlayerController.Instance.unlockedUpCast;
                writer.Write(playerUnlockedUpCast);
                playerUnlockedDownCast = PlayerController.Instance.unlockedDownCast;
                writer.Write(playerUnlockedDownCast);
    
                playerPosition = PlayerController.Instance.transform.position;
                writer.Write(playerPosition.x);
                writer.Write(playerPosition.y);
    
                lastScene = SceneManager.GetActiveScene().name;
                writer.Write(lastScene);
            }
            Debug.Log("saved player data");
            
    
        }
        public void LoadPlayerData()
        {
            if(File.Exists(Application.persistentDataPath + "/save.player.data"))
            {
                using(BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "/save.player.data")))
                {
                    playerHealth = reader.ReadInt32();
                    playerMaxHealth = reader.ReadInt32();
                    playerHeartShards = reader.ReadInt32();
    
                    playerMana = reader.ReadSingle();
                    playerHalfMana = reader.ReadBoolean();
    
                    playerUnlockedWallJump = reader.ReadBoolean();
                    playerUnlockedDash = reader.ReadBoolean();
                    playerUnlockedVarJump = reader.ReadBoolean();
    
                    playerUnlockedSideCast = reader.ReadBoolean();
                    playerUnlockedUpCast = reader.ReadBoolean();
                    playerUnlockedDownCast = reader.ReadBoolean();
    
                    playerPosition.x = reader.ReadSingle();
                    playerPosition.y = reader.ReadSingle();
    
                    lastScene = reader.ReadString();
    
                    SceneManager.LoadScene(lastScene);
                    PlayerController.Instance.transform.position = playerPosition;
                    PlayerController.Instance.halfMana = playerHalfMana;
                    PlayerController.Instance.Health = playerHealth;
                    PlayerController.Instance.maxHealth = playerMaxHealth;
                    PlayerController.Instance.heartShards = playerHeartShards;
                    PlayerController.Instance.Mana = playerMana;
    
                    PlayerController.Instance.unlockedWallJump = playerUnlockedWallJump;
                    PlayerController.Instance.unlockedDash = playerUnlockedDash;
                    PlayerController.Instance.unlockedVarJump = playerUnlockedVarJump;
    
                    PlayerController.Instance.unlockedSideCast = playerUnlockedSideCast;
                    PlayerController.Instance.unlockedUpCast = playerUnlockedUpCast;
                    PlayerController.Instance.unlockedDownCast = playerUnlockedDownCast;
                }
                Debug.Log("load player data");
                Debug.Log(playerHalfMana);
            }
            else
            {
                Debug.Log("File doesnt exist");
                PlayerController.Instance.halfMana = false;
                PlayerController.Instance.Health = PlayerController.Instance.maxHealth;
                PlayerController.Instance.Mana = 0.5f;
                PlayerController.Instance.heartShards = 0;
    
                PlayerController.Instance.unlockedWallJump = false;
                PlayerController.Instance.unlockedDash = false;
                PlayerController.Instance.unlockedVarJump = false;
    
                PlayerController.Instance.unlockedSideCast = false;
                PlayerController.Instance.unlockedUpCast = false;
                PlayerController.Instance.unlockedDownCast = false;
            }
        }
    
        #endregion
    }

    2. Checking for pickups/Chests being already used.

    As for the saving of the Heart Shards being picked up, that’s a bit more complicated.
    Like what was discussed here:

    [part 4] heart and mana UI problems

    Here’s a code i modified from ChatGPT [Should work in theory but I haven’t yet tested it] that uses the idea in the above statement:


    New Script: PickupManager.cs

    using System.Collections.Generic;
    using UnityEngine;
    
    public class PickupManager : MonoBehaviour
    {
        public static PickupManager Instance;
    
        private void Awake()
        {
            if (Instance != null && Instance != this)
            {
                Destroy(gameObject);
            }
            else
            {
                Instance = this;
            }
            
            DontDestroyOnLoad(gameObject);
        }
    
        void Start()
        {
            LoadAndDeactivateInteractedObjects(); // Load saved data and deactivate pickups/chests
        }
    
        void LoadAndDeactivateInteractedObjects()
        {
            HashSet collectedPickups = SaveData.Instance.collectedPickups;
            HashSet openedChests = SaveData.Instance.openedChests;
    
            // Deactivate pickups that have been collected
            foreach (GameObject pickup in GameObject.FindGameObjectsWithTag("Pickup"))
            {
                string pickupID = pickup.name; // Assuming the name of the object is its ID
                if (collectedPickups.Contains(pickupID))
                {
                    pickup.SetActive(false); // Deactivate the pickup
                }
            }
    
            // Deactivate chests that have been opened
            foreach (GameObject chest in GameObject.FindGameObjectsWithTag("Chest"))
            {
                string chestID = chest.name; // Assuming the name of the object is its ID
                if (openedChests.Contains(chestID))
                {
                    chest.SetActive(false); // Deactivate the chest
                }
            }
        }
    }
    

    SaveData.cs

    [System.Serializable]
    public struct SaveData
    {
        public static SaveData Instance;
    
        // Other data...
    
        // Pickups and Chests data
        public HashSet collectedPickups; // HashSet to store collected pickups
        public HashSet openedChests; // HashSet to store opened chests
    
        public void Initialize()
        {
            // Initialize other data...
    
            if (collectedPickups == null)
            {
                collectedPickups = new HashSet();
            }
            if (openedChests == null)
            {
                openedChests = new HashSet();
            }
        }
    
        public void AddCollectedPickup(string pickupID)
        {
            // Add pickup ID to collected pickups
            collectedPickups.Add(pickupID);
            SavePickupsAndChests();
        }
    
        public void AddOpenedChest(string chestID)
        {
            // Add chest ID to opened chests
            openedChests.Add(chestID);
            SavePickupsAndChests();
        }
    
        public void SavePickupsAndChests()
        {
            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "/save.pickups_chests.data")))
            {
                // Save collected pickups
                writer.Write(collectedPickups.Count);
                foreach (string pickupID in collectedPickups)
                {
                    writer.Write(pickupID);
                }
    
                // Save opened chests
                writer.Write(openedChests.Count);
                foreach (string chestID in openedChests)
                {
                    writer.Write(chestID);
                }
            }
        }
    
        public void LoadPickupsAndChests()
        {
            if (File.Exists(Application.persistentDataPath + "/save.pickups_chests.data"))
            {
                using (BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "/save.pickups_chests.data")))
                {
                    // Load collected pickups
                    int numCollectedPickups = reader.ReadInt32();
                    for (int i = 0; i < numCollectedPickups; i++)
                    {
                        collectedPickups.Add(reader.ReadString());
                    }
    
                    // Load opened chests
                    int numOpenedChests = reader.ReadInt32();
                    for (int i = 0; i < numOpenedChests; i++)
                    {
                        openedChests.Add(reader.ReadString());
                    }
                }
            }
        }
    }

    GameManager.cs

    public class GameManager : MonoBehaviour
    {
        // Method to trigger pickup interaction event
        public void TriggerPickupInteract(string pickupID)
        {
            SaveData.Instance.AddCollectedPickup(pickupID); // Add pickup to SaveData
        }
    
        // Method to trigger chest interaction event
        public void TriggerChestInteract(string chestID)
        {
            SaveData.Instance.AddOpenedChest(chestID); // Add chest to SaveData
        }
    }

    PlayerController.cs

    public class PlayerController : MonoBehaviour
    
        public IEnumerator WalkIntoNewScene(Vector2 _exitDir, float _delay)
        {
            PickupManager.Instance.LoadAndDeactivateInteractedObjects();
            pstate.invincible = true;
    
            //If exit direction is upwards
            if(_exitDir.y > 0)
            {
                rb.velocity = jumpForce * _exitDir;
            }
    
            //If exit direction requires horizontal movement
            if(_exitDir.x != 0)
            {
                xAxis = _exitDir.x > 0 ? 1 : -1;
                Move();
            }
    
            Flip();
            yield return new WaitForSeconds(_delay);
            pstate.invincible = false;
            pState.cutscene = false;
        }

    To summarize,
    1) SaveData.cs will have new code to keep a Hashset of Chest names and pickup names, as well as add them.

    2) A new singleton script, PickupManager.cs, will then take the Hashset of names and compare them to any and all Gameobjects that have been tagged appropriately with “Chest” or “Pickup”. If the hashset of names matches the names of the tagged gameobjects, that gameobject will be set to false.

    3) GameManager.cs will host a method that will call the method in SaveData.cs to save names of the pickups in the Hashset.

    4) PlayerController.cs will call the PickupManager’s LoadAndDeactivateInteractedObjects() on entering a new scene to set up all the pickups in the scene.


    Preparation:
    1) Tags: Create a “Pickup” and “Chest” tag and set them on the related gameobjects.

    2) A way to call GameManager’s TriggerChestInteract() or TriggerPickupInteract(). This will primarily be from scripts, you can add a line into, let’s say, orbShard or heartShard scripts that when picked up, they call the GameManager and give the gameobject’s name. For instance: GameManager.Instance.TriggerPickupInteract(gameObject.name).

    3) Create a null object and set the pickupmanager script onto it.


    This is all theory and could possibly work, if it doesn’t, it possibly only needs some tweaking to the code a bit and fix up some errors. I’ve cleaned up the code as much as i can for the theory of it.

    #14525
    Aria
    Former Patron

    I had a look with the save data, but i have an issue where the amount of hearts being instantiated when i load the game being only 1 for some reason
    here is my save data script

    using System.Collections;
    using System.Collections.Generic;
    using UnityEngine;
    using System.IO;
    using UnityEngine.SceneManagement;
    
    [System.Serializable]
    public struct SaveData
    {
        public static SaveData Instance;
        public HashSet<string> sceneNames;
        public string typewriterSceneName;
        public Vector2 typewriterPos;
        public int playerHealth;
        public Vector2 playerPos;
        public string lastScene;
        public string lastFrame;
        public bool playerUnlockedDash;
        public bool playerUnlockedDoubleJump;
        public int playerHeartShards;
        public int playerMaxHealth;
        
    
        public void Initialized()
        {
            if(!File.Exists(Application.persistentDataPath + "saving.typewriter.data"))
            {
                BinaryWriter writer = new BinaryWriter(File.Create(Application.persistentDataPath + "saving.typewriter.data"));
            }
            if (!File.Exists(Application.persistentDataPath + "saving.player.data")) //if file doesnt exist, well create the file
            {
                BinaryWriter writer = new BinaryWriter(File.Create(Application.persistentDataPath + "saving.player.data"));
            }
    
            if (sceneNames == null)
            {
                sceneNames = new HashSet<string>(); 
    
            }
        }
       public void SaveTypewriter()
        {
            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "saving.typewriter.data")))
            {
                writer.Write(typewriterSceneName);
                writer.Write(typewriterPos.x);
                writer.Write(typewriterPos.y);
            }
            
        }
        public void LoadTypewriter()
        {
            if(File.Exists(Application.persistentDataPath + "saving.typewriter.data"))
            {
                using (BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "saving.typewriter.data")))
                {
                    typewriterSceneName = reader.ReadString();
                    typewriterPos.x = reader.ReadSingle();  
                    typewriterPos.y = reader.ReadSingle();
                }
            }
            else
            {
                Debug.Log("Typewriter does not exist");
            }
           
        }
        public void SavePlayerData()
        {
    
            using (BinaryWriter writer = new BinaryWriter(File.OpenWrite(Application.persistentDataPath + "save.user.data")))
            {
                playerHealth = PlayerMovement.Instance.Health;
                writer.Write(playerHealth);
                playerHeartShards = PlayerMovement.Instance.heartShards;
                writer.Write(playerHeartShards);
                playerMaxHealth = PlayerMovement.Instance.maxHealth;
                writer.Write(playerMaxHealth);
                playerUnlockedDash = PlayerMovement.Instance.unlockedDash;
                writer.Write(playerUnlockedDash);
                playerUnlockedDoubleJump = PlayerMovement.Instance.unlockedDoubleJump;
                writer.Write(playerUnlockedDoubleJump);
                
    
                playerPos = PlayerMovement.Instance.transform.position;
                writer.Write(playerPos.x);
                writer.Write(playerPos.y);
    
                lastScene = SceneManager.GetActiveScene().name;
                writer.Write(lastScene);
            }
            Debug.Log("saved player data");
            
    
        }
        public void LoadPlayerData()
        {
            if (File.Exists(Application.persistentDataPath + "save.user.data"))
            {
                string savePath = Application.persistentDataPath + "save.user.data";
                if (File.Exists(savePath) && new FileInfo(savePath).Length > 0)
                {
    
                    using (BinaryReader reader = new BinaryReader(File.OpenRead(Application.persistentDataPath + "save.user.data")))
                    {
                        playerHealth = reader.ReadInt32();
                        playerMaxHealth = reader.ReadInt32();
                        playerHeartShards = reader.ReadInt32();
    
                        playerUnlockedDash = reader.ReadBoolean();
                        playerUnlockedDoubleJump = reader.ReadBoolean();
                        
                        playerPos.x = reader.ReadSingle();
                        playerPos.y = reader.ReadSingle();
                        lastScene = reader.ReadString();
    
                        SceneManager.LoadScene(lastScene);
                        PlayerMovement.Instance.transform.position = playerPos;
                        PlayerMovement.Instance.Health = playerHealth;
                        PlayerMovement.Instance.maxHealth = playerMaxHealth;
                        PlayerMovement.Instance.unlockedDash = playerUnlockedDash;
                        PlayerMovement.Instance.unlockedDoubleJump = playerUnlockedDoubleJump;
                        PlayerMovement.Instance.heartShards = playerHeartShards;
                    }
                    Debug.Log("load player data");
                    
    
                }
    
            }
            else
            {
                Debug.Log("File doesnt exist");
                
                PlayerMovement.Instance.health = PlayerMovement.Instance.maxHealth;
                PlayerMovement.Instance.unlockedDash = false;
                PlayerMovement.Instance.unlockedDoubleJump = false;
                PlayerMovement.Instance.heartShards = 0;
                PlayerMovement.Instance.maxHealth = 5;
    
            }
    
        }
    
    }
    #14528
    Joseph Tang
    Moderator

    Oh I believe I also added a part for that, which i forgot to send here.

    I think what’s happening is your HeartController.cs is taking your Player’s health values before its updated with the SaveData.Instance.LoadPlayerData();. [Although I can’t be certain since I’m not sure if you set your MaxHealth to be 1. I reccomend testing to see if your Health is really at 1 or will be updated after getting hit, but regardless I think you should do this.]

    You can try calling onHealthChangedCallback.Invoke(); in your PlayerController.cs after the SaveData.Instance.LoadPlayerData(); code, to update the HeartController.cs.

        // Start is called before the first frame update
        void Start()
        {
            pState = GetComponent();
    
            rb = GetComponent();
            sr = GetComponent();
    
            anim = GetComponent();
    
            SaveData.Instance.LoadPlayerData();
            if (halfMana)
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.HalfMana);
            }
            else
            {
                UIManager.Instance.SwitchMana(UIManager.ManaState.FullMana);
            }
    
            onHealthChangedCallback.Invoke();
    
            gravity = rb.gravityScale;
    
            if (Health == 0)
            {
                pState.alive = false;
                GameManager.Instance.RespawnPlayer();
            }
    
            Mana = mana;
            manaStorage.fillAmount = Mana;
    
            Health = maxHealth;
        }

    If this does not solve the issue, I will probably need a video of you entering the game, getting hit, resetting the game or healing, to understand what exactly the issue looks like.

Viewing 4 posts - 1 through 4 (of 4 total)
  • You must be logged in to reply to this topic.

Go to Login Page →


Advertisement below: