Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. We have updated the language to the Editor Terms based on feedback from our employees and community. Learn more.
    Dismiss Notice

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

Discussion in 'Scripting' started by zeinhatduong, Apr 8, 2023.

  1. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    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.
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    5. using UnityEngine.SceneManagement;
    6.  
    7. public class CharacterController : MonoBehaviour
    8. {
    9.     public float speed;
    10.     public float jumpForce;
    11.     public int health = 10;
    12.     public float delayDamage;
    13.     public int currentHealth;
    14.  
    15.     public LayerMask groundLayer;
    16.     public LayerMask enemyLayer;
    17.     public LayerMask questionLayer;
    18.     public Rigidbody2D rb;
    19.     public BoxCollider2D boxCollider;
    20.     public TextMeshProUGUI healthText;
    21.     public GameObject deathScreen;
    22.     public GameObject questionScreen;
    23.     public GameObject redScreen;
    24.  
    25.     public float qZoneStart;
    26.     public bool inQuietZone = false;
    27.  
    28.     private static bool initDone = false;
    29.  
    30.     // Start is called before the first frame update
    31.     void Start()
    32.     {
    33.        health = PlayerPrefs.GetInt("health");
    34.     }
    35.  
    36.     // Update is called once per frame
    37.     void Update()
    38.     {
    39.         if (delayDamage > 0)
    40.         {
    41.             delayDamage -= Time.deltaTime;
    42.         }
    43.  
    44.         float horizontal = Input.GetAxis("Horizontal");
    45.  
    46.         transform.Translate(Vector3.right * speed * horizontal * Time.deltaTime);
    47.  
    48.         if (isOnGround())
    49.         {
    50.             if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W))
    51.             {
    52.                 rb.velocity = Vector2.up * jumpForce;
    53.             }              
    54.         }
    55.  
    56.         if (delayDamage <= 0)
    57.         {
    58.             DamagePlayer();
    59.         }
    60.  
    61.         healthText.text = "Health: " + (health * 10).ToString() + "%";
    62.  
    63.         if (health <= 3)
    64.         {
    65.             redScreen.SetActive(true);
    66.         }
    67.  
    68.         else
    69.         {
    70.             redScreen.SetActive(false);
    71.         }
    72.  
    73.         if (health <= 0)
    74.         {
    75.             deathScreen.SetActive(true);
    76.  
    77.             Time.timeScale = 0f;
    78.         }
    79.  
    80.         qZoneStart = 30;
    81.  
    82.         if (transform.position.x >= qZoneStart)
    83.         {
    84.             inQuietZone = true;
    85.         }
    86.  
    87.         else
    88.         {
    89.             inQuietZone = false;
    90.         }
    91.  
    92.         if (questionCollided())
    93.         {
    94.             questionScreen.SetActive(true);
    95.  
    96.             Time.timeScale = 0f;
    97.         }
    98.  
    99.         PlayerPrefs.SetInt("health", health);
    100.     }
    101.  
    102.     private bool isOnGround()
    103.     {
    104.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0f, Vector2.down, 0.1f, groundLayer);
    105.  
    106.         return raycast.collider != null;
    107.     }
    108.  
    109.     private bool enemyCollided()
    110.     {
    111.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, enemyLayer);
    112.  
    113.         return raycast.collider != null;
    114.     }
    115.  
    116.     private void DamagePlayer()
    117.     {
    118.         delayDamage = 1;
    119.  
    120.         if (enemyCollided())
    121.         {
    122.             health -= 1;
    123.         }
    124.     }
    125.  
    126.     private bool questionCollided()
    127.     {
    128.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, questionLayer);
    129.  
    130.         if (raycast.collider != null)
    131.         {
    132.             Destroy(raycast.collider.gameObject);
    133.             return true;
    134.         }
    135.  
    136.         return false;
    137.     }
    138. }
    139.  
    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 )
     
  2. TimmyTheTerrible

    TimmyTheTerrible

    Joined:
    Feb 18, 2017
    Posts:
    186
    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.
     
  3. icauroboros

    icauroboros

    Joined:
    Apr 30, 2021
    Posts:
    101
  4. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    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.
     
    zeinhatduong likes this.
  5. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    I’m sorry, I’m not that familiar with scriptable objects. Can you explain how I could use it?
     
  6. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    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.
     
  7. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    https://docs.unity3d.com/ScriptReference/Object.DontDestroyOnLoad.html

    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
     
    zeinhatduong likes this.
  8. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    So I could say DontDestroyOnLoad(PlayerPrefs.GetInt(“health”) but still set the default in the Start() function?
     
  9. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    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.
     
    zeinhatduong likes this.
  10. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    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?
     
  11. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    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
    乁༼☯‿☯✿༽ㄏ
     
    zeinhatduong likes this.
  12. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    I've put all the health data into a seperate script, do I now DontDestroyOnLoad(healthScript) or healthScript.gameObject?
     
  13. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,951
    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://forum.unity.com/threads/i-need-to-save-the-score-when-the-scene-resets.1168766/#post-7488068

    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:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    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.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.
     
  14. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    When I try to use DontDestroyOnLoad(), whenever I switch scenes, it still doesn't appear. Here's the code for the character
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4.  
    5. public class CharacterController : MonoBehaviour
    6. {
    7.     public float speed;
    8.     public float jumpForce;
    9.     public int health = 10;
    10.  
    11.     public LayerMask groundLayer;
    12.     public LayerMask questionLayer;
    13.     public Rigidbody2D rb;
    14.     public BoxCollider2D boxCollider;
    15.     public GameObject questionScreen;
    16.     public GameObject redScreen;
    17.     public Health healthScript;
    18.  
    19.     public float qZoneStart;
    20.     public bool inQuietZone = false;
    21.  
    22.     // Start is called before the first frame update
    23.     void Start()
    24.     {
    25.         DontDestroyOnLoad(this.gameObject);
    26.         DontDestroyOnLoad(healthScript.gameObject);
    27.     }
    28.  
    29.     // Update is called once per frame
    30.     void Update()
    31.     {
    32.         float horizontal = Input.GetAxis("Horizontal");
    33.  
    34.         transform.Translate(Vector3.right * speed * horizontal * Time.deltaTime);
    35.  
    36.         if (isOnGround())
    37.         {
    38.             if (Input.GetKeyDown(KeyCode.Space) || Input.GetKeyDown(KeyCode.UpArrow) || Input.GetKeyDown(KeyCode.W))
    39.             {
    40.                 rb.velocity = Vector2.up * jumpForce;
    41.             }              
    42.         }
    43.  
    44.         qZoneStart = 30;
    45.  
    46.         if (transform.position.x >= qZoneStart)
    47.         {
    48.             inQuietZone = true;
    49.         }
    50.  
    51.         else
    52.         {
    53.             inQuietZone = false;
    54.         }
    55.  
    56.         if (questionCollided())
    57.         {
    58.             questionScreen.SetActive(true);
    59.  
    60.             Time.timeScale = 0f;
    61.         }
    62.  
    63.         health = healthScript.health;
    64.     }
    65.  
    66.     private bool isOnGround()
    67.     {
    68.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, 0f, Vector2.down, 0.1f, groundLayer);
    69.  
    70.         return raycast.collider != null;
    71.     }
    72.  
    73.     private bool questionCollided()
    74.     {
    75.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, questionLayer);
    76.  
    77.         if (raycast.collider != null)
    78.         {
    79.             Destroy(raycast.collider.gameObject);
    80.             return true;
    81.         }
    82.  
    83.         return false;
    84.     }
    85. }
    86.  
    And here's the code for the health object
    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using TMPro;
    5.  
    6. public class Health : MonoBehaviour
    7. {
    8.     public LayerMask enemyLayer;
    9.     public BoxCollider2D boxCollider;
    10.     public CharacterController player;
    11.     public GameObject redScreen;
    12.     public GameObject deathScreen;
    13.     public TextMeshProUGUI healthText;
    14.  
    15.     public float delayDamage;
    16.     public int health = 10;
    17.  
    18.     // Start is called before the first frame update
    19.     void Start()
    20.     {
    21.         health = 10;
    22.     }
    23.  
    24.     // Update is called once per frame
    25.     void Update()
    26.     {
    27.         if (delayDamage > 0)
    28.         {
    29.             delayDamage -= Time.deltaTime;
    30.         }
    31.  
    32.         if (delayDamage <= 0)
    33.         {
    34.             DamagePlayer();
    35.         }
    36.  
    37.         if (health <= 3)
    38.         {
    39.             redScreen.SetActive(true);
    40.         }
    41.  
    42.         else
    43.         {
    44.             redScreen.SetActive(false);
    45.         }
    46.  
    47.         if (health <= 0)
    48.         {
    49.             deathScreen.SetActive(true);
    50.  
    51.             Time.timeScale = 0f;
    52.         }
    53.  
    54.         healthText.text = "Health: " + (health * 10).ToString() + "%";
    55.     }
    56.  
    57.     private bool enemyCollided()
    58.     {
    59.         RaycastHit2D raycast = Physics2D.BoxCast(boxCollider.bounds.center, boxCollider.bounds.size, Random.Range(90f, -90f), Vector2.down, 0.1f, enemyLayer);
    60.  
    61.         return raycast.collider != null;
    62.     }
    63.  
    64.     private void DamagePlayer()
    65.     {
    66.         delayDamage = 1;
    67.  
    68.         if (enemyCollided())
    69.         {
    70.             health -= 1;
    71.         }
    72.     }
    73.  
    74.     public void Incorrect()
    75.     {
    76.         player.questionScreen.gameObject.SetActive(false);
    77.  
    78.         Time.timeScale = 1f;
    79.     }
    80.  
    81.     public void Correct()
    82.     {
    83.         if (player.health < 10)
    84.         {
    85.             player.health += 1;
    86.         }
    87.  
    88.         player.questionScreen.gameObject.SetActive(false);
    89.  
    90.         Time.timeScale = 1f;
    91.     }
    92. }
    93.  
     
  15. zeinhatduong

    zeinhatduong

    Joined:
    Sep 18, 2021
    Posts:
    13
    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.
     
    StarBornMoonBeam likes this.
  16. StarBornMoonBeam

    StarBornMoonBeam

    Joined:
    Mar 26, 2023
    Posts:
    209
    Sorry I didn't reply sooner I missed the response glad it's working now