MissingReferenceExcepetion after deleting an object - unity3d

I have the following script
using System.Collections;
using UnityEngine;
public class Bullet : MonoBehaviour
{
[SerializeField]
private float _speed;
private IEnumerator ShootAt(Vector3 target)
{
while (transform.position != target)
{
yield return null;
transform.localPosition = Vector3.MoveTowards(transform.localPosition, target, _speed * Time.deltaTime);
}
}
private void OnTriggerEnter2D(Collider2D other)
{
if (other.tag == "Animal")
{
Destroy(gameObject);
}
}
}
After the bullet hits the animal and gets destroyed, I get the following error: MissingReferenceException: The object of type 'Bullet' has been destroyed but you are still trying to access it. Your script should either check if it is null or you should not destroy the object.
The error is referring to the while loop in my code. I guess it's still running that loop because the condition to stop it hasn't been met yet
while(transform.position != target)
I tried adding a second condition to stop that loop, like
while(transform.position != target || gameObject != null)
but that doesn't seem to work. Any help would be more than appreciated.

Apart from some refactoring like another user suggested in the comments, you could try the same logic you have, but reversed:
while(gameObject != null || transform.position != target)
The || operator will be evaluated in order it is written, so in your case the transform.position is still being evaluated before your null check.
Also, you could consider using StopCoroutine prior to calling Destroy on your object.
Finally, as I've also personally had trouble with such a case (perhaps related to this behavior)
The only thing that the C# object has is a pointer to the native object, so Destroy(myGameObject) destroys the native object but the managed object in the C# side still exists, and is destroyed when it's no longer referenced and is collected by the garbage collector.
So you might just consider introducing a isHit member variable and setting it to true upon a successful collision, followed up with your Destroy.

Related

NullReferenceException problem in Unity when object is not null [duplicate]

This question already has answers here:
What is a NullReferenceException, and how do I fix it?
(27 answers)
Closed 3 years ago.
I am trying to troubleshoot the following piece of code which causes a NullReferenceException. Essentially when an object is created, I'm trying to have it register with my game manager class. This is the component for my object:
void Start()
{
Debug.Log("Registering");
if (gameObject != null)
{
GameMngr.Instance.RegisterAttraction(gameObject);
}
else
{
Debug.Log("Gameobject null");
}
}
In my game manager I have the following:
public void RegisterAttraction(GameObject newAttraction)
{
if (newAttraction != null)
{
Debug.Log("Attempting to register gameObject");
attractionLastID++;
sceneAttractions.Add(attractionLastID, newAttraction);
Debug.Log("Registered");
}
else
{
Debug.Log("unable to register: null provided");
}
}
My console output is as following:
Registering
Attempting to register gameObject
NullRefereceException
The fact that my code displays the attempting to register gameObject lines leads me to believe that my newAttraction variable is not null. Why do I get the error ?
Thank for the help
Where's attractionLastId defined by chance? Is it being initialized? Since you're doing a null check inside of your Start function already, try refactoring your RegisterAttraction function to look something like:
public void RegisterAttraction(GameObject newAttraction)
{
Debug.Log("Attempting to register gameObject");
attractionLastID++;
sceneAttractions.Add(attractionLastID, newAttraction);
Debug.Log("Registered");
}
I highly recommend setting a breakpoint on the beginning curly of RegisterAttraction. Take it step by step and hover over each variable to see which one is null.

Serialization of a list of custom objects in unity

While trying to make a script for building assets, I ran into an issue with unity's serialization. I have a class in which I store some arbitrary information, which is then stored in an array in a MonoBehaviour on a prefab. I cannot for the life of me get the array to save however, as when I make the object into a prefab it loses the list's values. I have tried using [System.Serializable] and ScriptableObject, but both seem to pose their own new issues.
For instance, using ScriptableObject would mean having to save the data objects as assets, which would become way too much since these objects can get to hundreds in number.
Am I making a mistake in my understanding of unity's serialization? Is there a way to get this working without the ScriptableObject approach of saving every ArbitraryInfo object in an asset?
Data object:
[System.Serializable]
public class ArbitraryInfo{
public int intValue;
public Vector3 vectorValue;
}
OR
public class ArbitraryInfo : ScriptableObject {
public int intValue;
public Vector3 vectorValue;
void OnEnable() {
hideflags = HideFlags.HideAndDontSave;
}
}
Behaviour:
public class MyBuilder : MonoBehaviour {
public ArbitraryInfo[] infoArray;
}
Editor:
[CustomEditor(typeof(MyBuilder))]
public class MyBuilderEditor : Editor {
private SerializedProperty infoArrayProperty;
void OnLoad() {
infoArrayProperty = serializedObject.FindProperty("infoArray");
}
void OnInspectorGUI() {
serializedObject.Update();
for (var i = 0; i < infoArrayProperty.arraySize; i++) {
if (i > 0) EditorGUILayout.Space();
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
EditorGUILayout.LabelField("Info " + i, EditorStyles.boldLabel);
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
}
serializedObject.ApplyModifiedProperties();
}
}
EDIT 1, Thank you derHugo
I changed my code to incorporate the changes. Now there are errors for ArbitraryInfo not being a supported pptr value.
Secondly, ArbitraryInfo no longer being a ScriptableObject poses the question of how to initialize it. An empty object can be added to infoArrayProperty through infoArrayProperty.arraySize++, but this new empty object seems to be null in my case. This might be due to the pptr issue mentioned above.
EDIT 2
The issue I was having was caused by another piece of code where I tried to check if infoArrayProperty.objectReferenceValue == null. I changed this to another check that did the same thing and everything worked!
No, no ScriptableObject needed.
But note that GetArrayElementAtIndex(i) returns a SerializedProperty. You can not simply parse it to your target class.
so instead of
var info = infoArrayProperty.GetArrayElementAtIndex(i).objectReferenceValue as ArbitraryInfo;
and
info.intValue = EditorGUILayout.IntField(info.intValue);
info.vectorValue = EditorGUILayout.Vector3Field(info.vectorValue);
you have to get the info's SerializedPropertys by using FindPropertyRelative:
var info = infoArrayProperty.GetArrayElementAtIndex(i);
var intValue = info.FindPropertyRelative("intValue");
var vectorValue = info.FindPropertyRelative("vectorValue");
than you can/should use PropertyFields
EditorGUILayout.PropertyField(intValue);
EditorGUILayout.PropertyField(vectorValue);
allways try to avoid using direct setters and use those SerializedProperties instead! This provides you with Undo/Redo functionality and marking the changed Behaviour/Scene as unsaved automatically. Otherwise you would have to tak care of that manually (... don't ^^).

how to access GameObject by instance id in Unity

In Unity, it provides a function to get the instance of GameObject
GetInstanceID ()
While the instance id could differ GameObjects, but the function to get GameObject by instance id
InstanceIDToObject()
is only provided in EditorUtility, which can't use in release.
As far as I think, using HashTable maybe is a method to reach that, but is t here any other method to achieve that?
Solution can be found on Unity forums, I'll quote for convenience.
Not directly, but you can use the InstanceID to rename the object, and then do a GameObject.Find using the InstanceID to access the object that way.
gameObject.name = GetInstanceID().ToString();
var foo = 38375; // A made-up InstanceId...in actual code you'd get this from a real object...you can store the instance ids in an array or something
GameObject.Find(foo.ToString()).transform.position = whatever;
I've also had a hashtable and used the instance ids as keys for the hashtable in order to access each object. You can go through and assign objects an identifier yourself, but since every object has a unique InstanceID() anyway, might as well use it if the situation calls for it.
Source: https://forum.unity3d.com/threads/can-i-use-instanceid-to-access-an-object.12817/
public static GameObject getObjectById(int id)
{
Dictionary<int, GameObject> m_instanceMap = new Dictionary<int, GameObject>();
//record instance map
m_instanceMap.Clear();
List<GameObject> gos = new List<GameObject>();
foreach (GameObject go in Resources.FindObjectsOfTypeAll(typeof(GameObject)))
{
if (gos.Contains(go))
{
continue;
}
gos.Add(go);
m_instanceMap[go.GetInstanceID()] = go;
}
if (m_instanceMap.ContainsKey(id))
{
return m_instanceMap[id];
}
else
{
return null;
}
}

How to get the connection used as parameter in NetworkIdentity.AssignClientAuthority

Weapon is a child object of the player object, the child object Weapon cant be regard as a part of the player object, right? So, I need to use NetworkIdentity.AssignClientAuthority() to give non-player object local authority.
public class Weapon: NetworkBehaviour
{
void Start()
{
// how to get the conn?
GameObject.GetComponent<NetworkIdentity>().AssignClientAuthority(conn);
}
void Update()
{
CmdShot();
}
[Command]
void CmdShot()
{
// shot...
}
}
On the client side you can get the connection using NetworkClient.connection. On the server the list of all current active connections are avaiable in NetworkServer.connections. Both contain the object NetworkConnection which is the one used as a parameter for AssignClientAuthority.
The NetworkIndentity component must on the root gameobject, and it can only have one in a prefab.
So, only the root gameobject have the ClientAuthority to send Command to server.The child object under this gameobject has no ClientAuthority.
And here is more about the NetworkIndentity.

Check if spawn is empty

I have two spawn spots where a player will show up upon connection.
I need that, when one of the players connects on one of the spots, any other player will always spawn at the other spot.
Here's some visual in case it helps: https://goo.gl/Y0ohZC
Here is the code I'm using:
using UnityEngine;
using System.Collections;
public class SpawnSpot : MonoBehaviour {
public int teamId=0;
public GameObject[] Spots; //Drag the spots in here (In the editor)
bool[] OccupiedSpawnSpots;
//Using Photon Networking
void OnJoinedRoom()
{
//Request the recent OccupiedSpawnSpots List
PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
}
//In "RequestList" the MasterClient sends his List of the SpawnSpots
//by calling "ReceiveList"
[RPC]
void RequestList(PhotonPlayer player)
{
PhotonView.RPC("ReceiveList", PhotonTargets.All, player, OccupiedSpawnSpots);
}
[RPC]
void ReceiveList(PhotonPlayer Sender, bool[] ListOfMasterClient)
{
OccupiedSpawnSpots = ListOfMasterClient;
//Get the free one
if (OccupiedSpawnSpots[0] == false)
{
//Spawn player at 0
if (Sender == PhotonNetwork.player)
PhotonNetwork.Instantiate("PlayerController", Spots[0].transform.position);
OccupiedSpawnSpots[0] = true;
}
else
{
//Spawn player at 1
if (Sender == PhotonNetwork.player)
PhotonNetwork.Instantiate("PlayerController", Spots[1].transform.position);
OccupiedSpawnSpots[1] = true;
}
}
The errors given are:
Assets/Scripts/SpawnSpot.cs(14,28): error CS0120: An object reference
is required to access non-static member `PhotonView.RPC(string,
PhotonPlayer, params object[])'
Assets/Scripts/SpawnSpot.cs(22,28): error CS0120: An object reference
is required to access non-static member `PhotonView.RPC(string,
PhotonPlayer, params object[])'
Assets/Scripts/SpawnSpot.cs(36,47): error CS1501: No overload for
method Instantiate' takes3' arguments
Assets/Scripts/SpawnSpot.cs(43,47): error CS1501: No overload for
method Instantiate' takes3' arguments
Thanks in advance, IC
It appears that you are trying to call an instance function using a static reference. instead of doing PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
you need to create a reference to an PhatorView object, and then call the RPC function on it.
public PhotonView photonView;
void OnJoinedRoom()
{
if(photonView == null)
{
photonView = GetComponent<PhotonView>();
}
//Request the recent OccupiedSpawnSpots List
PhotonView.RPC("RequestList", PhotonTargets.MasterClient, PhotonNetwork.player);
}
you should have a PhotonView object on the same GameObject that this script is on, or assign a reference to a PhotonView in the editor.
That should fix your problems, but I think you should look into compiler errors and how to to fix them. In Unity3D if you double click the error in the console it will take you to the line that isn't compiling. It also tends to give you a pretty good hint as to why it isn't compiling.
your error was this
"Assets/Scripts/SpawnSpot.cs(14,28): error CS0120: An object reference is required to access non-static member `PhotonView.RPC(string, PhotonPlayer, params object[])'"
which means that you need an object in order to call this function.
The third and fourth errors are because you are calling Instantiate with only 2 arguments, but it takes at least 4. Include the rotation of the GameObject you're trying to instantiate, and a group number:
PhotonNetwork.Instantiate("PlayerController", Spots[0].transform.position, Quaternion.identity, 0);
Note that you may not want the identity quaternion, so you may need to change this. Also I'm not familiar with PhotonNetwork so 0 may not be an advisable group.