C#: Help with "Object reference not set to an instance of an object" error

I’m trying to use a list to store values for a tile map using the following class:

class TerrainInfo {
	public int terrainTileID;
	public GameObject terrainGameObject;
	
	public TerrainInfo(int tileID, GameObject gameObject) {
		terrainTileID = tileID;
		terrainGameObject = gameObject;
	}
}

From what I can tell, this is set up correctly. Now, after making sure the list is set up correctly using…

List<TerrainInfo> terrainArray = new List<TerrainInfo>();

…the next thing I do is fill this list with 0/null values just to make sure there are enough spaces in the array to store the full grid of tiles.

//Create list.
for (int x = 0; x < mapWidth; x++) {
	for (int z = 0; z < mapHeight; z++) {
		terrainArray.Add(new TerrainInfo(0, null));
	}
}

No errors up to this point, so I’m pretty sure that’s all correct. The final bit of the code is to create the boundary tiles around the edge of the map, so I use the following code (with some removed since it’s not relevant):

//Create bondary objects.
for (int x = 0; x < mapWidth; x++) {
	for (int z = 0; z < mapHeight; z++) {
		if (x == 0 || x == mapWidth - 1 || z == 0 || z == mapHeight - 1) {
			CreateTerrainGameObject(x, z, 1);
		}
	}
}

//Create Terrain Object
public void CreateTerrainGameObject(int x, int z, int tileID) {
	terrainArray[arrayPosition] = (new TerrainInfo(1, Instantiate(parentTile, new Vector3(x * tileSpacing, 0, z * tileSpacing), Quaternion.identity) as GameObject));
	terrainArray[arrayPosition].terrainGameObject.renderer.material.color = Color.red;
}

The “.renderer.material.color” line is just to test whether or not I can make changes to the object that was just created using a reference to its spot in the list. This is the line where the error is appearing, but I can’t figure out why. If there’s any info that left out or any clarification that needs to be made please let me know. Thanks in advance for the help!

Don’t you need to instantiate an instance of the terrainGameObject in your TerrainInfo class?

I’m thinking that you’r Instantiate call in CreateTerrainGameObject returns an instance of Transform. If that is the case you need to modify your method tolook like this

public void CreateTerrainGameObject(int x, int z, int tileID) {
    terrainArray[arrayPosition] = (new TerrainInfo(1, (Instantiate(parentTile, new Vector3(x * tileSpacing, 0, z * tileSpacing), Quaternion.identity) as Transform).GameObject));
    terrainArray[arrayPosition].terrainGameObject.renderer.material.color = Color.red;
}

Instantiate the object in play mode (without the .render.material call), and then pause the game. Inspect the newly-instantiated object and see if it actually has a renderer with a material attached. You might need to update your prefab or something.

Thanks for all the replies.

@dilbertian: I’m not convinced of that, but not really sure. By the time it gets to the TerrainInfo class aren’t you already supposed to have the data to plug in? Maybe I misunderstood what you meant?

@robin.theilade: Did a few tests with my original code to see if this was the case. First, I created a stand-in GameObject variable to ensure that problem wasn’t with the list itself. I got the same errors with the variable as I did the list, so that’s ruled out I guess. Next, I changed that stand-in variable from a GameObject to a Transform. This time I got the error “Cannot implicitly convert type UnityEngine.GameObject' to UnityEngine.Transform’,” which (I think) means the code I have is definitely returning a GameObject.

@Louis: Did this and the instantiated objects all have a renderer and material attached. Is there anything specific in the prefab that might need to be updated?

Oh, to tie together the tests mentioned in my response to robin.theilade and Loius’ concerns about the renderer being the issue, I changed this line of code…

terrainArray[arrayPosition].terrainGameObject.renderer.material.color = Color.red;

…to this (testGameObject replaced the list for my tests):

testGameObject.transform.position = new Vector3(0, 0, 0);

Unfortunately I still got the original error. It’s probably something really obvious that I’ll smack myself for when it’s figured out, haha. Until then…

The thought just hit me that the problem is most likely with my “instantiate” code (like robin.theilade had said), seeing as setting it to a variable instead of a list still results in the same error. The strange thing is that using the current instantiate code does create an object, I’m just not getting a reference to it. I’ve searched for other variations of the instantiate code such as…

GameObject testGameObject = (GameObject) Instantiate(parentTile, new Vector3(x * tileSpacing, 0, z * tileSpacing), Quaternion.identity);

…but they return other errors (such as “Cannot cast from source type to destination type” for this specific one).

I have tried reproducing your problem in Unity and I had to take some assumptions as you can see here:

using System.Collections.Generic;
using UnityEngine;

public class Vartib : MonoBehaviour
{
	private List<TerrainInfo> terrainArray = new List<TerrainInfo>();

	// don't see where you initializes these fields
	private float tileSpacing = 1.0f;
	public Transform parentTile;
	public int mapWidth = 100;
	public int mapHeight = 100;

	private void Awake()
	{
		//Create list.
		for( int x = 0; x < mapWidth; x++ )
		{
			for( int z = 0; z < mapHeight; z++ )
			{
				terrainArray.Add( new TerrainInfo( 0, null ) );
			}
		}

		//Create bondary objects.
		for( int x = 0; x < mapWidth; x++ )
		{
			for( int z = 0; z < mapHeight; z++ )
			{
				if( x == 0 || x == mapWidth - 1 || z == 0 || z == mapHeight - 1 )
				{
					CreateTerrainGameObject( x, z, 1 );
				}
			}
		}
	}

	//Create Terrain Object
	public void CreateTerrainGameObject( int x, int z, int tileID )
	{
		// don't see where you calculate arrayPosition so I added this line
		var arrayPosition = x + z * mapWidth;

		terrainArray[arrayPosition] = ( new TerrainInfo( 1, Instantiate( parentTile, new Vector3( x * tileSpacing, 0, z * tileSpacing ), Quaternion.identity ) as GameObject ) );
		terrainArray[arrayPosition].terrainGameObject.renderer.material.color = Color.red;
	}
}

This code does not fail for me. Maybe you should paste in the bare minimum for trying out the code you have without giving away your entire game.

There are a few issues. First, there’s a difference between the two ways you’re casting. This:

x = (MyClass)y;

is a strong cast and will throw an exception (“cannot cast from source to destination”) if it can’t convert y into a MyClass. Whereas this:

x = y as MyClass;

is a weak cast, and won’t throw an exception, but will just set x to null if it can’t convert y into a MyClass. That’s why you’re getting the null reference exception.

Next, the problem is probably that you can’t cast the instantiated parentTile clone to a GameObject, probably because it’s not a GameObject. The original parentTile is a Transform, which is a Component, and when you pass a Component to Instatiate, it tries to find the GameObject that it’s attached to, and clones it. If the Transform’s not actually attached to a GameObject, it won’t instantiate one. If it’s attached to an Object but not a GameObject it would instantiate an Object that it can’t cast into a GameObject. We can’t see from your code what that parentTile transform is actually attached to though.

Edited to add: If you can change your code so that you declare parentTile as “public GameObject parentTile” instead of Transform, that should solve it, since you’ll know you’re cloning an actual GameObject.

Welp, looks like it’s working now. I ended up trimming it down to the most basic pieces as robin suggested, and in the process changed partentTile to a GameObject like makeshiftwings suggested. It still wasn’t working, but I noticed in Unity itself the parentTile value was still a transform. Ended up reattaching the script to the object controlling the terrain and then reassigning an object to parentTile and it’s all working.

Thanks again to everyone that spent time helping. Off to tackle the next bug :slight_smile: