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. Dismiss Notice

Question Moving a changed boolean between scenes

Discussion in 'Scripting' started by Draconek, Mar 24, 2023.

  1. Draconek

    Draconek

    Joined:
    Mar 16, 2023
    Posts:
    7
    So I've spent my day looking through tutorials and completely breaking my game having to fix it multiple times so I thought I would reach out again. Right now I've got an attack animation locked behind a boolean that will become true after collecting a weapon. I've set up a script to store the variables, one for the trigger of the weapon, and one for the weapon animation.

    Here's the relevant parts of the scripts.

    Code (CSharp):
    1. public class playerState : MonoBehaviour
    2. {
    3.     public bool hasKnife;
    4.     public bool hasGun;
    5. }
    Code (CSharp):
    1. public class pickupKnife : MonoBehaviour
    2. {
    3.     public GameObject theKnife;
    4.     public GameObject ThePlayer;
    5.     public GameObject uiPanel;
    6.     public GameObject textBox;
    7.     public string actionText;
    8.     public playerState playerstate;
    9.  
    10.  
    11.     public void Update()
    12.     {
    13.         if (Input.GetButtonDown("Action"))
    14.         {
    15.             theKnife.SetActive(false);
    16.             playerstate.hasKnife = true;
    17.         }
    18.     }
    19.  
    20.     private void OnTriggerEnter2D(Collider2D other)
    21.     {
    22.         textBox.GetComponent<Text>().text = actionText;
    23.         uiPanel.SetActive (true);
    24.     }
    25.  
    26.     private void OnTriggerExit2D(Collider2D other)
    27.     {
    28.         uiPanel.SetActive(false);
    29.     }
    30. }
    Code (CSharp):
    1. public class knifeAttack1 : MonoBehaviour
    2. {
    3.     public Animator animator;
    4.     public playerState playerstate;
    5.  
    6.     void Update()
    7.     {
    8.         if (Input.GetButtonDown("Fire1") && playerstate.hasKnife == true)
    9.         {
    10.             animator.SetBool("isStabbing", true);
    11.  
    12.         }
    13.         if (Input.GetButtonUp("Fire1"))
    14.         {
    15.             animator.SetBool("isStabbing", false);
    16.         }
    17.     }
    18. }
    Everything works fine, except for when I change scenes. After learning a bit more, I understand why, but the problems arise when I try and fix them. I've tried a few things, I've tried making a scriptable object, and while I don't remember exactly what went wrong, it went...very wrong. I've tried making my two variables static and that's throwing up errors saying that the variable cannot be accessed with an instant reference. My last big attempt was I tried using DontDestroyOnLoad via the following:

    Code (CSharp):
    1. public class playerState : MonoBehaviour
    2. {
    3.     public bool hasKnife;
    4.     public bool hasGun;
    5.     private static playerState instance;
    6.  
    7.     private void Awake()
    8.     {
    9.         if (instance != null)
    10.         {
    11.             Destroy(gameObject);
    12.         }
    13.         else
    14.         {
    15.             instance = this;
    16.             DontDestroyOnLoad(gameObject);
    17.         }
    18.      
    19.     }
    20. }
    I'm thinking my latest attempt is the right way to go about it (?) but it's still not working. When the new scene is launched, it's destroying the old blank game object as desired, but now my player object is no longer tied to the NEW gameobject. Anyone have any guidance on what to look for or what to do next?
     
  2. Magiichan

    Magiichan

    Joined:
    Jan 5, 2014
    Posts:
    403
    When the new scene loads, it'll still destroy the gameObject because you're not checking whether the instance is the singleton.
    upload_2023-3-24_10-13-14.png
    To fix this you should add another if statement to check whether the gameObject is newly instantiated.
    Also, since the destroy logic has been implemented regardless of the DontDestroyOnLoad, we can safely move that to the top of the awake script.

    The result looks something like
    Code (CSharp):
    1. public class playerState : MonoBehaviour
    2. {
    3.     public bool hasKnife;
    4.     public bool hasGun;
    5.     private static playerState instance;
    6.     private void Awake()
    7.     {
    8.         DontDestroyOnLoad(gameObject);
    9.         if (instance != null)
    10.         {
    11.             if (instance != this)
    12.             {
    13.                 Destroy(gameObject);
    14.             }
    15.         }
    16.         else
    17.         {
    18.             instance = this;
    19.         }
    20.    
    21.     }
    22. }
     
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    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.
     
  4. Draconek

    Draconek

    Joined:
    Mar 16, 2023
    Posts:
    7
    Thanks for the responses, guy, I've got something working now! I think my major issue was I was trying to reference the object and of course that object is going to be a different instance than upon scene creation so now instead of referencing it directly I'm using FindObjectOfType<playerState> and am pulling the object as needed.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,711
    Then you have failed to do the singleton correctly, or you're just doing some other pattern.

    The entire POINT is that it is the same persistent object.

    See the links and code snippets above. It actually works as advertised.

    Again, completely unnecessary with a proper service locator pattern, as identified above in my post. Yes it will work and if you're happy, go with it. But using FindObjectOfType<T>() on a per-frame basis would never pass my team's code review process.
     
    Magiichan likes this.