NullReferenceException en Unity

11

Dado que muchos usuarios se enfrentan al NullReferenceException: Object reference not set to an instance of an objecterror en Unity, pensé que sería una buena idea reunir algunas fuentes y explicaciones para solucionar este error.


Síntomas

Me aparece el siguiente error en mi consola, ¿qué significa y cómo lo soluciono?

NullReferenceException: referencia de objeto no establecida en una instancia de un objeto

Hellium
fuente
Esto parece una pregunta de programación general y no específica del desarrollador del juego. La respuesta de OP a su propia pregunta incluye un enlace a SO que cubre este tema.
Pikalek
3
Si bien la "NullReferenceException" es, de hecho, una pregunta de programación general, aquí, la pregunta cubre específicamente la excepción en Unity : dónde se puede encontrar en la programación de Unity y cómo resolverlos (ver los diversos ejemplos).
Hellium
@Pikalek, también hemos ampliado nuestro alcance para lo que permitimos en términos de programación general. Esto se aclaró cuando lo pregunté en meta . Ahora me doy cuenta de que esto aún podría ajustarse a los parámetros de "demasiado genérico", según la respuesta de Josh.
Gnemlock
La respuesta actual tampoco menciona nada específico de Unity (aparte de usar tipos específicos de Unity en los ejemplos). Es, de hecho, una respuesta de programación genérica. No usamos respuestas en argumentos cercanos, pero dado que es una respuesta propia, sí sirve para apoyar el argumento de intención.
Gnemlock
3
Unity tiene algunas formas únicas / características para desencadenar estos errores, a través de campos de Inspector no asignados, intentos fallidos de GetComponent o Find, o a través de su variante sabor "MissingReferenceException" cuando tenía una referencia válida pero obtuvo Destroy () ed. Entonces, creo que las respuestas a esta pregunta en el contexto de Unity tienen un buen potencial para ser útiles para la comunidad, incluso si la Excepción en sí es muy general.
DMGregory

Respuestas:

14

Tipo de valor vs tipo de referencia

En muchos lenguajes de programación, las variables tienen lo que se llama un "tipo de datos". Los dos tipos de datos principales son los tipos de valor (int, float, bool, char, struct, ...) y el tipo de referencia (instancia de clases). Mientras que los tipos de valor contienen el valor en sí , las referencias contienen una dirección de memoria que apunta a una parte de la memoria asignada para contener un conjunto de valores (similar a C / C ++).

Por ejemplo, Vector3es un tipo de valor (una estructura que contiene las coordenadas y algunas funciones) mientras que los componentes adjuntos a su GameObject (incluidos sus scripts personalizados que heredan MonoBehaviour) son de tipo de referencia.

¿Cuándo puedo tener una NullReferenceException?

NullReferenceException se lanzan cuando intenta acceder a una variable de referencia que no hace referencia a ningún objeto, por lo tanto, es nula (la dirección de memoria apunta a 0).

Se NullReferenceExceptionplantearán algunos lugares comunes a :

Manipular un GameObject / Component que no se haya especificado en el inspector

// t is a reference to a Transform.
public Transform t ;

private void Awake()
{
     // If you do not assign something to t
     // (either from the Inspector or using GetComponent), t is null!
     t.Translate();
}

Recuperando un componente que no está conectado al GameObject y luego, tratando de manipularlo:

private void Awake ()
{
    // Here, you try to get the Collider component attached to your gameobject
    Collider collider = gameObject.GetComponent<Collider>();

    // But, if you haven't any collider attached to your gameobject,
    // GetComponent won't find it and will return null, and you will get the exception.
    collider.enabled = false ;
}

Acceder a un GameObject que no existe:

private void Start()
{
    // Here, you try to get a gameobject in your scene
    GameObject myGameObject = GameObject.Find("AGameObjectThatDoesntExist");

    // If no object with the EXACT name "AGameObjectThatDoesntExist" exist in your scene,
    // GameObject.Find will return null, and you will get the exception.
    myGameObject.name = "NullReferenceException";
}

Nota: Tenga cuidado, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypedevolver sólo GameObjects que están habilitadas en la jerarquía cuando se invoca la función.

Intentando usar el resultado de un getter que está regresando null:

var fov = Camera.main.fieldOfView;
// main is null if no enabled cameras in the scene have the "MainCamera" tag.

var selection = EventSystem.current.firstSelectedGameObject;
// current is null if there's no active EventSystem in the scene.

var target = RenderTexture.active.width;
// active is null if the game is currently rendering straight to the window, not to a texture.

Acceder a un elemento de una matriz no inicializada

private GameObject[] myObjects ; // Uninitialized array

private void Start()
{
    for( int i = 0 ; i < myObjects.Length ; ++i )
        Debug.Log( myObjects[i].name ) ;
}

Menos común, pero molesto si no lo sabe sobre los delegados de C #:

delegate double MathAction(double num);

// Regular method that matches signature:
static double Double(double input)
{
    return input * 2;
}

private void Awake()
{
    MathAction ma ;

    // Because you haven't "assigned" any method to the delegate,
    // you will have a NullReferenceException
    ma(1) ;

    ma = Double ;

    // Here, the delegate "contains" the Double method and
    // won't throw an exception
    ma(1) ;
}

Como arreglar ?

Si ha entendido los párrafos anteriores, sabe cómo corregir el error: asegúrese de que su variable haga referencia (señale) a una instancia de una clase (o que contenga al menos una función para delegados).

¿Es más fácil decirlo que hacerlo? Si, de hecho. Aquí hay algunos consejos para evitar e identificar el problema.

La forma "sucia": el método try & catch:

Collider collider = gameObject.GetComponent<Collider>();

try
{
    collider.enabled = false ;
}       
catch (System.NullReferenceException exception) {
    Debug.LogError("Oops, there is no collider attached", this) ;
}

La forma "más limpia" (en mi humilde opinión): el cheque

Collider collider = gameObject.GetComponent<Collider>();

if(collider != null)
{
    // You can safely manipulate the collider here
    collider.enabled = false;
}    
else
{
    Debug.LogError("Oops, there is no collider attached", this) ;
}

Cuando enfrenta un error que no puede resolver, siempre es una buena idea encontrar la causa del problema. Si es "flojo" (o si el problema se puede resolver fácilmente), use Debug.Logpara mostrar en la consola información que lo ayudará a identificar qué podría causar el problema. Una forma más compleja es utilizar los puntos de interrupción y el depurador de su IDE.

El uso Debug.Loges bastante útil para determinar qué función se llama primero, por ejemplo. Especialmente si tiene una función responsable de inicializar campos. Pero no olvide eliminarlos Debug.Logpara evitar abarrotar su consola (y por razones de rendimiento).

Otro consejo, no dude en "cortar" sus llamadas a funciones y agregarlas Debug.Logpara hacer algunas verificaciones.

En lugar de :

 GameObject.Find("MyObject").GetComponent<MySuperComponent>().value = "foo" ;

Haga esto para verificar si todas las referencias están establecidas:

GameObject myObject = GameObject.Find("MyObject") ;

Debug.Log( myObject ) ;

MySuperComponent superComponent = myObject.GetComponent<MySuperComponent>() ;

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Aun mejor :

GameObject myObject = GameObject.Find("MyObject") ;

if( myObject != null )
{
   MySuperComponent superComponent = myObject.GetComponent<MySuperComponent>() ;
   if( superComponent != null )
   {
       superComponent.value = "foo" ;
   }
   else
   {
        Debug.Log("No SuperComponent found onMyObject!");
   }
}
else
{
   Debug.Log("Can't find MyObject!", this ) ;
}

Fuentes:

  1. http://answers.unity3d.com/questions/47830/what-is-a-null-reference-exception-in-unity.html
  2. /programming/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it/218510#218510
  3. https://support.unity3d.com/hc/en-us/articles/206369473-NullReferenceException
  4. https://unity3d.com/fr/learn/tutorials/topics/scripting/data-types
Hellium
fuente
Esto requiere mucho esfuerzo para explicar el "cómo" diagnosticar el problema. No consideraría una respuesta real a la pregunta "cuál es el problema". Esto tampoco logra abordar las respuestas que generalmente aparecen en este tipo de preguntas. ¿Quizás esto sería mejor en la documentación de StackOverflow? Talvez no.
Gnemlock
2
No diría que usar el registro de depuración es vago . Para mí, es mucho más rápido usar el debug.log para reducir el alcance de dónde está ocurriendo el error, luego usar el depurador para encontrar realmente el error. Pero siempre depende del error en cuestión. En cualquier caso, no diría que usar el registro de depuración es vago : P
Vaillancourt
También debería haber señalado que no siempre es una buena idea poner cheques para nulo. Incluso peor idea sería usar try/catch. El error le dice mucho sobre el problema que tiene allí, y antes de que los principiantes comiencen a poner cheques nulos en todas partes, su problema principal está en el inspector, ya que olvida hacer referencia a algún objeto (arrastre el objeto al script). He visto mucho código try/catchy comprobaciones nulas en lugares donde es totalmente innecesario. La depuración y el trabajo con un código como ese es "una molestia en el a **". Los principiantes aprenden sobre los casos de uso de esos controles y solo luego los usan.
Candid Moon _Max_
Creo que tener una comprobación nula puede ser una buena idea si se proporciona un mensaje de depuración explícito en el else. Tener un NullReferenceExceptionno siempre se explica por sí mismo mientras No Rigidbody component attached to the gameObjectexplica directamente lo que está mal. Estoy de acuerdo en que solo tener el if( obj != null )mensaje sin "oculta" el problema, y ​​puede tener un proyecto que funcione pero no hacer lo que esperaría sin saber por qué.
Hellium
4

Si bien podemos simplemente hacer una verificación para asegurarnos de que no estamos tratando de acceder a una referencia nula, esta no siempre es una solución adecuada. Muchas veces, en la programación de Unity, nuestro problema puede derivarse del hecho de que la referencia no debe ser nula. En algunas situaciones, simplemente ignorar referencias nulas puede romper nuestro código.

Por ejemplo, podría ser una referencia a nuestro controlador de entrada. Es genial que el juego no se bloquee debido a la excepción de referencia nula, pero necesitamos descubrir por qué no hay un controlador de entrada y solucionar ese problema. Sin él, tenemos un juego que puede no bloquearse, pero tampoco puede recibir información.

A continuación, enumeraré posibles razones y soluciones, a medida que las encuentre en otras preguntas.


¿Estás tratando de acceder a una clase de "gerente"?

Si está intentando acceder a una clase que actúa como un "administrador" (es decir, una clase que solo debería tener una instancia ejecutándose a la vez), es mejor que utilice el enfoque Singleton . Se puede acceder idealmente a una clase Singleton desde cualquier lugar, directamente, manteniendo una public staticreferencia a sí misma. De esta manera, un Singleton puede contener una referencia a la instancia activa, que sería accesible sin la molestia de configurar la referencia real cada vez.

¿Estás haciendo referencia a la instancia de tu objeto?

Es común simplemente marcar una referencia como public, por lo que podemos establecer la referencia a la instancia a través del inspector. Compruebe siempre que haya establecido la referencia a una instancia, a través del inspector, ya que no es raro que se salte este paso.

¿Estás instanciando tu instancia?

Si estamos configurando nuestro objeto en código, es importante asegurarse de crear una instancia del objeto. Esto puede llevarse a cabo utilizando la newpalabra clave y los métodos de construcción. Por ejemplo, considere lo siguiente:

private GameObject gameObject;

Hemos creado una referencia a a GameObject, pero no apunta a nada. Acceder a esta referencia como está dará como resultado una excepción de referencia nula . Antes de hacer referencia a nuestra GameObjectinstancia, podemos llamar a un método de constructor predeterminado de la siguiente manera:

gameObject = new GameObject();

El tutorial de Unity sobre clases explica la práctica de crear y usar constructores.

¿Está utilizando el GetComponent<t>()método con el supuesto de que el componente existe?

Primero, asegúrese de que siempre llamemos GetComponent<t>()antes de llamar a los métodos desde la instancia del componente.

Por razones por las que no vale la pena entrar, podemos suponer que nuestro objeto de juego local contiene un componente en particular y tratar de acceder a él GetComponent<t>(). Si el objeto del juego local no contiene ese componente en particular, devolveremos un nullvalor.

Puede verificar fácilmente si el valor de retorno es null, antes de acceder a él. Sin embargo, si su objeto de juego debe tener el componente requerido, puede ser mejor asegurarse de que al menos tenga una versión predeterminada de ese componente. Podemos etiquetar una MonoBehaviourcomo [RequireComponent(typeof(t))]para asegurar que siempre tenemos ese tipo de componente.

Aquí hay un ejemplo de un MonoBehaviourobjeto para un juego que siempre debe contener un Rigidbody. Si el script se agrega a un objeto del juego que no contiene un Rigidbody, se Rigidbodycreará un valor predeterminado .

[RequireComponent(typeof(Rigidbody))]
public class AlwaysHasRigidbody : MonoBehaviour
{
    Rigidbody myRigidbody;


    void Start()
    {
        myRigidbody = GetComponent<Rigidbody>();
    }
}

¿Has intentado reconstruir tu proyecto?

Hay algunos casos en los que Unity puede causar problemas al intentar hacer referencia a una versión en caché de un objeto de juego. En línea con la antigua solución de "apagar y volver a encender", intente eliminar su carpeta Biblioteca y vuelva a abrir Unity. Unity se verá obligado a reconstruir su proyecto. Esto puede resolver algunos casos muy peculiares de este problema, y ​​debería apuntar a problemas que no aparecerían en una compilación final.

Gnemlock
fuente
1
Todavía no estoy seguro de si esta pregunta debería estar en el tema. Pero aquí hay una wiki comunitaria para que los usuarios publiquen posibles respuestas adicionales; Hasta ahora se compone de los conceptos básicos de la primera media página de respuestas aceptadas para preguntas marcadas como unidad y "referencia nula" (que en realidad cumplían con los criterios de la pregunta).
Gnemlock
-5

Veo que hay una respuesta aceptada. Pero, hay una mejor respuesta o sugerencia para usted para el manejo NullReferenceException. Si puede relacionar la programación en lenguaje Java como yo, puede evitar enviar un error nulo utilizando el try-catchbloque. Pruébalo por ti mismo! ;-)

Si está utilizando en C #, verifique si tiene using System;en la parte superior de su archivo de script. Si no, agréguelo. Ahora, puede usar todo tipo de Exceptionclases mientras intenta atrapar una línea de código.

Si está utilizando UnityScript, use import System;

Aquí hay un ejemplo:

using System; // --> This exact line of code. That's it.
using UnityEngine;

public class Test : MonoBehaviour {

    public GameObject player; // --> Example to check if there's a null content;

    public void Update() {

        // You may now catch null reference here.
        try {

            player.transform.Translate(0, 0, 2);

        } catch(NullReferenceException e) { // --> You may use this type of exception class

        }

    }
}

También recuerde, se puede tomar también otras excepciones como MissingReferenceException, MissingComponentException, IndexOutOfRangeException, o cualquier otra clase de excepción, siempre y cuando usted incluye using Systemen su guión.

Eso es todo.

David Dimalanta
fuente
2
El método try & catch se ha descrito en la respuesta aceptada ...
Hellium