I'm making a save system for my game, and have run into a NullReferenceException while loading the keybindings for one of the player objects (player 2). Since I used identical code to load the keybinds for player 1, and that one works fine, I believe that the problem comes from player 2 being inactive when the load script is run.
Here's most of the relevant code, based on Brackey's Save/Load tutorial:
public string[] player1;
public string[] player2;
public GameData(AchievementsManager am, GameObject play2)
{
var p1 = play1.GetComponent<playerMovement>();
var p2 = play2.GetComponent<playerMovement>();
player1 = new string[4] { p1.up, p1.down, p1.left, p1.right};
player2 = new string[4] { p2.up, p2.down, p2.left, p2.right};
}
public class SaveManager : MonoBehaviour
{
public GameObject play2;
void Start()
{
GameData data = SaveSystem.loadGame();
if (data != null)
{
var p1 = play1.GetComponent<playerMovement>();
var p2 = play2.GetComponent<playerMovement>();
string[] p1_controls = data.player1;
string[] p2_controls = data.player2;
p1.up = p1_controls[0]; p2.up = p2_controls[0];
p1.down = p1_controls[1]; p2.down = p2_controls[1];
p1.left = p1_controls[2]; p2.left = p2_controls[2];
p1.right = p1_controls[3]; p2.right = p2_controls[3];
}
}
}
The error is on the line p2.up = p2_controls[0];, but I think that it's because there's a problem with play2.GetComponent<playerMovement>(); related to player 2 being inactive at startup. Is there a good way to fix this without making player 2 active at startup, or at least a way to better understand what exactly is causing the problem?
play2 is a GameObject referenced in the class header of SaveManager. play2 does have a playerMovement component that runs properly. I think that the NullReferenceException comes from the SaveManager not properly loading the information that it contains.
You should use in the GameData() an If(play2 != null) before trying to access to the P2 variable and assign it.
And you can do the same thing in the Start () when you assign the P2 variables
Related
This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 2 months ago.
I am learning to use Unity, I am currently doing the "Personal 3D Gallery Project" , here is the link if you want to take a look at it: https://learn.unity.com/project/create-a-personal-3d-gallery-project-with-unity?uv=2019.4
However, instead of displaying works of art in my gallery, I am displaying planets and my objective is that when the player approaches one of the planets, a text with information about that given planet pops out. In order to this, I am using the proximity script provided in the Unity website with a few changes. Everything seems to be working fine but when I approach the planets the text does not show and I keep receiving this error:
NullReferenceException: Object reference not set to an instance of an object
Proximity.Start () (at Assets/Scripts/Proximity.cs:32)
Apparently the problem is on line 32 but I can't figure out what's causing it. I am going to be attaching images for a better understating of the problem and my code.
I would really appreciate your help since this is for a final project, thank you so much in advance.
Here is my code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class Proximity : MonoBehaviour
{
public string newTitle;
public string newNumber;
public string newInfo;
private Transform other;
private Text myTitle;
private Text myNumber;
private Text myInfo;
private float dist;
private GameObject player;
private GameObject message1;
private GameObject message2;
private GameObject message3;
private bool check;
// Start is called before the first frame update
void Start()
{
player = GameObject.FindWithTag("Player");
other = player.GetComponent<Transform>();
message1 = GameObject.FindWithTag("PlanetTitle");
message2 = GameObject.FindWithTag("PlanetNumber");
message3 = GameObject.FindWithTag("PlanetInfo");
myTitle = message1.GetComponent<Text>();
myTitle.text = "";
myNumber = message2.GetComponent<Text>();
myNumber.text = "";
myInfo = message3.GetComponent<Text>();
myInfo.text = "";
check = false;
}
// Update is called once per frame
void Update()
{
if (other)
{
dist = Vector3.Distance(transform.position, other.position);
print("Distance to player: " + dist);
if (dist < 4)
{
myTitle.text = newTitle;
myNumber.text = newNumber;
myInfo.text = newInfo;
check = true;
}
if (dist > 4 && check == true)
{
Start();
}
}
}
}
This is what I'm trying to do
But this is how it looks when I approach the planet (or moon in this case)
Here is my inspector, just in case the issue could be there
I checked my code multiple times and compared it to the original, I think I made all the necessary changes but clearly there is something wrong with it.
myTitle is not initialised before you are setting the property text on it. Initialise it by assigning the required components in Awake.
void Awake()
{
player = GameObject.FindWithTag("Player");
other = player.GetComponent<Transform>();
message1 = GameObject.FindWithTag("PlanetTitle");
message2 = GameObject.FindWithTag("PlanetNumber");
message3 = GameObject.FindWithTag("PlanetInfo");
myTitle = message1.GetComponent<Text>();
myNumber = message2.GetComponent<Text>();
myInfo = message3.GetComponent<Text>();
}
void Start()
{
myTitle.text = "";
myNumber.text = "";
myInfo.text = "";
check = false;
}
I need your help with things that are going to blow up my mind...(Don't know why but I can't write "Hello everybody!" at the start of the post. Forum hides it:)
I can't figure out why my List <'My Tile Class> is cleared by itself.
I have few prefabs (with box-collider) with no scripts attached to them and one script on the scene. The idea is to take random prefab, attach random value to it and initiate it. After that player clicks on prefab on the scene and gets its value.
public class Generate_Field : MonoBehaviour {
public class Tiles {
public GameObject My_Tile;
public int My_Value;
}
public List<Tiles> My_List;
private GameObject EmptyObj;
// ... other variables
void Start (){
List<Tiles> My_List = new List<Tiles> ();
// ... below some calculation (about 100 rows)
//Instantiate obj and give its reference to Emptyobj
EmptyObj = Instantiate (My_GameObj_To_Inst, new Vector3(My_X, My_Y, 0f), Quaternion.identity);
Tiles Tile1 = new Tiles ();
Tile1.My_Tile = EmptyObj;
Tile1.My_Value = 1; //
My_List.Add (Tile1); // Add Tile1 (with reference to Gameobj and value to List)
// If I put code inside Void Start () it works ok and print all values
foreach (Tiles SS in My_List) {
print (SS.My_Value);
}
The problem is when I put it to Void Update ().
My_List somehow "Suicide" to zero.count although it is public List. In that case I get error:
"NullReferenceException: Object reference not set to an instance of an object..."
void Update ()
{
if (Input.GetKeyDown (KeyCode.Mouse1)) {
print (My_List.Count);
}
}
You declare a new local list in Start and populate that one. If you mean to initialise the class member, remove the type from the line doing the initialisation.
void Start (){
My_List = new List<Tiles> (); // class member, not new variable.
As a usual guideline, it's better to initialise lists at the declaration point. There's usually no reason to replace the list at a later time.
I am trying to change game objects, specifically Cubes, to change material during run-time.
Rather then trying to search for material by name, I decided to try a different approach, assign a few game objects with pre-set materials, then when I want to change material of something, I am trying to get it from those pre-made objects.
I already spent some time trying to fix that, found a few possible mistakes, fixed them, but I still don't get the result I need.
And I also use debugging, will give more information below.
So, I first created a few Materials as global variables.
public Material MaterialGrass;
public Material MaterialRock;
public Material MaterialSand;
public Material MaterialWater;
Then, I am trying to load actual materials from pre-set game objects.
GameObject MainVariablesObject = GameObject.Find("MainVariablesObject");
GlobalVariablesScript thisGlobalVariablesScript = MainVariablesObject.GetComponent<GlobalVariablesScript>();
thisGlobalVariablesScript.MaterialGrass = GameObject.Find("GrassTexture").GetComponent<MeshRenderer>().materials[0];
thisGlobalVariablesScript.MaterialRock = GameObject.Find("RockTexture").GetComponent<MeshRenderer>().materials[0];
thisGlobalVariablesScript.MaterialSand = GameObject.Find("SandTexture").GetComponent<MeshRenderer>().materials[0];
thisGlobalVariablesScript.MaterialWater = GameObject.Find("WaterTexture").GetComponent<MeshRenderer>().materials[0];
The game view is divided into 9 squares.
GameObject CubeFront0 = GameObject.Find("CubeFront0");
GameObject CubeFront1 = GameObject.Find("CubeFront1");
GameObject CubeFront2 = GameObject.Find("CubeFront2");
GameObject CubeLeft0 = GameObject.Find("CubeLeft0");
GameObject CubeLeft1 = GameObject.Find("CubeLeft1");
GameObject CubeLeft2 = GameObject.Find("CubeLeft2");
GameObject CubeRight0 = GameObject.Find("CubeRight0");
GameObject CubeRight1 = GameObject.Find("CubeRight1");
GameObject CubeRight2 = GameObject.Find("CubeRight2");
And now the part that I still didn't manage to solve:
public static void DisplayPlayerView()
{
GameObject MainVariablesObject = GameObject.Find("MainVariablesObject");
GlobalVariablesScript thisGlobalVariablesScript = MainVariablesObject.GetComponent<GlobalVariablesScript>();
DefineTexturesSources();
GetSurroundingCells();
GameObject CubeFront0 = GameObject.Find("CubeFront0");
GameObject CubeFront1 = GameObject.Find("CubeFront1");
GameObject CubeFront2 = GameObject.Find("CubeFront2");
GameObject CubeLeft0 = GameObject.Find("CubeLeft0");
GameObject CubeLeft1 = GameObject.Find("CubeLeft1");
GameObject CubeLeft2 = GameObject.Find("CubeLeft2");
GameObject CubeRight0 = GameObject.Find("CubeRight0");
GameObject CubeRight1 = GameObject.Find("CubeRight1");
GameObject CubeRight2 = GameObject.Find("CubeRight2");
GameObject[] Cubes = new GameObject[] { CubeFront0, CubeFront1, CubeFront2,
CubeLeft0, CubeLeft1, CubeLeft2,
CubeRight0, CubeRight1, CubeRight2};
char[] Map = new char[] { thisGlobalVariablesScript.Front0, thisGlobalVariablesScript.Front1, thisGlobalVariablesScript.Front2,
thisGlobalVariablesScript.Left0, thisGlobalVariablesScript.Left1, thisGlobalVariablesScript.Left2,
thisGlobalVariablesScript.Right0, thisGlobalVariablesScript.Right1, thisGlobalVariablesScript.Right2};
for (int i = 0; i < 9; i++)
{
Material thisMaterial = Cubes[i].GetComponent<MeshRenderer>().materials[0];
//MeshRenderer thisMaterial = Cubes[i].GetComponent<MeshRenderer>();
if (Map[i] == 'G')
thisMaterial = thisGlobalVariablesScript.MaterialGrass;
if (Map[i] == 'R')
thisMaterial = thisGlobalVariablesScript.MaterialRock;
if (Map[i] == 'S')
thisMaterial = thisGlobalVariablesScript.MaterialSand;
if (Map[i] == 'W')
thisMaterial = thisGlobalVariablesScript.MaterialWater;
Cubes[i].GetComponent<MeshRenderer>().materials[0] = thisMaterial;
Debug.Log(Cubes[i].name + " " + Cubes[i].GetComponent<MeshRenderer>().materials[0]);
}
What I found by placing those Debug.Log commands:
1) All game objects I try to "Find" exist, there is no error here.
2) "thisMaterial" gets the correct material, so I think there is no problem with defining materials as variables, and getting correct data.
3) I no longer get error that material isn't attached.
4) Inside the "for" loop, the "thisMaterial" shows new and correct data, and "Cubes[i].GetComponent().materials[0]" shows old data - materials I set by hand in the beginning.
5) The data in array "Map" is correct.
What I am missing?
I have a gameObject (myObject) and a component that attached somewhere in that gameObject (myComponent).
I duplicate game object:
var duplicate = Instantiate(myObject);
And then I want to have reference to the same component, but on my duplicate gameObject.
Is there a way to get same component on duplicated object?
I tried to get component by index, but is is not work for hierarchy of game objects.
You could make a copy of component but since component can have only 1 parent (gameObject) this means 1 component instance cannot be shared with 2 game objects.
Otherwise if you are OK with having 2 separate instances of component on both game objects, you could create Prefab out of gameObject (with component) and instantiate prefab.
Considering that you might have multiple components of the same type, but with different settings (properties) and you want to find component with the same settings you would have to use GetComponents and loop through results to find a new (duplicated) component with exactly the same settings.
Considering (for simplicity) you look for property called Id:
MyComponent myObjectsComponent = ... // here logic to find it, etc
GameObject duplicate = Instantiate(myObject);
List<MyComponent> myComponents = duplicate.GetComponents<MyComponent>();
// This can be replaced with bellow LINQ
MyComponent foundComponent = null;
foreach(MyComponent c in myComponents) {
if (c.Id=myObjectsComponent.Id) {
foundComponent = c;
break;
}
}
Alternatively you could use LINQ to simplify loop:
MyComponent foundComponent = (from c in myComponents where c.Id=myObjectsComponent.Id select c).FirstDefault<MyComponent>();
Thanks to all of you.
I was needed something like this:
public static T GetSameComponentForDuplicate<T>(T c, GameObject original, GameObject duplicate)
where T : Component
{
// remember hierarchy
Stack<int> path = new Stack<int>();
var g = c.gameObject;
while (!object.ReferenceEquals(g, original))
{
path.Push(g.transform.GetSiblingIndex());
g = g.transform.parent.gameObject;
}
// repeat hierarchy on duplicated object
GameObject sameGO = duplicate;
while (path.Count != 0)
{
sameGO = sameGO.transform.GetChild(path.Pop()).gameObject;
}
// get component index
var cc = c.gameObject.GetComponents<T>();
int componentIndex = -1;
for (int i = 0; i < cc.Length; i++)
{
if (object.ReferenceEquals(c, cc[i]))
{
componentIndex = i;
break;
}
}
// return component with the same index on same gameObject
return sameGO.GetComponents<T>()[componentIndex];
}
Try this:
var duplicate = Instantiate(myObject) as GameObject;
var componentDup = duplicate.GetComponent<__YOUR_COMPONENT__>();
https://docs.unity3d.com/ScriptReference/GameObject.GetComponent.html
Alright, I'm going to have a go at trying to understand what you want as well... My interpretation is that you want a reference to a specific myComponent on your duplicateObject. Very well, this is how you could do it:
public class MyObject : MonoBehaviour {
// Create a reference variable for the duplicate to have
public Component theComponent { get; set; }
void Start()
{
// Save the component you want the duplicate to have
theComponent = GetComponent<Anything>();
// Create the duplicate
var duplicate = Instantiate(gameObject);
// Set the component reference to the saved component
duplicate.GetComponent<MyObject>().theComponent = theComponent;
}
}
This way you should have a reference to the same component in all your instances of the object. You could also just create a script that holds a static reference to the specific component, which every script can reach without instantiating anything.
using UnityEngine;
public class DataHolder {
public static Component theComponent;
}
I am new to Unity3D and C# and have been struggling to find a convenient way to reuse the same prefab, with unique name & tag, and Add it to a List.
I have the following test code:
public class testScript : MonoBehaviour {
public static List<string> cardDeckBackListNames = new List<string> {
"HAB", "H2B", "H3B", "H4B", "H5B", "H6B", "H7B", "H8B", "H9B", "H10B", "HJB", "HQB", "HKB",};
private GameObject temp_GameObject;
public static List<GameObject> master_GameObject_List = new List<GameObject> ();
// Use this for initialization
void Start () {
temp_GameObject = Resources.Load ("BackSide") as GameObject;
temp_GameObject.name = cardDeckBackListNames [0];
temp_GameObject.tag = cardDeckBackListNames [0];
master_GameObject_List.Add (temp_GameObject);
temp_GameObject = Resources.Load ("BackSide") as GameObject;
temp_GameObject.name = cardDeckBackListNames [1];
temp_GameObject.tag = cardDeckBackListNames [1];
master_GameObject_List.Add (temp_GameObject);
int yy = 0;
foreach (GameObject aGO in master_GameObject_List) {
print ("After loading: " + aGO.tag + " #"+ yy);
yy++;
}
print ("master_GameObject_List.Count: " + master_GameObject_List.Count);
}
}
Get the following result:
After loading: H2B #0
After loading: H2B #1
master_GameObject_List.Count: 2
My conclusion is that I cannot use the same GameObject and "only" modify the name & tag as it seems like when i do that it also modify all objects that i have loaded into the List.
As i am very new at C# i would like to ask what would be the correct way of doing what i am trying to do? 1.) reusing the same prefab with unique name & tag, 2.) load the "unique" objects into a List
ps. I initially used a "foreach" loop but removed that to be able to do a clean and simple test.
Problem
When you use Resources.Load() you loading the prefab, but every time you make a change you are editing the same prefab.
Here is a picture of the editor showing you what you are pointing to:
Solution
Since you want unique GameObjects the first thing you need to do is Instantiate them. After that you can edit each individual GameObject as you like.
void Start ()
{
temp_GameObject = Instantiate(Resources.Load("BackSide")) as GameObject;
temp_GameObject.name = cardDeckBackListNames[0];
temp_GameObject.tag = cardDeckBackListNames[0];
master_GameObject_List.Add(temp_GameObject);
temp_GameObject = Instantiate(Resources.Load("BackSide")) as GameObject;
temp_GameObject.name = cardDeckBackListNames[1];
temp_GameObject.tag = cardDeckBackListNames[1];
master_GameObject_List.Add(temp_GameObject);
}
These are the results: