The question is answered, right answer was accepted
Can someone make sense out of seemingly inconsistent null reference exception?
Today I encountered the weirdest error, I hope someone can make sense out of this cause I'm baffled. I am a relative noob to Unity so please forgive and point out any wrong practices or design patterns. I have a class (below) that lets me add ammo to actors. It sends the ammo types to the component responsible for instantiating them, BeamFire and ProjectileFire; who both inherit from Fire. When I run this code, I get an error at the line Debug.Log("AddAmmoInManager" + GetFireComponent(ammoType).ToString()); because both beamFire and projectileFire are null. However, when I checked those field after initialising, in Start(), they display the correct component. What is going on here? Thanks so much for your time, this really has been frustrating me all day.
EDIT More to the point: why is beamFire and projectileFire null at line 37 when it's not at line 14? They don't seem to be destroyed externally, I say this because when the editor pauses I can still see both components on the gameobject. I call the AddAmmo from another script after Start(), and my console output is:
LeftPlayer (ProjectileFire) UnityEngine.Debug:Log(Object) ActorAmmoManager:Start() (at Assets/Scripts/Ammo/ActorAmmoManager.cs:17)
LeftPlayer (BeamFire) UnityEngine.Debug:Log(Object) ActorAmmoManager:Start() (at Assets/Scripts/Ammo/ActorAmmoManager.cs:18)
Projectile1 UnityEngine.Debug:Log(Object) ActorAmmoManager:AddAmmo(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:23) c__Iterator0:MoveNext() (at Assets/Scripts/WorldCreation/CreatePlayer.cs:60) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
Null UnityEngine.Debug:Log(Object) ActorAmmoManager:GetFireComponent(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:37) ActorAmmoManager:AddAmmo(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:24) c__Iterator0:MoveNext() (at Assets/Scripts/WorldCreation/CreatePlayer.cs:60) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
Null UnityEngine.Debug:Log(Object) ActorAmmoManager:GetFireComponent(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:38) ActorAmmoManager:AddAmmo(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:24) c__Iterator0:MoveNext() (at Assets/Scripts/WorldCreation/CreatePlayer.cs:60) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
GetFireComponentProjectile1 UnityEngine.Debug:Log(Object) ActorAmmoManager:GetFireComponent(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:39) ActorAmmoManager:AddAmmo(AmmoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:24) c__Iterator0:MoveNext() (at Assets/Scripts/WorldCreation/CreatePlayer.cs:60) UnityEngine.SetupCoroutine:InvokeMoveNext(IEnumerator, IntPtr)
NullReferenceException: Object reference not set to an instance of an object ActorAmmoManager.AddAmmo (AmmoType ammoType) (at Assets/Scripts/Ammo/ActorAmmoManager.cs:24) CreatePlayer+c__Iterator0.MoveNext () (at Assets/Scripts/WorldCreation/CreatePlayer.cs:60) UnityEngine.SetupCoroutine.InvokeMoveNext (IEnumerator enumerator, IntPtr returnValueAddress) (at /Users/builduser/buildslave/unity/build/Runtime/Export/Coroutines.cs:17)
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
[RequireComponent (typeof (BeamFire), typeof(ProjectileFire))]
public class ActorAmmoManager : MonoBehaviour
{
private ProjectileFire projectileFire;
private BeamFire beamFire;
void Start()
{
projectileFire = gameObject.GetComponent<ProjectileFire> ();
beamFire = gameObject.GetComponent<BeamFire> ();
Debug.Log (projectileFire);
Debug.Log (beamFire);
}
public void AddAmmo(AmmoType ammoType)
{
Debug.Log (ammoType);
Debug.Log("AddAmmoInManager" + GetFireComponent(ammoType).ToString());
GetFireComponent(ammoType).AddAmmo (ammoType);
}
public void RemoveAmmo(AmmoType ammoType)
{
Debug.Log ("RemoveAmmo");
GetFireComponent (ammoType).RemoveAmmo (ammoType);
}
private Fire GetFireComponent(AmmoType ammoType)
{
Debug.Log (projectileFire);
Debug.Log (beamFire);
Debug.Log ("GetFireComponent" + ammoType);
if (AmmoData.IsProjectile (ammoType))
return projectileFire;
if (AmmoData.IsBeam (ammoType))
return beamFire;
Debug.Log ("Error: ammoType is unknown");
Debug.Break ();
return null;
}
}
EDIT Thanks a lot to everybody helping me. It's really, really appreciated and makes for a very positive first experience on this site.
Answer by Matthewj866 · Apr 25, 2017 at 03:57 PM
Hi there.
I can't cay for certain, but based on what you've said about projectileFire
and beamFire
becoming null suggests something externally is destroying them, however I can't be certain of that, so I will just go with my own theory.
AmmoData.IsProjectile()
and AmmoData.IsBeam()
are both returning false because the ammoType
in question has been either set up incorrectly, or the way the check is being done has issues of it's own - again, an external problem. This means it will return null
because that's what you've told it to do. It cannot call ToString()
on null
, because null literally means nothing.
Hope this helps.
Hi, thanks so much for helping me out! I double checked, the IsProjectile and IsBeam functions are sound; also the error at lines 45 - 46 is never reached which means one of my functions must be returning true (right?). For sure the ToString method is passed a null parameter, and as you said my best guess is something externally is destroying them, but this doesn't seem to be happening.
Unfortunately I think I'd need to get a bit more hands on to find out for myself, but another possibility is that somehow the object is pointing to the saved prefab rather than the in-world instance? Which would of course make it return null, but besides that based on the information given I'm kind of stumped. :s
You are right! I read your answer but somehow first looked into the one from Bunny83. As I was copying the code to use in the comment on his answer, I noticed the error you suggested:
StartCoroutine(AddAmmo (leftPlayerPrefab, leftPlayerStartAmmo));
As you can see I'm adding ammo to the prefab ins$$anonymous$$d of the instance, just like you said. I'm gonna leave the bad code up for posterity. This code works like a charm:
StartCoroutine(AddAmmo (leftPlayer, leftPlayerStartAmmo));
Thanks so much!
Answer by krisuman · Apr 25, 2017 at 03:52 PM
What is AmmoData.IsProjectile and AmmoData.IsBeam doing? (Line 40 and 42) Are you sure that at least one of them returns true? Becaouse it looks that your function GetFireComponent returns null and that's why you got null reference exeception.
Hey, thanks so much for your help. You're completely right; unfortunately I didn't make myself as clear as I could. Indeed they return null; my problem is: how did they become null? As far as I can tell I'm not changing them. Not in this script; and also not in any other script: the BeamFire and ProjectileFire are are still there on the gameobject when the editor pauses on the null reference exception.
Answer by Bunny83 · Apr 28, 2017 at 03:47 PM
Well, it seems that your "GetFireComponent" is called from another component. You most likely have a "race condition" here (not related to threads in this case ^^). You initialize your two variables inside Start. However your other component that calls "GetFireComponent" directly or indirectly seems to run before your Start runs.
The easiest solution is to renamce "Start" into "Awake". Awake is called when the object has been created / loaded / instantiated. Start is called "later" right before Update is called on the scripts.
If you want to keep the code in Start you have to make sure that scripts runs before the other. You can do this by changing the script execution order. Note that if the other component tries to access GetFireComponent from it's own Awake method the script execution order won't help as Awake is always called before Start.
Have a look at the flowchart over here for more information.
Hey, thanks so much for your answer! I wasn't aware of the term "race condition" nor the ability to change the execution order, so I'm very grateful for this new knowledge. However I fail to see how it applies here. I tried your suggestion of rena$$anonymous$$g the Start method to Awake, but the same error arose. Also, lines 17 and 18 return the correct component. To prevent this type of error altogether I had also called AddAmmo from a "LateStart", a method recommended here: http://50.17.205.126/questions/971957/how-to-initialize-after-start.html?childToView=972003#answer-972003 . I've included the calling script but I had problems getting it into the right format for some reason.
using System.Collections; using System.Collections.Generic; using UnityEngine;
public class CreatePlayer : $$anonymous$$onoBehaviour { public GameObject leftPlayerPrefab; public GameObject rightPlayerPrefab; public ShipType leftPlayerShipType; public ShipType rightPlayerShipType; public AmmoType[] leftPlayerStartAmmo; public AmmoType[] rightPlayerStartAmmo; public Vector2 leftPlayerPos; public Vector2 rightPlayerPos;
private ShipCreator shipCreator;
private Player$$anonymous$$iddle player$$anonymous$$iddle;
void Start()
{
shipCreator = GameObject.Find ("ShipHolder").GetComponent<ShipCreator> ();
CreateLeftPlayer ();
}
private void CreateLeftPlayer()
{
GameObject leftPlayer = GameObject.Instantiate (leftPlayerPrefab, transform);
shipCreator.CreateShip (leftPlayerShipType, leftPlayerPos, leftPlayer.transform);
leftPlayer.name = "LeftPlayer";
StartCoroutine(AddAmmo (leftPlayerPrefab, leftPlayerStartAmmo));
}
IEnumerator AddAmmo (GameObject player, AmmoType[] ammoTypes)
{
yield return 2;
foreach (AmmoType ammoType in ammoTypes)
player.GetComponent<ActorAmmo$$anonymous$$anager> ().AddAmmo (ammoType);
}
}
Edit This code contains the error pointed out in the accepted answer; see above for the correct version.