Little Help Please

So my game have a bug where it counts points per blocks in level 1 but when i go to level to the score doesn’t change no matter how many block i destroy .
any clue?

Hi there

I think your problem may be related to the singleton implementation of your Game Status which was handled in the previous video’s discussion. It may be a better place to ask this question.

I have detailed a lot of what I went through, but the short answer is at the bottom of this reply.

I is hard to say without more detail.

I found that the Destroy() function did not actually destroy the TextMeshPro textbox, for the new gamestatus object. When block then tried to find the text box to use , it updated the new one, not the one from your original game status object, but the original game status objects text box is the visible one.

In short the Block,cs script found 2 game status objects, chose the wrong one an updated its text.

I tried 2 possible solutions:

  1. One solution which worked was just having the gameStatus prefab in the first stage and removing it from the other stages. Because the donotDestroyOnLoad() function keeps the game status object instance alive when the next level/scene is loaded.

The problem with this is that if the game starts anywhere else than level then there will be no game status object, no way to keep score.

  1. The solution that I am currently using is simpler. I used Rick’s for code gamestatus exactly, but instead of Destroy(gameObject) I used DestroyImmediate(gameObject) .

This works fine, but I have read that there are certain dangers to this.

My suspicion is that that Destroy(gameObject) has found some reference to the game status instance or its text box and is refusing to destroy that instance until the object referencing it releases it, which only happens when the scene unloads and all the objects get destroyed.

I would love to hear if anyone else has an idea about how to solve this and bring my code back in line with the course code.

Does anyone know what the difference is between Destroy() and DestroyImmediate()? Why is DestroyImmediate() discouraged?

Here is the short answer:

What I did wrong:

In my Block.cs I cached the GameStatus object (GameSession in later video) by declaring it at the top of my Block class

GameStatus gameStatusObject;

and assigned it in the Block class Start() function

GameStatus gameStatusObject = FindObjectOfType<GameStatus>();

I think that this created a reference to the GameStatus() object right at the start of the scene which then prevented Destroy(gameObject) from destroying the instance that was referenced.
I suspect that DestroyImmediate(gameObject) will destroy objects regardless of reference (which may be unsafe).

How I solved it:

Realised that I only needed to refer to the GameStatus when the block breaks to update the score, therefore caching it is unnecessary.

I declared and initialised it all in 1 line

GameStatus gameStatusObject = FindObjectOfType<GameStatus>();

in the destroyBlock() function, which only gets called when a block gets destroyed, long after the scenes GameStatus instance needed to be destroyed.

I hope this helps.

1 Like

Thanks Manie,

that solved my problem as well.

Glad it helped. This really had me scratching my head for two days. :slight_smile:

I did caching the gamestatusobject as you did. but how did you figure out this? can you briefly tell?
i was checking as well then i saw your message.

@ Volkan_Ilbasmis

I will try my best.

When my score display did not update on the second stage, I wondered why.

I thought that there were 3 possibilities:

  1. My gameStatus object was not being kept from 1 level to the next ie. destroyed at the end of each level.

I was able to rule this out pretty quickly because the old score from the previous stage still appeared. The score TextMeshPro object was a class for this and would have been destroyed and recreated as well.

  1. My gamestatus object was not addding the score after level 1.

To rule this out I used Debug.Log to log the score to the console. The score updated correctly in the log, but not on the screen. I did notice 2 different sets of scores in my logs… a clue!

  1. The last possibility was that there was more than 1 gamestatus object. This meant that one of the gamestatus objects TMProText was visible over the other one and that the hidden one was being updated.

To test this I changed my Gamestatus class Awake() function to log how many GameStatus object existed before and after the Destroy(gameObject) was called.

Like this:

  private void Awake()
    {
        //Here we will check if a GameStatus object exists from a previous scene. if do destroy this one
        //else keep this one and tell it not to destroy itself when level unloads

        int gameStatusCount = FindObjectsOfType<GameStatus>().Length;
        Debug.Log("Gamestatus count " + gameStatusCount.ToString());
        if (gameStatusCount > 1)
        {
          
            Destroy(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
        if (SceneManager.GetActiveScene().buildIndex != difficultyScreenIndex)
        {
            livesText.SetText("Lives: " + numberOfLives.ToString());
        }
        Debug.Log("Gamestatus count " + gameStatusCount.ToString());
    }

My logs indicated that after level 1 ended there were 2 GameStatus object / instances at the end of the Awake() function in every stage after level 1.

This meant that Destroy(gameObject) was not destroying the new instance of GameStatus before the level ended (big clue, level ends when all the blocks are destroyed) .

This meant that when Block.cs started up there was more than one GameStatus instance for it to find.

I searched around and found DestroyImmediate and used it in Gamestatus.cs, which solved the problem. Like this:

 private void Awake()
    {
        //Here we will check if a GameStatus object exists from a previous scene. if do destroy this one
        //else keep this one and tell it not to destroy itself when level unloads

        int gameStatusCount = FindObjectsOfType<GameStatus>().Length;
        Debug.Log("Gamestatus count " + gameStatusCount.ToString());
        if (gameStatusCount > 1)
        {
         
            DestroyImmediate(gameObject);
        }
        else
        {
            DontDestroyOnLoad(gameObject);
        }
        if (SceneManager.GetActiveScene().buildIndex != difficultyScreenIndex)
        {
            livesText.SetText("Lives: " + numberOfLives.ToString());
        }
        Debug.Log("Gamestatus count " + gameStatusCount.ToString());
    }

I noticed that DestroyImmediate() was not recommended and wondered why. I could not get a concrete answer but guessed that DestroyImmediate() did an unsafe destroy (destroy the object even if it is referred to by another object)

This gave me the idea to try and figure out which other object could be referencing the GameStatus while the level is still loading.

I remembered that my Block.cs script needed a GameStatus object to call the updateScore() method.
When I looked at the Block.cs script I noticed that I had referenced the GameStatus object right at the Start() and then held onto it until the block is hit/destroyed. That is why the GameStatus object was not destroyed, because all the blocks on the screen were holding onto it.

I then moved the code to find the GameStatus to the part of my code that only executes when the block is actually hit, ensuring that none of the blocks references the GameStatus before the extra GameStatus object could be destroyed.

Here is my Block.cs code:

[RequireComponent(typeof(AudioSource))]

public class Block : MonoBehaviour {
    [SerializeField] AudioClip[] breakSounds;

[SerializeField] int blockValue = 30;
[SerializeField] GameObject blockSparklesVFX; //Particle prefab
   
    [SerializeField] Sprite[] breakSprites;
    //Cached component variables 
    //Cached game objects

    Level levelObject; //level was created as a game object in the scene.

    //state variables

    [SerializeField] int timesHit = 0; //Serialised for debugging

    public void Start()
    {
      
        countBreakableBlocks();
    }

    private void countBreakableBlocks()
    {
        levelObject = FindObjectOfType<Level>();


        if (tag == "Breakable")
        {
            levelObject.countBreakableBlocks();
        }
    }

    private void OnCollisionEnter2D(Collision2D collision)
    {

        timesHit++;
        int maxHits = breakSprites.Length + 1;
        if (timesHit >= maxHits)
        {
            destroyBlock();
        }
        else
        {
            changeSprite(timesHit);
            playSound();
          //  Debug.Log("Calling sprite change"); 
        }

    }

    private void destroyBlock()
    {
        //Get the camera position
        playSound();

        if (tag == "Breakable")
        {
            triggerSparkleVFX();

            GameStatus gameStatusObject = FindObjectOfType<GameStatus>();
            gameStatusObject.addToScore(blockValue);

            Destroy(gameObject);
            // Debug.Log("Was hit by " + collision.gameObject.name);
            levelObject.removeBlock();



        }
    }

    private void playSound()
    {
        Vector3 blockLocation = Camera.main.transform.position;

        //Play the audio clip at the camera location

        if (tag != "Breakable")
        {
            AudioSource.PlayClipAtPoint(breakSounds[0], blockLocation);
        }
        else
        {
            AudioSource.PlayClipAtPoint(breakSounds[timesHit - 1], blockLocation);
        }
        //Use PlayClipAtPoint so that the audioclip plays even after the block is destroyed
    }

    void triggerSparkleVFX()
    {
        
       GameObject sparkle =  Instantiate(blockSparklesVFX, transform.position,transform.rotation);
        
        Object.Destroy(sparkle,2f);
        
    }
    void changeSprite(int spriteIndex)
    {
        if ( spriteIndex <= breakSprites.Length )
        {
          //  Debug.Log("Change sprite " + spriteIndex.ToString());
            if (breakSprites[spriteIndex - 1] != null) // In case we forget to assign sprites to breakSprites array 
            {
                GetComponent<SpriteRenderer>().sprite = breakSprites[spriteIndex - 1];
            }
            else {
                Debug.Log("The hit sprite was not assigned or was null in " + gameObject.name);
            }
        }

    }
}

I hope this explanation was not too long and that it makes sense. I current have the flu and the medication makes me a little fuzzy :wink:

Feel free to ask if I need to clarify.

2 Likes

thanx for your time
appriceated

Pleasure.

Privacy & Terms