Search Unity

Question UI Text is still Destroyed even with DontDestroyOnLoad

Discussion in 'Scripting' started by oguzsabitay, May 30, 2020.

  1. oguzsabitay

    oguzsabitay

    Joined:
    May 21, 2020
    Posts:
    5
    Hello. I have a problem with DontDestroyOnLoad. I want to carry the current score of the scene to the next scene and to achieve this I use the following script.

    Code (CSharp):
    1. public class GameStatus : MonoBehaviour
    2. {
    3.     [SerializeField] private Text currentScoreText;
    4.  
    5.     private void Awake()
    6.     {
    7.         var gameStatusCount = FindObjectsOfType<GameStatus>().Length;
    8.  
    9.         if (gameStatusCount > 1)
    10.         {
    11.             Destroy(gameObject);
    12.         }
    13.         else
    14.         {
    15.             DontDestroyOnLoad(gameObject);
    16.         }
    17.     }
    18. }
    When in the first scene DontDestroyOnLoad works and keeps the GameStatus object in the next scene and also currentScoreText is there as well. But the problem is when the if condition is satisfied, GameStatus object of the next scene is destroyed and the UI Text (currentScoreText) in it is also destroyed. Thus, I have a GameStatus object under DontDestroyOnLoad but the next scene does not see its UI Text and Unity throws MissingReferenceException.

    Is there a way to fix this issue? Am I following a wrong approach?

    Thank you for your help.
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,674
    DontDestroyOnLoad() can only be applied to root GameObjects, otherwise it will happily get destroyed. If this is not on a root GameObject, you will get a yellow warning at runtime:

    DontDestroyOnLoad only work for root GameObjects or components on root GameObjects.
    UnityEngine.Object:DontDestroyOnLoad(Object)


    If you put this on a root GameObject, it's obvious that cannot also be the score text object, since all text objects have to go under canvases and hence they are not root GameObjects. Thus your text is destroyed while this script is still pointing at it.

    You can solve this a bunch of ways. Here are two possibles:

    - make a general GameManager class (hundreds of tutorials on Youtube for this) to keep your persistent score, then have a script on your Text object to retrieve it and display it.

    - put this at the root of your canvas hierarchy and your entire canvas hierarchy will be preserved. You would need to Destroy it when you left the game and went to the menu though.
     
    oguzsabitay likes this.
  3. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,908
    Destroy() doesn't actually destroy the object until the end of the frame. Therefore if you have two of these, both of them will find... both of them, and destroy themselves.

    Try this instead:

    Code (CSharp):
    1. static GameStatus instance = null;
    2.  
    3. void Awake() {
    4.   if (instance == null) {
    5.     instance = this;
    6.   }
    7.   else {
    8.     Destroy(gameObject);
    9.   }
    10. }
    Edit: OK I think Kurt's answer is probably closer to the truth of what's happening... I still suggest using a static variable vs. FindObjectsOfType though.
     
    oguzsabitay and Kurt-Dekker like this.
  4. oguzsabitay

    oguzsabitay

    Joined:
    May 21, 2020
    Posts:
    5
    These are the hierarchies of two of my levels.

    Hierarchy.png Hierarchy2.png

    On the first level DontDestroyOnLoad gets executed and on the second level Destroy is executed. But the GameStatus from level one proceeds to level 2.

    Hierarchy1_dontdestroy.png Hierarchy2_dontdestroy.png

    Because Level 2's own GameStatus is destroyed, the Score Text inside GameStatus which was not destroyed after Level 1 can not be seen by Level 2.

    Score Text from Level 1 is also carried to Level 2 with the correct value but in Level 2 whenever the script tries to update the Score Text, unity throws MissingReferenceException.

    Is there something I am missing? Thank you.
     
  5. oguzsabitay

    oguzsabitay

    Joined:
    May 21, 2020
    Posts:
    5
    I have tried your solution as well. I directly copied it and on Level 2 GameStatus from Level 1 was destroyed and I added
    DontDestroyOnLoad(this)
    after line 5, but then the same issue remains. Is there something I can't see? Thank you.
     
  6. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,674
    You can also have the score-setter check if the Text object has been destroyed and then re-find it, in any number of ways:

    - custom identifier MonoBehavior
    - finding it by unique name, then GetComponent
    - etc.

    There are other larger solutions to this sort of persistence, such as my datasacks package, but it might be overkill in your case. Datasacks is free online if you want to see, and it works like this diagram:

    20180724_datasacks_overview.png

    It bypasses all the DontDestroyOnLoad() stuff and just persists data, and you can display your data however you like.

    Datasacks is presently hosted at these locations:

    https://bitbucket.org/kurtdekker/datasacks

    https://github.com/kurtdekker/datasacks

    https://gitlab.com/kurtdekker/datasacks

    https://sourceforge.net/projects/datasacks/
     
    oguzsabitay likes this.
  7. PraetorBlue

    PraetorBlue

    Joined:
    Dec 13, 2012
    Posts:
    7,908
    Ah yeah - so you're in a good spot now, you just need one final piece - how do you reference the DontDestroyOnload object from the new scene after loading?

    Let's expand the example a little bit:
    Code (CSharp):
    1. public static GameStatus Instance { get; private set; } = null;
    2. void Awake() {
    3.   if (instance == null) {
    4.     Instance = this;
    5.     DontDestroyOnLoad(gameObject);
    6.   }
    7.   else {
    8.     Destroy(gameObject);
    9.   }
    10. }
    Then from your other script, instead of having a public GameStatus field, you can get the instance like this:
    Code (CSharp):
    1. GameStatus gameStatus = GameStatus.Instance;
    2.  
    3. // Do whatever you need to do with the GameStatus object.
     
    oguzsabitay likes this.
  8. oguzsabitay

    oguzsabitay

    Joined:
    May 21, 2020
    Posts:
    5
    Thank you for the answer. There was an issue with the score not being reset but after fixing that it works as it should.
     
  9. oguzsabitay

    oguzsabitay

    Joined:
    May 21, 2020
    Posts:
    5
    Thank you for the good explanation. I managed to fix it and I will also check datasacks, thank you for suggesting that as well.
     
    Kurt-Dekker likes this.