Reset health to 100% when game starts but keep health in between scenes

I’m creating this 2D platformer game in Unity. In the game, there is a place where you switch scenes to go to another part of the game called the ‘Quiet Zone’. At the start of the game, I want the health to reset to 100% but use PlayerPrefs to keep the health in between scenes.

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;
using UnityEngine.SceneManagement;

public class CharacterController : MonoBehaviour
{
    public float speed;
    public float jumpForce;
    public int health = 10;
    public float delayDamage;
    public int currentHealth;

    public LayerMask groundLayer;
    public LayerMask enemyLayer;
    public LayerMask questionLayer;
    public Rigidbody2D rb;
    public BoxCollider2D boxCollider;
    public TextMeshProUGUI healthText;
    public GameObject deathScreen;
    public GameObject questionScreen;
    public GameObject redScreen;

    public float qZoneStart;
    public bool inQuietZone = false;

    private static bool initDone = false;

    // Start is called before the first frame update
    void Start()
    {
       health = PlayerPrefs.GetInt("health");
    }

    // Update is called once per frame
    void Update()
    {
        if (delayDamage > 0)
        {
            delayDamage -= Time.deltaTime;
        }

        float horizontal = Input.GetAxis("Horizontal");

        transform.Translate(Vector3.right * speed * horizontal * Time.deltaTime);

        if (isOnGround())
        {
            if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W))
            {
                rb.velocity = Vector2.up * jumpForce;
            }              
        }

        if (delayDamage <= 0)
        {
            DamagePlayer();
        }

        healthText.text = "Health: " + (health * 10).ToString() + "%";

        if (health <= 3)
        {
            redScreen.SetActive(true);
        }

        else
        {
            redScreen.SetActive(false);
        }

        if (health <= 0)
        {
            deathScreen.SetActive(true);

            Time.timeScale = 0f;
        }

        qZoneStart = 30;

        if (transform.position.x >= qZoneStart)
        {
            inQuietZone = true;
        }

        else
        {
            inQuietZone = false;
        }

        if (questionCollided())
        {
            questionScreen.SetActive(true);

            Time.timeScale = 0f;
        }

        PlayerPrefs.SetInt("health", health);
    }

    private bool isOnGround()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0f, Vector2.down, 0.1f, groundLayer);

        return raycast.collider != null;
    }

    private bool enemyCollided()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, enemyLayer);

        return raycast.collider != null;
    }

    private void DamagePlayer()
    {
        delayDamage = 1;

        if (enemyCollided())
        {
            health -= 1;
        }
    }

    private bool questionCollided()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, questionLayer);

        if (raycast.collider != null)
        {
            Destroy(raycast.collider.gameObject);
            return true;
        }

        return false;
    }
}

Here, I said in Update() that the PlayerPrefs ‘health’ was equal to health. And in Start() I said to set the health to that PlayerPrefs. That means when I switch scenes, it will keep it. But when I restart the game, it will stay like that. I’ve tried many different ways like using the Awake() function, or OnApplicationQuit() but nothing seems to work. I’m not that good at Unity and C# so please help me. (this might just be a very simple problem and I am just overreacting )

Why don’t you make a player data scriptable object that your scene components store data in at runtime. This component could keep a copy of default values and ‘current’ values, and unlike components this will store the data in a data structure that won’t be reset by scene changes.

is the solution but it is not a good thing to use player pref save at update method. Instead you can use scriptable object to store data or mark your character with Unity - Scripting API: Object.DontDestroyOnLoad to not reset between scenes.

You may add additional info to the player prefs tag; this additional info identifies your current games save number; and a default is loaded on a new game ✓™ and for a new game a new ID extends the string of the tag identifying that game; and you can check the ID to your save file
(ノಠ益ಠ)ノ彡┻━┻ and finally restore the correct value for the correct file.

1 Like

I’m sorry, I’m not that familiar with scriptable objects. Can you explain how I could use it?

How would I set the health to the default at the start, but keep the current health between scenes? Because if I set it to default in the Start(), every time I switch scenes, it will go back to default every time.

Don’t destroy on load

read the page very carefully
It is for “An Object not destroyed on Scene change.”

What you do not want to destroy from the hierarchy on scene change can be any object carrying any data, so it could just be a script that carries the ID of your player prefs extension. Or it can carry the whole data of health itself

1 Like

So I could say DontDestroyOnLoad(PlayerPrefs.GetInt(“health”) but still set the default in the Start() function?

No,

a player pref is not an object.

the player object in each scene is the object you don’t want to destroy. The one with your health script.

What I meant was if you stored the string in its own script on its own object, then you could not destroy that object on load and reference it for your data through your player.

otherwise

You make sure your player is not destroyed on load, and remove any duplicate players from other scene.

1 Like

So I could store the data in another script and dont destroy that and then take the data from there into my player script. And I could still put the default in the Start() function?

Yes, if the start function searched for your don’t destroy script using GameObject Find and if it located the object it can modify the variables on start by getting the component. And here If it’s not located or returns null you can create it the don’t destroy object
乁༼:yin_yang::yin_yang:✿༽ㄏ

1 Like

I’ve put all the health data into a seperate script, do I now DontDestroyOnLoad(healthScript) or healthScript.gameObject?

Steps to success:

  • identify parts of your game that live longer than a scene ← essential first step; without this you are lost

  • identify how long they live (game manager: life of the game … music / menu manager: forever? etc)

  • implement the parts so that they have the appropriate lifecycle

Here’s some points of reference:

ULTRA-simple static solution to a GameManager:

https://discussions.unity.com/t/855136/6

https://gist.github.com/kurtdekker/50faa0d78cd978375b2fe465d55b282b

OR for a more-complex “lives as a MonoBehaviour or ScriptableObject” solution…

Simple Singleton (UnitySingleton):

Some super-simple Singleton examples to take and modify:

Simple Unity3D Singleton (no predefined data):

https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

public void DestroyThyself()
{
   Destroy(gameObject);
   Instance = null;    // because destroy doesn't happen until end of frame
}

There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

And finally there’s always just a simple “static locator” pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

WARNING: this does NOT control their uniqueness.

WARNING: this does NOT control their lifecycle.

public static MyClass Instance { get; private set; }

void OnEnable()
{
  Instance = this;
}
void OnDisable()
{
  Instance = null;     // keep everybody honest when we're not around
}

Anyone can get at it via MyClass.Instance., but only while it exists.

When I try to use DontDestroyOnLoad(), whenever I switch scenes, it still doesn’t appear. Here’s the code for the character

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

public class CharacterController : MonoBehaviour
{
    public float speed;
    public float jumpForce;
    public int health = 10;

    public LayerMask groundLayer;
    public LayerMask questionLayer;
    public Rigidbody2D rb;
    public BoxCollider2D boxCollider;
    public GameObject questionScreen;
    public GameObject redScreen;
    public Health healthScript;

    public float qZoneStart;
    public bool inQuietZone = false;

    // Start is called before the first frame update
    void Start()
    {
        DontDestroyOnLoad(this.gameObject);
        DontDestroyOnLoad(healthScript.gameObject);
    }

    // Update is called once per frame
    void Update()
    {
        float horizontal = Input.GetAxis("Horizontal");

        transform.Translate(Vector3.right * speed * horizontal * Time.deltaTime);

        if (isOnGround())
        {
            if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W))
            {
                rb.velocity = Vector2.up * jumpForce;
            }              
        }

        qZoneStart = 30;

        if (transform.position.x >= qZoneStart)
        {
            inQuietZone = true;
        }

        else
        {
            inQuietZone = false;
        }

        if (questionCollided())
        {
            questionScreen.SetActive(true);

            Time.timeScale = 0f;
        }

        health = healthScript.health;
    }

    private bool isOnGround()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0f, Vector2.down, 0.1f, groundLayer);

        return raycast.collider != null;
    }

    private bool questionCollided()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, questionLayer);

        if (raycast.collider != null)
        {
            Destroy(raycast.collider.gameObject);
            return true;
        }

        return false;
    }
}

And here’s the code for the health object

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using TMPro;

public class Health : MonoBehaviour
{
    public LayerMask enemyLayer;
    public BoxCollider2D boxCollider;
    public CharacterController player;
    public GameObject redScreen;
    public GameObject deathScreen;
    public TextMeshProUGUI healthText;

    public float delayDamage;
    public int health = 10;

    // Start is called before the first frame update
    void Start()
    {
        health = 10;
    }

    // Update is called once per frame
    void Update()
    {
        if (delayDamage > 0)
        {
            delayDamage -= Time.deltaTime;
        }

        if (delayDamage <= 0)
        {
            DamagePlayer();
        }

        if (health <= 3)
        {
            redScreen.SetActive(true);
        }

        else
        {
            redScreen.SetActive(false);
        }

        if (health <= 0)
        {
            deathScreen.SetActive(true);

            Time.timeScale = 0f;
        }

        healthText.text = "Health: " + (health * 10).ToString() + "%";
    }

    private bool enemyCollided()
    {
        RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, enemyLayer);

        return raycast.collider != null;
    }

    private void DamagePlayer()
    {
        delayDamage = 1;

        if (enemyCollided())
        {
            health -= 1;
        }
    }

    public void Incorrect()
    {
        player.questionScreen.gameObject.SetActive(false);

        Time.timeScale = 1f;
    }

    public void Correct()
    {
        if (player.health < 10)
        {
            player.health += 1;
        }

        player.questionScreen.gameObject.SetActive(false);

        Time.timeScale = 1f;
    }
}

Oh! Doesn’t matter, I’ve solved the problem guys. I just used DontDestroyOnLoad() for pretty much all of the objects so it doesn’t lose its data like the camera, so it doesn’t lose which object its following. Thanks guys so much :)) I appreciate it.

1 Like

Sorry I didn’t reply sooner I missed the response glad it’s working now

What exactly did you do? We are facing the same problem now. I cannot save the health bar between levels.

Based on their reply they just… made everything DontDestroyOnLoad.

But rather than necro post, make your own thread describing your problem.