Search Unity

Lives are resetting when I do not want them to on scene change

Discussion in 'Scripting' started by danmct1995, Aug 19, 2020.

  1. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    So I have an idea of why my lives are being reset to the default of three each time I complete a level in my game and the scene changes, but I am unsure how else to set up my manager so that it initially starts at three on the very first level.

    Code (CSharp):
    1.         private void Awake()
    2.         {
    3.             life = 3;
    4.             heart1.gameObject.SetActive(true);
    5.             heart2.gameObject.SetActive(true);
    6.             heart3.gameObject.SetActive(true);
    7.             heart4.gameObject.SetActive(false);
    8.             heart5.gameObject.SetActive(false);
    9. }
    I'm setting my awake method to take in these variables and I am insuring this manager is never destroyed upon entering a new scene if another exists. I think what is happening is my code is causing the current lives to be overwritten at the start of the next scene with this awake method. How would I go about only allowing this awake method to be called once or something else similar? thanks!
     
  2. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    You need to store data that you want to persist between scenes either in static variables or by calling DontDestroyOnLoad with something you've added to the scene and storing a reference to that in a static variable to easily access it and check whether it exists already (and if not then Instantiate or generate it). If you need more detailed info than that (like example code) I'll post again.
     
    Joe-Censored and danmct1995 like this.
  3. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    I'm a bit confused on how to get there still, I'll attach my full script. I also have another attached to prevent it from being destroyed upon loading the next scene. Could you explain how I would do that in my code ?

    Code (CSharp):
    1. using System.Collections;
    2. using System.Collections.Generic;
    3. using UnityEngine;
    4. using UnityEngine.SceneManagement;
    5. using LevelManagement;
    6.  
    7. namespace SampleGame
    8. {
    9.     public class GameManager : MonoBehaviour
    10.     {
    11.         #region LIVES
    12.         // number of lives & images
    13.         public GameObject heart1, heart2, heart3, heart4, heart5;
    14.         public static int life;
    15.  
    16.         // are we alive?
    17.         private bool dead;
    18.         #endregion
    19.         // reference to player
    20.         private KeyboardControls _player;
    21.  
    22.         // reference to player
    23.          private Level _level;
    24.  
    25.         private bool _isGameOver;
    26.         public bool IsGameOver { get { return _isGameOver; } }
    27.  
    28.         private static GameManager _instance;
    29.         public static GameManager Instance { get { return _instance; } }
    30.  
    31.        [SerializeField] private TransitionFader _endTransitionPrefab;
    32.  
    33.         // initialize references
    34.         private void Awake()
    35.         {
    36.             life = 3;
    37.             heart1.gameObject.SetActive(true);
    38.             heart2.gameObject.SetActive(true);
    39.             heart3.gameObject.SetActive(true);
    40.             heart4.gameObject.SetActive(false);
    41.             heart5.gameObject.SetActive(false);
    42.             if (_instance != null)
    43.             {
    44.                 Destroy(gameObject);
    45.             }
    46.             else
    47.             {
    48.                 _instance = this;
    49.             }
    50.  
    51.             _player = FindObjectOfType<KeyboardControls>();
    52.             _level = FindObjectOfType<Level>();
    53.         }
    54.  
    55.         private void OnDestroy()
    56.         {
    57.             if (_instance == this)
    58.             {
    59.                 _instance = null;
    60.             }
    61.         }
    62.  
    63.         // end the level
    64.        public void EndLevel()
    65.         {
    66.             if (_player != null)
    67.             {
    68.                 // disable the player controls
    69.                 KeyboardControls keyboardControls =
    70.                     _player.GetComponent<KeyboardControls>();
    71.  
    72.                 if (keyboardControls != null)
    73.                 {
    74.                     keyboardControls.enabled = false;
    75.                 }
    76.  
    77.                 // remove any existing motion on the player
    78.                 Rigidbody rbody = _player.GetComponent<Rigidbody>();
    79.                 if (rbody != null)
    80.                 {
    81.                     rbody.velocity = Vector3.zero;
    82.                 }
    83.             }
    84.  
    85.             // check if we have set IsGameOver to true, only run this logic once
    86.             if (!_isGameOver)
    87.             {
    88.                 _isGameOver = true;
    89.                 StartCoroutine(WinRoutine());
    90.             }
    91.         }
    92.        
    93.         private IEnumerator WinRoutine()
    94.         {
    95.             TransitionFader.PlayTransition(_endTransitionPrefab);
    96.             float fadeDelay = (_endTransitionPrefab != null) ? _endTransitionPrefab.Delay + _endTransitionPrefab.FadeOnDuration : 0f;
    97.             yield return new WaitForSeconds(fadeDelay);
    98.             WinScreen.Open();
    99.         }
    100.  
    101.         // check for the end game condition on each frame
    102.         private void Update()
    103.         {
    104.             {
    105.                 ParametersForDeath();
    106.                 if (dead == true)
    107.                 {
    108.                     FindObjectOfType<SceneLoader>().LoadStartScene();
    109.                 }
    110.             }
    111.                 if (life > 5)
    112.             life = 5;
    113.  
    114.         switch (life)
    115.         {
    116.  
    117.             case 5:
    118.                 heart1.gameObject.SetActive(true);
    119.                 heart2.gameObject.SetActive(true);
    120.                 heart3.gameObject.SetActive(true);
    121.                 heart4.gameObject.SetActive(true);
    122.                 heart5.gameObject.SetActive(true);
    123.                 break;
    124.             case 4:
    125.                 heart1.gameObject.SetActive(true);
    126.                 heart2.gameObject.SetActive(true);
    127.                 heart3.gameObject.SetActive(true);
    128.                 heart4.gameObject.SetActive(true);
    129.                 heart5.gameObject.SetActive(false);
    130.                 break;
    131.             case 3:
    132.                 heart1.gameObject.SetActive(true);
    133.                 heart2.gameObject.SetActive(true);
    134.                 heart3.gameObject.SetActive(true);
    135.                 heart4.gameObject.SetActive(false);
    136.                 heart5.gameObject.SetActive(false);
    137.                 break;
    138.             case 2:
    139.                 heart1.gameObject.SetActive(true);
    140.                 heart2.gameObject.SetActive(true);
    141.                 heart3.gameObject.SetActive(false);
    142.                 heart4.gameObject.SetActive(false);
    143.                 heart5.gameObject.SetActive(false);
    144.                 break;
    145.             case 1:
    146.                 heart1.gameObject.SetActive(true);
    147.                 heart2.gameObject.SetActive(false);
    148.                 heart3.gameObject.SetActive(false);
    149.                 heart4.gameObject.SetActive(false);
    150.                 heart5.gameObject.SetActive(false);
    151.                 break;
    152.             case 0:
    153.                 heart1.gameObject.SetActive(false);
    154.                 heart2.gameObject.SetActive(false);
    155.                 heart3.gameObject.SetActive(false);
    156.                 heart4.gameObject.SetActive(false);
    157.                 heart5.gameObject.SetActive(false);
    158.                 Time.timeScale = 0;
    159.                 break;
    160.         }
    161. }
    162. public void SubtractLife(){ life--; }
    163. public void ParametersForDeath()
    164. {
    165.     if (life <= 0) { dead = true; }
    166.     else { dead = false; }
    167. }
    168. public void AddLife()
    169. {
    170.     life++;
    171. }
    172.     }
    173. }
     
  4. seejayjames

    seejayjames

    Joined:
    Jan 28, 2013
    Posts:
    691
    I recommend using a static variable in a static class. You don't need DontDestroyOnLoad in that case, and the static class (a script) just hangs out, no need to attach to any GameObject. This static class holds any variables that you want to persist across scenes, as well as any variables you want *one and only one* copy of.

    One note: static classes don't run Awake(), Start(), or Update(). All functions in a static class have to be explicitly called by other scripts (afaik). But you can simply set the variables to the initial values you want, no need for Awake().
     
  5. adamgolden

    adamgolden

    Joined:
    Jun 17, 2019
    Posts:
    1,555
    First change this:
    Code (CSharp):
    1. public GameObject heart1, heart2, heart3, heart4, heart5;
    To this:
    Code (CSharp):
    1. public GameObject[] hearts;
    ..and then add each of your hearts to "Hearts" in the Inspector, in order of 1 to 5.

    Remove the line
    life = 3;
    from your Awake.

    Change
    public static int life;

    To:
    public static int life = 3;


    Add this function:
    Code (CSharp):
    1. public void UpdateHearts()
    2. {
    3.   for (int h = 0; h < hearts.Length; h++)
    4.   {
    5.     hearts[h].gameObject.SetActive(life > h);
    6.   }
    7. }
    In your Awake, replace this:
    Code (CSharp):
    1.  
    2. heart1.gameObject.SetActive(true);
    3. heart2.gameObject.SetActive(true);
    4. heart3.gameObject.SetActive(true);
    5. heart4.gameObject.SetActive(false);
    6. heart5.gameObject.SetActive(false);
    With this:
    Code (CSharp):
    1. UpdateHearts();
    Then in Update replace this:
    Code (CSharp):
    1.  
    2. switch (life)
    3.       {
    4.         case 5:
    5.           heart1.gameObject.SetActive(true);
    6.           heart2.gameObject.SetActive(true);
    7.           heart3.gameObject.SetActive(true);
    8.           heart4.gameObject.SetActive(true);
    9.           heart5.gameObject.SetActive(true);
    10.           break;
    11.         case 4:
    12.           heart1.gameObject.SetActive(true);
    13.           heart2.gameObject.SetActive(true);
    14.           heart3.gameObject.SetActive(true);
    15.           heart4.gameObject.SetActive(true);
    16.           heart5.gameObject.SetActive(false);
    17.           break;
    18.         case 3:
    19.           heart1.gameObject.SetActive(true);
    20.           heart2.gameObject.SetActive(true);
    21.           heart3.gameObject.SetActive(true);
    22.           heart4.gameObject.SetActive(false);
    23.           heart5.gameObject.SetActive(false);
    24.           break;
    25.         case 2:
    26.           heart1.gameObject.SetActive(true);
    27.           heart2.gameObject.SetActive(true);
    28.           heart3.gameObject.SetActive(false);
    29.           heart4.gameObject.SetActive(false);
    30.           heart5.gameObject.SetActive(false);
    31.           break;
    32.         case 1:
    33.           heart1.gameObject.SetActive(true);
    34.           heart2.gameObject.SetActive(false);
    35.           heart3.gameObject.SetActive(false);
    36.           heart4.gameObject.SetActive(false);
    37.           heart5.gameObject.SetActive(false);
    38.           break;
    39.         case 0:
    40.           heart1.gameObject.SetActive(false);
    41.           heart2.gameObject.SetActive(false);
    42.           heart3.gameObject.SetActive(false);
    43.           heart4.gameObject.SetActive(false);
    44.           heart5.gameObject.SetActive(false);
    45.           Time.timeScale = 0;
    46.           break;
    47.       }
    48.  
    With this:
    Code (CSharp):
    1. UpdateHearts();
    2. if (life == 0) Time.timeScale = 0;
    Ideally you should also add something like a lifePrev variable and only do the above 2 lines if the number of lives has changed since the previous Update.
     
  6. danmct1995

    danmct1995

    Joined:
    Apr 21, 2020
    Posts:
    70
    You are a life saver! Clearest instructions I've even gotten on a forum, ty so much!!!!
     
    adamgolden likes this.
  7. Xepherys

    Xepherys

    Joined:
    Sep 9, 2012
    Posts:
    204
    I agree - and while it's easy to come across a million articles online about why "Singletons are teh Debil", manager classes are an excellent situation where singletons are perfect. I use them in manager classes for production code anytime one is needed (Loggers, GameManagers, DatabaseManagers, etc.). This is the template I've built and use: https://gist.github.com/xepherys/34d3d5ce3f44749e8649a25b38127347. It also happens to be threadsafe if you need it (and if you don't, it doesn't hinder performance).

    While it won't expose itself in the inspector, you could write a MonoBehavior that sets variables if you feel the need. Once the Manager is called the first time it's initialized for the length of runtime, regardless of scene changes and such. Any other script, MonoBehaviour or not, can call it by calling Manager.Instance. I tend to use a specific variable in any script that calls a manager, so it it was GameManager, I'd probably do:

    GameManager _gm = GameManager.Instance;


    At the initialization of the class. Then you can read _gm.Life. A damage script could call _gm.TakeDamage(int dmg), for instance, which would decrement Life by dmg. Another script could call _gm.Heal(int health) to increment Life by health.

    Singletons can be very, very bad if abused, but used in the right situations, they are wildly useful.
     
    seejayjames likes this.