Search code examples
c#unity-game-engineruntime-errornullreferenceexceptionbreakout

Unity C# 2d Breakout Clone, Null Reference Exception


Okay so before I begin, Yes I have looked online to find this answer. I've followed advice on multiple other questions, gone through the unity documentation, and done more than a few web searches and nothing I've found so far has solved the error. I'm sure someone will take one look at this and know immediately what's wrong, but as for me, I just can't find it.

Now that that's out of the way Here's the problem. I'm making a Breakout clone and I had everything done, everything working properly. I've got one static class that takes care of the scoring and score related variables, so that other scripts can access them easily. I wanted to practice some basic saving and loading with PlayerPrefs, so I added something for highscores. It's pretty much independent of the other classes, but once I finished that, I started getting a Null Reference Exception in a script that has been done for hours, and was working fine.

I appreciate any help you might have, and any tips for preventing this kind of error the next time around. Sorry It's such a long question.

Here's the full error:

NullReferenceException: Object reference not set to an instance of an object
MenuManager.ActivateLose () (at Assets/Scripts/MenuScripts/MenuManager.cs:31)
Scoring.CheckGameOver () (at Assets/Scripts/Scoring.cs:64)
Scoring.LifeLost () (at Assets/Scripts/Scoring.cs:51)
DeadZone.OnTriggerEnter2D (UnityEngine.Collider2D other) (at Assets/Scripts/DeadZone.cs:22)

And here are the three scripts listed in said error:

using UnityEngine;
using System.Collections;

public class DeadZone : MonoBehaviour 
{

    public GameObject ballPrefab;
    public Transform paddleObj;

    GameObject ball;

    void Update () 
    {
        ball = GameObject.FindGameObjectWithTag("Ball");
    }

    void OnTriggerEnter2D(Collider2D other)
    {
        //if the object that entered the trigger is the ball
        if(other.tag == "Ball")
        {
            Scoring.LifeLost();
            //destroy it, and instantiate a new one above where the paddle currently is
            Destroy(ball);
            paddleObj.transform.position = new Vector2(0, -2.5f);
            (Instantiate(ballPrefab, new Vector2(paddleObj.transform.position.x, paddleObj.transform.position.y + 0.3f), Quaternion.identity) as GameObject).transform.parent = paddleObj;
        }
    }
}

using UnityEngine;
using System.Collections;

public static class Scoring
{

    public static GameObject scoreValue;
    public static TextMesh scoreText;
    public static int score;

    static int multiplier = 0;
    static int consecutiveBreaks = 0;
    static int lives = 3;
    static int totalBricks;
    static int remainingBricks;

    public static GameObject menuManagerObj;
    public static MenuManager menuManager = new MenuManager();

    static void Awake()
    {
        scoreValue = GameObject.FindGameObjectWithTag("Scoring");
        scoreText = scoreValue.GetComponent<TextMesh>();
        menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
        //menuManager = menuManagerObj.GetComponent<MenuManager>();
    }

    public static void BrickDestroyed()
    {
        if(scoreValue == null && scoreText == null)
        {
            scoreValue = GameObject.FindGameObjectWithTag("Scoring");
            scoreText = scoreValue.GetComponent<TextMesh>();
        }

        remainingBricks--;
        consecutiveBreaks++;
        multiplier = 1 + (consecutiveBreaks % 5);
        score += 10 * multiplier;
        CheckGameOver();
        scoreText.text = score + "";
    }

    public static void LifeLost()
    {
        consecutiveBreaks = 0;
        multiplier = 1;
        score -= 100;
        lives--;
        LivesDisplay.SetLives(lives);
        CheckGameOver();
        scoreText.text = score + "";
    }

    public static void SetBrickCount(int brickCount)
    {
        totalBricks = brickCount;
        remainingBricks = totalBricks;
    }

    public static void CheckGameOver()
    {
        //lose condition
        if(lives < 0) menuManager.ActivateLose();

        //win condition
        if(remainingBricks == 0) menuManager.ActivateWin();

    }

}

using UnityEngine;
using System.Collections;

public class MenuManager :  MonoBehaviour
{

    public GameObject winMenu;
    public GameObject loseMenu;
    public GameObject pauseMenu;
    public HighScoreManager highScores;

    bool isGamePaused = true;

    void Awake()
    {
        winMenu = GameObject.FindGameObjectWithTag("Win");
        loseMenu = GameObject.FindGameObjectWithTag("Lose");
        pauseMenu = GameObject.FindGameObjectWithTag("Pause");
    }

    public void ActivateWin()
    {
        Time.timeScale = 0f;
        winMenu.transform.position = new Vector3(0, 0, -1);
        highScores.CompareToHighScores(Scoring.score);
    }

    public void ActivateLose()
    {
        Time.timeScale = 0f;
        loseMenu.transform.position = new Vector3(0, 0, -1);
    }

    void ActivatePause()
    {
        if(isGamePaused)
        {
            Time.timeScale = 0f;
            pauseMenu.transform.position = new Vector3(0, 0, -1);
        }
        else
        {
            Time.timeScale = 1f;
            pauseMenu.transform.position = new Vector3(35, 0, -1);
        }
        isGamePaused = !isGamePaused;
    }

    void Update()
    {
        if(Input.GetKeyDown(KeyCode.Escape))
        {
            ActivatePause();
        }
    }

}

Solution

  • The problem is that Scoring is not a MonoBehaviour so the Awake method never gets called. You can try to initialize the fields in a static constructor

    static Scoring()
    {
        scoreValue = GameObject.FindGameObjectWithTag("Scoring");
        scoreText = scoreValue.GetComponent<TextMesh>();
        menuManagerObj = GameObject.FindGameObjectWithTag("MenuManager");
        //menuManager = menuManagerObj.GetComponent<MenuManager>();
    }
    

    or make the Awake method public and call it from another MonoBehaviour.