Search Unity

  1. Megacity Metro Demo now available. Download now.
    Dismiss Notice
  2. Unity support for visionOS is now available. Learn more in our blog post.
    Dismiss Notice

Unable to spawn new objects (Null Reference)

Discussion in 'Scripting' started by TheBereaved, Mar 22, 2019.

  1. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    Hey Guys,

    I'm fairly new to Unity (and programming in general) so bear with me if this is pretty messy.
    I've looked around for an answer on this, and I'm just not figuring it out.

    So I'm trying to make it a Breakout Clone.
    The goal I'm tryiing to achieve is that when a ball object collides with an OnTrigger collider below the playing field, the object is destroyed, and I spawn a new one on my paddle.

    My code works for the first re-spawn - but the second time, I'm getting a MissingReferenceException (despite having dragged a ball prefab on the public GameObject.

    Here's the event for when the Collider is triggered:
    Code (CSharp):
    1.     public BallSpawner ballSpawner;
    2.     public delegate void LoseBall();
    3.     public event LoseBall onLoseBall;
    4.  
    5.     void Start()
    6.     {
    7.         ballSpawner.SpawnOnPaddle(BallSpawner.BallTypes.Standard);
    8.     }
    9.  
    10.  
    11.     private void OnTriggerEnter2D(Collider2D collision)
    12.     {
    13.         onLoseBall();
    14.         ballSpawner.SpawnOnPaddle(BallSpawner.BallTypes.Standard);
    15.     }
    Here's my Spawner:
    Code (CSharp):
    1. public class BallSpawner : MonoBehaviour
    2. {
    3.     private GameObject spawnedBall;
    4.  
    5.     public GameObject standardBall;
    6.     public enum BallTypes
    7.     {
    8.         Standard
    9.     }
    10.  
    11.     private void SpawnBall(BallTypes balltype,Vector3 spawnPosition)
    12.     {
    13.         if(balltype == BallTypes.Standard)
    14.         {
    15.             spawnedBall = new GameObject("Ball in Play");
    16.             spawnedBall = Instantiate(standardBall, spawnPosition, Quaternion.identity);
    17.         }
    18.     }
    19.  
    20.     public void SpawnOnPaddle(BallTypes ballType)
    21.     {
    22.         Vector3 paddlePosition = PaddleManager.Instance.paddle.transform.position;
    23.         SpawnBall(ballType, paddlePosition);
    24.  
    25.         float offset = spawnedBall.GetComponent<CircleCollider2D>().bounds.extents.x;
    26.         paddlePosition.y += offset;
    27.         spawnedBall.transform.position = paddlePosition;
    28.         spawnedBall.GetComponent<Balls>().isOnPaddle = true;
    29.  
    30.     }
    31. }
    Finally, here's what I trigger on the Ball component when the collider is triggered:
    Code (CSharp):
    1. public class Balls : MonoBehaviour
    2. {
    3.  
    4. private void LoseBall()
    5.     {
    6.         if (gameObject != null)
    7.         {
    8.             Destroy(gameObject);
    9.         }
    10.     }
    11. }
     
  2. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    What line does the missing reference give as being the problem? Can you show where the
    onLoseBall
    from the first script is being subscribed to. It is possible that you are registering your template ball and destroying that instead of the ball-in-play.

    Also, there is no need for line 15 in your spawner script (unless you intend some side effect).
     
  3. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    Line 6 in LoseBall() is giving me the missing reference, specifically:
    "
    The object of type 'Balls' has been destroyed but you are still trying to access it.
    Your script should either check if it is null or you should not destroy the object."


    Turns out that if I change
    Code (CSharp):
    1. if(gameObject != null)
    to
    Code (CSharp):
    1. if(this !=null)
    It works perfectly. That's pretty confusing to me as I was under the impression that this was refering to the instance of the component, while gameObject was referencing the GameObject on which the component is attached to. I checked, and during runtime, the Ball gameObject is in fact destroyed like it should.

    As for the line 15 - Yes thank you! I realized that myself when I saw that I was creating an empty game object everytime.
     
  4. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    I didn't initially notice that line of code. So there are 2 things here.

    First, it would be worth having a read of this Unity blog explaining the problem you were seeing. For a slightly more accessible view of what this means in practice (i.e. how this can be viewed from within the debugger), you may like to have a read of this blog about it.

    Second,
    this
    should always be non null in raw C# unless you are specifically changing the generated IL. So if you remove the
    if(this != null)
    your code should still work. This will also bring your code in line with the Unity API reference.
     
    TheBereaved likes this.
  5. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    The links you shared have been amazingly useful! Thank you so much for this!

    I went ahead and just removed the conditional check
    if(this)

    and it's still returning the MissingExceptionError for some reason. Wrapping it again solves that problem.

    I'm destroying the gameObject from one of its attached component. If I understand the
    == null
    operator from Unity correctly, I'm then trying to access to Balls component attached to it, but it hasn't been picked up by GC yet and the c# still exists. I guess? it makes sense that checking for
    this
    cleans that up as I end up skipping the Balls instances who's c++ Object was destroy, but who's c# wrapper still lingers.
     
  6. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Must admit, I wasn't expecting that. But, dependent on the rest of your project, if you need it, then you need it. :)

    However, it does imply that you are destroying the object multiple times. Is that intentional?
     
  7. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    I'm only calling the Destroy Method once in the lifetime of the script. I'm not sure how I'm destroying it multiple times?

    However it is intentional that once I destroy a Ball - I do in fact spawn a new one, which can also be destroyed (and so on). In hindsight, I probably could have simply repositioned the Ball instead of destroying it - but I did want to fiddle around with destroying/spawning objects at runtime.
     
  8. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    In this instance, either repositioning or destroying/creating the ball is fine. Of course, the latter would not scale well in the case of things that created / destroyed with any rapidity.

    Still, it does seem that something isn't quite plugged in correctly as you appear to be getting multiple destroys on a single object. Out of interest, what is
    gameObject
    , can you paste the Balls.cs script?
     
  9. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    So Balls.cs is actually an abstract class for the future types of balls I'll be adding.
    Code (CSharp):
    1.  
    2. public abstract class Balls : MonoBehaviour
    3. {
    4.     public int diameter;
    5.     public float speed;
    6.     public float reflectIntensity;
    7.     public Rigidbody2D rb;
    8.     public bool isOnPaddle;
    9.     public GameController gameController;
    10.  
    11.     private Vector2 velocity;
    12.  
    13.     protected virtual void Awake()
    14.     {
    15.         transform.localScale = new Vector3(diameter, diameter, 0f);
    16.         gameController = FindObjectOfType<GameController>();
    17.         gameController.onLoseBall += LoseBall;
    18.     }
    19.     protected virtual void Update()
    20.     {
    21.         if (isOnPaddle)
    22.         {
    23.             FollowPaddle();
    24.             if (Input.GetMouseButtonDown(0))
    25.             {
    26.                 LaunchBall();
    27.             }
    28.         }
    29.         if(!isOnPaddle)
    30.         {
    31.             Move();
    32.         }
    33.     }
    34.  
    35.     public void Move()
    36.     {
    37.         rb.velocity = velocity*speed;
    38.     }
    39.  
    40.     protected virtual void OnCollisionEnter2D(Collision2D collision)    
    41.     {
    42.         velocity = Vector2.Reflect(velocity, collision.contacts[0].normal);
    43.  
    44.         //reflection against paddle. Increases the horizontal reflection based on the distance from the center of the paddle, and the collision location.
    45.         //Increase the reflectIntensity float to increase the effect of this.
    46.         if (collision.collider.tag == "Player" && !isOnPaddle)
    47.         {
    48.             var distFromCenter = (collision.contacts[0].point.x - collision.transform.position.x);
    49.             var direction = new Vector2(distFromCenter*reflectIntensity, 1).normalized;
    50.             velocity = direction;
    51.         }
    52.  
    53.         //assigns the new velocity to the rigidBody.
    54.         Move();
    55.     }
    56.  
    57.     //Call this when the paddle needs to stay on the paddle before being launched.
    58.     protected void FollowPaddle()
    59.     {
    60.         var paddleXPos = PaddleManager.Instance.paddle.transform.position.x;
    61.         var pos = new Vector3(paddleXPos, transform.position.y);
    62.         transform.position = pos;
    63.     }
    64.     public void LaunchBall()
    65.     {
    66.         Vector2 startingVector = Vector2.up.normalized;
    67.         velocity = startingVector;
    68.         isOnPaddle = false;
    69.  
    70.     }
    71.     private void LoseBall()
    72.     {
    73.         if (this)
    74.         {
    75.             Destroy(gameObject);
    76.         }
    77.     }
    78. }
    The actual ball that is being spawned is of type Standard_Ball, which doesn't have anything different than the base class right now:
    Code (CSharp):
    1.  
    2. public class Standard_Ball : Balls
    3. {
    4.     protected override void Awake()
    5.     {
    6.         base.Awake();
    7.     }
    8. }
    I'm spawning specifically a Standard Ball using this. I plan on having this handle the different types of Ball being spawned.

    Code (CSharp):
    1. public class BallSpawner : MonoBehaviour
    2. {
    3.     private GameObject spawnedBall;
    4.  
    5.     public GameObject standardBall;
    6.     public enum BallTypes
    7.     {
    8.         Standard
    9.     }
    10.  
    11.     private void SpawnBall(BallTypes balltype,Vector3 spawnPosition)
    12.     {
    13.         if(balltype == BallTypes.Standard)
    14.         {
    15.             spawnedBall = Instantiate(standardBall, spawnPosition, Quaternion.identity);
    16.         }
    17.     }
    18.  
    19.     public void SpawnOnPaddle(BallTypes ballType)
    20.     {
    21.         Vector3 paddlePosition = PaddleManager.Instance.paddle.transform.position;
    22.         SpawnBall(ballType, paddlePosition);
    23.  
    24.         float offset = spawnedBall.GetComponent<CircleCollider2D>().bounds.extents.x;
    25.         paddlePosition.y += offset;
    26.         spawnedBall.transform.position = paddlePosition;
    27.         spawnedBall.GetComponent<Balls>().isOnPaddle = true;
    28.     }
    29. }
     
    Last edited: Mar 23, 2019
  10. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    Ok, looks like the reason for the extra calls is that on line 17 of Balls.cs the ball is registering itself to the event. That needs to be undone otherwise, with every destruction, all previously 'lost' balls will continue to receive the event calls.

    Maybe try something like this:
    Code (CSharp):
    1.     private void LoseBall()
    2.     {
    3.         gameController.onLoseBall -= LoseBall;
    4.         Destroy(gameObject);
    5.         gameObject = null;
    6.     }
    :
     
  11. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    Oh!
    That actually makes so much sense haha!

    So I went ahead and did this
    Code (CSharp):
    1. private void LoseBall()
    2.     {
    3.             gameController.onLoseBall -= LoseBall;
    4.             Destroy(gameObject);
    5.     }
    That seems to work wonders. However if I try to add
    gameObject = null
    I get an error because Component.gameObject is a readonly property and I can't set it.
     
  12. Doug_B

    Doug_B

    Joined:
    Jun 4, 2017
    Posts:
    1,596
    My mistake, that line clearly isn't going to work. At least you got the root problem resolved anyway. :)
     
    TheBereaved likes this.
  13. TheBereaved

    TheBereaved

    Joined:
    Mar 4, 2019
    Posts:
    7
    Yes!

    Thank you so much for all the help!
     
    Doug_B likes this.