This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 9 months ago.
This is my code that is receiving the error on the playerScore increment line. The code is in a prefab that is generated many times, and if it hits the player, it is destroyed and the player's score is incremented. Currently the code will destroy on collision with anything that does not have the name "Player" due to the error once the if statement is executed.
private void OnCollisionEnter2D(Collision2D collisionInfo)
{
Debug.Log($#"Collision name = {collisionInfo.collider.name}");
if (collisionInfo.collider.name == "Player")
{
GetComponent<GameMechanics>().playerScore++;
}
GameObject e = Instantiate(explosion) as GameObject;
e.transform.position = transform.position;
Destroy(gameObject);
}
This is my code for the referenced variable, I just cannot tell what I am doing wrong here, I want the object destroyed in all cases, but want the score incremented when the player hits it.Is the object being destroyed preventing the changes to be made to the player score?
public class GameMechanics : MonoBehaviour
{
public int playerScore;
// Start is called before the first frame update
void Start()
{
playerScore = 0;
}
// Update is called once per frame
void Update()
{
Debug.Log($#"Player Score = {playerScore} ");
}
}
Thanks in advance!
This is because you are using GetComponent in a class that does not have it and should find GameMechanics in it, but the result is null.
GetComponent<GameMechanics>().playerScore++; // GameMechanics dosen't found..
One way is to give GameMechanic to the other object's is through the inspector:
public GameMechanics Mechanics;
private void OnCollisionEnter2D(Collision2D collisionInfo)
{
if (collisionInfo.collider.name == "Player") Mechanics.playerScore++;
}
However There are other ways to reference GameMechanic and Manager classes in other objects, the best of which is Singleton. here is a simple way to solve your problem easy and that is to use FindObjectOfType. Change the code below and it will probably be fixed.
if (collisionInfo.collider.name == "Player")
{
FindObjectOfType<GameMechanics>().playerScore++; // replace it with this way
}
NullReferenceExceptions occur when the code attempts to access a variable which has not been initialized properly. In your case, this could be because of several reasons.
Ensure the gameobject is set to active in the scene
Ensure the script object is enabled on the gameobject
If these do not help, you can try making your variable static, and setting it to an ititial value of 0.
Static variables are loaded before everything else, and accessible by most other classes, so this may be able to fix your issue.
Related
I figured out the issue with a previous question on here, but now it's still not working.
Luckily I think I have figured out the issue. It's not detecting that an enemy has been killed/destroyed.
The way I have my code set up is that when an enemy is out of health the object is deactivated. But I assumed it would detect when an instance of the object was deactivated. But apparently not (at least from what I have seen).
I think that the best course of action is to make my enemy's "death script" connected to my win condition in some way (unless that isn't the proper way to go about it, in which case let me know what that proper action is).
I assume it would be to make the enemies variable global, but I am uncertain of how to do that or if it would even work for certain.
To reiterate, I am completely new to C# and unity, so if its solution is obvious or if the question is just stupid, remember that I have no clue where to go from this point (which is why this question is being created in the first place). If there is an existing question that can address this please link it, otherwise, anything helps.
here is the code for the win condition
public class GameWinner : MonoBehaviour
{
public GameObject[] enemies;
public Font font;
void OnTriggerEnter(Collider other)
{
enemies = GameObject.FindGameObjectsWithTag("Enemy"); // Checks if enemies are available with tag "Enemy". Note that you should set this to your enemies in the inspector.
if (enemies == null)
{
SceneManager.LoadScene(2);
}
}
}
here is the code for enemy destruction (note, this was ripped from a tutorial, specifically "let's try shooter", let me know if it would be beneficial to rework it completely)
public class ShootableBox : MonoBehaviour
{
//The box's current health point total
public int currentHealth = 3;
public void Damage(int damageAmount)
{
//subtract damage amount when Damage function is called
currentHealth -= damageAmount;
//Check if health has fallen below zero
if (currentHealth <= 0)
{
//if health has fallen below zero, deactivate it
gameObject.SetActive (false);
}
}
}
There's a difference between an empty array, and a null reference.
In your case, you should check to see if the array is empty:
enemies = GameObject.FindGameObjectsWithTag("Enemy"); // Checks if enemies are available with tag "Enemy". Note that you should set this to your enemies in the inspector.
if (enemies.Length == 0)
SceneManager.LoadScene(2);
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed last year.
I am using Vector3.MoveTowards, i have a separate function that instantiates a gameObject and I want to move this, In the Update function i have an if statement,
void Update()
{
if (move == true)
{
DrewCard.transform.position = Vector3.MoveTowards(DrewCard.transform.position, pos + new Vector3((CardsInHand.Count - 1) * width + (width / 2), -63, 1), change);
}
But this does not work as "DrewCard" is not defined, when i define it using GameObject DrewCard; It then says "NullReferenceException: Object reference not set to an instance of an object" Any ideas?
I might be assuming a bit now as shared code is not showing whole picture, but to you surely need to have DrewCard defined outside of this function somewhere inside this class of yours. I would be assuming it would be of type GameObject. Then as you mentioned you're instantiating that in some other function, you need to use return value (which is the gameobject you're looking for) and store it as DrewCard.
Also make sure to verify in your update function that DrewCard is not null. So overall script may look like this:
public class MyClass : MonoBehaviour {
// Your DrewCard object
private GameObject DrewCard;
// Reference to card prefab assignable in Editor thanks to SerializeField
[SerializeField] private GameObject CardPrefab;
private void Start() {
DrewCard = Instantiate(CardPrefab, Vector3.zero, Quaternion.identity);
}
private void Update() {
if(DrewCard != null) {
// Do the movement magic
}
}
}
Hope this covers your question, let me know if now
so I'm wanting to pause the game once the amount of enemies hits 0. So I'm using GameObject.FindGameObjectsWithTag("Enemy").Length to find the number of enemies. I put this in a function that's called right when the enemies are instantiated so I can see the length go to 4, as there's 4 enemies spawning. When an enemy is killed the function is called again where the length is printed to console again. For some reason, on the first enemy killed the count repeats with a 4 again despite there only being 3 enemies. Once another enemy is killed it reports 3 when there's actually 2 and so on until I get to 1 when there's 0 enemies.
Here's the first snippet of code:
public class EnemyList : MonoBehaviour
{
public List<GameObject> weakMobs = new List<GameObject>();
public List<GameObject> mediumMobs = new List<GameObject>();
public List<GameObject> bossMobs = new List<GameObject>();
public List<Transform> spawningChildren = new List<Transform>();
public static int mobCount;
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0], spawningChildren[Random.Range(0, 4)]) as GameObject;
}
CheckMobCount();
}
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Length;
print(mobCount);
}
The next piece of code is where the enemy is killed and the CheckMobCount() is called again.
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
enemyList.CheckMobCount();
//needs death animations
}
}
Here's the console messages:
Console of printed lengths
I'm self taught so I apologize if this is elementary. I've tried doing this several different ways and this is the closest I've been but I'm open to new ideas as well.
Thank you!!
As noted in this answer, the object is not actually destroyed in the current frame.
From the documentation:
The object obj is destroyed immediately after the current Update loop… Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
I also agree that using DestroyImmediate() is a bad idea.
Ultimately, your question seems to really be about pausing the game when the enemy count reaches 0, which unfortunately hasn't actually been answered yet.
In fact, you don't really need to do anything different except move the check for the enemy count to the beginning of the Update() method, and pause the game there if it's 0. Then you'll find that the component for the enemy has been destroyed at that point.
Presumably enemies are spawned before the update loop starts (i.e. before the first frame), but if not then you can use whatever logic you're already using to decide that new enemies need to be spawned, to detect the fact that you haven't spawned any yet and avoid pausing before the enemies have spawned.
Here you have attached your script to your enemy instances. And they are still alive when you are querying for the number of enemies left.
You should do the following:
public class Enemy: MonoBehaviour
{
public static int EnemyCount = 0;
private void Start()
{
EnemyCount++;
}
private void OnDestroy()
{
EnemyCount--;
}
}
And then you can query the enemy count from anywhere but just excessing the EnemyCount by Enemy.EnemyCount.
If you want to get a more difficult example then you can check out this Game Dev tutorial: https://www.youtube.com/watch?v=LPBRLg4c5F8&t=134s
Destroy is actually executed at the end of the frame. There is DestroyImmediate that is executed immidiatelly but it's not recommended to be used. What I would do is to add a field or a property to identify whether the enemy is still alive and then to check against it. Something like:
class Enemy : MonoBehaviour
{
public bool IsAlive { get; set; } = true;
}
public class EnemyList : MonoBehaviour
{
//...
public void CheckMobCount()
{
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Select(x => x.GetComponent<Enemy>()).Count(x => x.IsAlive);
print(mobCount);
}
}
And then:
public void TakeDamage()
{
enemyCurrentHealth -= 25;
enemyHealthBar.SetHealth(enemyCurrentHealth);
if (enemyCurrentHealth == 0)
{
Destroy(this.gameObject);
this.GetComponent<Enemy>().IsAlive = false;
enemyList.CheckMobCount();
//needs death animations
}
}
This can be further optimized to store the Enemy somewhere and not use GetComponent every time but you get the idea.
As already mentioned by others the issue is that Destroy is executed delayed.
Actual object destruction is always delayed until after the current Update loop, but is always done before rendering.
You could simply count only the GameObjects that are still alive, those for which the bool operator is true.
Does the object exist?
It will be false for objects destroyed in that same frame.
E.g. using Linq Count
using System.Linq;
....
mobCount = GameObject.FindGameObjectsWithTag("Enemy").Count(e => e);
which basically equals doing
mobCount = 0;
foreach(e in GameObject.FindGameObjectsWithTag("Enemy"))
{
if(e) mobCount++;
}
There is no need for an additional property or Component.
I am suggesting you to use “DestroyImmediate” instead of “Destroy”,Then look at the result.
I have a better idea, why not just use static variables when spawning enemies?
void Start()
{
for (int i = 0; i < spawningChildren.Count; i++)
{
GameObject newWeakMob = Instantiate(weakMobs[0],
spawningChildren[Random.Range(0, 4)]) as GameObject;
mobCount++;
}
}
Do not use Linq
Do not use DestroyImmediate (it will freeze and bug your game, probably)
Avoid FindGameObjectsWithTag in loops, only in initialization.
Track your enemies in an array or list
When you destroy an enemy, remove it's reference from the list
Use the list count/length to get the real actual number.
I have a singleton class and everytime I reload a scene the object reference I store in variable is destroyed
public class GameManager : MonoBehaviour
{
private void Awake()
{
if (instance == null)
{
instance = this;
}
else
{
Destroy(this.gameObject);
}
DontDestroyOnLoad(this);
Debug.Log("Scene reloaded");
}
void Start()
{
shapeSpawnerGO = GameObject.Find("SpawnShapesObj");
scoreGO = GameObject.Find("ScoreText");
lifeGo = GameObject.Find("LifeText");
}
public bool RedShapeStatus(int rcv_RedShapeIndex)
{
if (shapeSpawnerGO == null)
{
shapeSpawnerGO = GameObject.Find("SpawnShapesObj");
}
return shapeSpawnerGO.GetComponent<ShapeSpawnerChild>().listofRedShape[rcv_RedShapeIndex].activeSelf;
}
}
What I've done is check if shapeSpawnerGO is null then reference again the gameobject. And I think this is not efficient. Is there other way to solve this issue?
There are certainly other ways to accomplish this, but my official answer is "You're already doing it an acceptable way." You specifically said this:
"What I've done is check if shapeSpawnerGO is null then reference
again the gameobject. And I think this is not efficient. Is there
other way to solve this issue?"
You said the only time your code reinitializes the variables is whenever the scene reloads.That operation time doesn't even matter. You're literally talking about optimizing something completely irrelevant. Reinitializing scene data during a reload is what normal scene loading is all about.
The only exception to this would be if your idea of a scene reload is something you're doing every few seconds. If you're talking about the normal idea of a scene reload where you load the game scene once and then proceed to run the game for many minutes before a new scene reloads, then there's no reason to be worried about this code doing its normal initialization behavior.
I am trying to get the scene to switch when a method in the script TrippleBall returns 0. I know it returns 0 at the appropriate time because I have tested it. Here is my code to switch scenes:
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "ball")
{
col.gameObject.GetComponent<Ball>().setIsOffScreen(true);
/*error*/ if (GameObject.Find("TrippleBalls").GetComponent<TripleBall>().getBallCount() == 0) {
Debug.Log("Loading next screen...");
SceneManager.LoadScene("GameOverScene");
}
}
}
Here is an image to show where TrippleBalls is
The script TrippleBall is in the component TrippleBalls
Here is an image to show where the code above is located.
The code above is in a class called SceneTrigger which has been put in LBackBoard and RBackBoard
When I test the code, and the getBallCount returns 0 (to satisfy the condition above) I get the following error:
Object reference not set to an instance of an object
This error line sends me to where i marked error in the code above.
If anyone can help me figure this out, that would be awesome. Thank you!
You GameObject is named TrippleBall in the Scene but you are looking for TrippleBalls in the Scene. Simply change GameObject.Find("TrippleBalls") to GameObject.Find("TrippleBall");.
Also Don't use GameObject.Find in the OnTrigger function. It will slow down your game.Cach TripleBall in a local variable then you can re-use it.
TripleBall myTripleBall = null;
void Strt(){
//Cache TripleBall
myTripleBall = GameObject.Find("TrippleBalls").GetComponent<TripleBall>()
}
Now you can re-use it any time.
void OnTriggerEnter(Collider col)
{
if (col.gameObject.tag == "ball")
{
Debug.Log("COUNT IS: "+myTripleBall.getBallCount());
col.gameObject.GetComponent<Ball>().setIsOffScreen(true);
if (myTripleBall.getBallCount() == 0) {
Debug.Log("Loading next screen...");
SceneManager.LoadScene("GameOverScene");
}
}
}
Make sure to Add the GameOverScene scene in your Build Setting.
Advice:
Advice for you is that when Looking for another GameObject inside another GameObject, Use '/' like you would in a folder path. For example, TrippleBall is under Specialties GameObject. So use GameObject.Find("Specialties/TrippleBall"); instead of GameObject.Find("TrippleBall");