I've got a bit of a weird setup here so to explain real briefly, I have pickups that when hit, the game object is destroyed, but the script of the object is cloned and put into a list. This is so my pick up queue knows which pick ups we have collected, and which one to activate next. This is the error I'm getting:
NullReferenceException: Object reference not set to an instance of an object
AssaultRiflePickUp.StartCoroutine (Single delay, UnityEngine.MonoBehaviour coroutineHost) (at Assets/Scripts/AssaultRiflePickUp.cs:76)
AssaultRiflePickUp.AssaultRifleShot (UnityEngine.GameObject mainBall) (at Assets/Scripts/AssaultRiflePickUp.cs:93)
Ball.OnCollisionEnter2D (UnityEngine.Collision2D collider) (at Assets/Scripts/Ball.cs:63)
And I've put several debug.Log messages to narrow down exactly when it is throwing the error, in the following code, it is thrown right after AssaultRifleShot - 2, but before AssaultRifleShot - 3, so basically it's something to do with my coroutine I believe. Hopefully someone can help me figure out what is going on with it because I'm confused how it can say null exception when it's already calling a method from the object. Here is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using System.Threading;
public class AssaultRiflePickUp : BasePickUp, ISpecialShotPickUp
{
private int AmmoRemaining = 35;
private float timer = 0.3f;
public GameObject newBall;
void Start ()
{
//assign paddle to variable
paddle = GameObject.FindObjectOfType<Paddle> ();
//find the ball and assign it to this variable
foreach (Ball ball in GameManager.pickUpManager.allBalls)
{
if (ball.mainBall)
{
mainBall = ball;
}
}
}
public override void ActivatePickUp (MonoBehaviour coroutineHost)
{
Debug.Log("ActivatePickUp");
//subscribe to events
mainBall.onBallHitPaddle += AssaultRifleShot;
mainBall.onBallCollided += DestroyExtraBalls;
}
public void SpecialShot ()
{
Debug.Log("SpecialShot");
Vector3 mainBallVector = mainBall.GetComponent<Rigidbody2D>().velocity; //grab mainBall's direction and speed before making it a destroyable ball
mainBall.GetComponent<Ball>().mainBall = false;
Instantiate(newBall);
GameManager.pickUpManager.allBalls.Add(newBall.GetComponent<Ball>());
newBall.transform.position = paddle.transform.position + ballSpawnOffset; //use the original ball's vector3 to follow it directly
newBall.GetComponent<Rigidbody2D>().velocity = mainBallVector;
}
public void DestroyExtraBalls (GameObject ball)
{
if (GameManager.pickUpManager.allBalls.Count > 1)
{
}
//TODO: Implement observer pattern to watch for balls colliding with something. If they do and they are not the main ball, destroy them.
}
public void DestroyPickUp ()
{
Debug.Log("DestroyPickUp");
Destroy (GameManager.pickUpManager.pickUpQueue [0]);
GameManager.pickUpManager.pickUpQueue.RemoveAt (0);
GameManager.pickUpManager.pickUpActive = false;
}
//a timer used for timed pick ups
public IEnumerator PowerUpTimer (float delay)
{
Debug.Log("PowerUpTimer");
yield return new WaitForSeconds (delay);
SpecialShot();
}
//The coroutine is used to start a short timer so that the balls don't spawn on top of each other all at the same time.
public void StartCoroutine (float delay, MonoBehaviour coroutineHost)
{
coroutineHost.StartCoroutine(PowerUpTimer(delay));
}
//Controls the pick up's cycle
public void AssaultRifleShot (GameObject mainBall)
{
Debug.Log("AssaultRifleShot called");
if (AmmoRemaining > 0)
{
Debug.Log("AssaultRifleShot - Inside If Statement");
//if there is ammo remaining, set the main ball to just a normal ball so it can be destroyed on impact and then start coroutine
mainBall.GetComponent<Ball> ().mainBall = false;
Debug.Log("AssaultRifleShot - 1");
mainBall.GetComponent<Ball> ().onBallHitPaddle -= AssaultRifleShot; //unsubscribe to event when mainBall is set to false.
Debug.Log("AssaultRifleShot - 2");
StartCoroutine(timer, GameManager.monoBehaviour);
Debug.Log("AssaultRifleShot - 3");
AmmoRemaining -= 1;
Debug.Log("AssaultRifleShot - 4");
}
else
{
DestroyPickUp();
}
}
}
I'll post the cloning code here just in case it has anything to do with it. Maybe I'm not cloning it correctly? But it's working for my other pick ups, which also use coroutines.
public object Clone ()
{ //clone the pick up object to prevent a null reference when the pick up game object is destroyed
BasePickUp newPickUp = (BasePickUp)this.MemberwiseClone();
return newPickUp;
}