NullReferenceException no Unity

11

Como muitos usuários estão enfrentando o NullReferenceException: Object reference not set to an instance of an objecterro no Unity, pensei que seria uma boa idéia reunir de várias fontes algumas explicações e maneiras de corrigir esse erro.


Sintomas

Estou recebendo o erro abaixo que aparece no meu console, o que significa e como faço para corrigi-lo?

NullReferenceException: referência de objeto não definida para uma instância de um objeto

Hellium
fonte
Parece uma questão de programação geral e não específica do desenvolvedor de jogos. A resposta do OP à própria pergunta inclui um link para o SO que aborda este tópico.
Pikalek
3
Embora a "NullReferenceException" seja, de fato, uma questão geral de programação, aqui, a questão cobre especificamente a exceção no Unity : onde pode ser encontrada na programação do Unity e como resolvê-las (veja os vários exemplos).
Hellium 02/02
@Pikalek, também ampliamos nosso escopo para o que permitimos em termos de programação geral. Isso foi esclarecido quando perguntei sobre isso na meta . Agora percebo que isso ainda pode se encaixar nos parâmetros de 'genérico demais', conforme a resposta de Josh.
Gnemlock 02/02
A resposta atual também não faz anotações em nada específico do Unity (além de usar tipos específicos do Unity em exemplos). É, de fato, uma resposta genérica de programação. Não usamos respostas em argumentos próximos, mas, como se trata de uma auto-resposta, ela serve para apoiar o argumento da intenção.
Gnemlock 02/02
3
O Unity possui algumas maneiras únicas / características de acionar esses erros, por meio de campos não atribuídos do Inspetor, falhas nas tentativas de GetComponent ou Find, ou por meio de seu tipo variável "MissingReferenceException" quando você tinha uma referência válida, mas foi destruída (). Então, acho que as respostas a essa pergunta no contexto do Unity têm um bom potencial para serem úteis à comunidade, mesmo que a Exceção em si seja muito geral.
DMGregory

Respostas:

14

Tipo de valor versus tipo de referência

Em muitas linguagens de programação, as variáveis ​​têm o que é chamado de "tipo de dados". Os dois tipos de dados primários são os tipos de valor (int, float, bool, char, struct, ...) e o tipo de referência (instância de classes). Enquanto os tipos de valor contêm o próprio valor , as referências contêm um endereço de memória apontando para uma parte da memória alocada para conter um conjunto de valores (semelhante ao C / C ++).

Por exemplo, Vector3é um tipo de valor (uma estrutura que contém as coordenadas e algumas funções), enquanto os componentes anexados ao seu GameObject (incluindo seus scripts personalizados herdados de MonoBehaviour) são do tipo de referência.

Quando posso ter uma NullReferenceException?

NullReferenceException são lançadas quando você tenta acessar uma variável de referência que não faz referência a nenhum objeto, portanto é nula (o endereço da memória está apontando para 0).

Alguns lugares comuns a NullReferenceExceptionserão levantados:

Manipulando um GameObject / Component que não foi especificado no inspetor

// 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 um componente que não está anexado ao GameObject e, em seguida, tentando manipulá-lo:

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 ;
}

Acessando um GameObject que não 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: Tenha cuidado, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTyperetornar apenas GameObjects que são habilitados na hierarquia quando a função é chamada.

Tentando usar o resultado de um getter que está retornando 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.

Acessando um elemento de uma matriz não inicializada

private GameObject[] myObjects ; // Uninitialized array

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

Menos comum, mas irritante se você não souber sobre os 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 consertar ?

Se você entendeu os parágrafos anteriores, sabe como corrigir o erro: verifique se sua variável está referenciando (apontando para) uma instância de uma classe (ou contendo pelo menos uma função para delegados).

Mais fácil falar do que fazer? Sim, de fato. Aqui estão algumas dicas para evitar e identificar o problema.

A maneira "suja": o 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) ;
}

O caminho "mais limpo" (IMHO): A verificação

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) ;
}

Ao enfrentar um erro que você não pode resolver, é sempre uma boa idéia encontrar a causa do problema. Se você é "preguiçoso" (ou se o problema pode ser resolvido facilmente), use Debug.Logpara mostrar no console informações que ajudarão você a identificar o que poderia causar o problema. Uma maneira mais complexa é usar os pontos de interrupção e o depurador do seu IDE.

O uso Debug.Logé bastante útil para determinar qual função é chamada primeiro, por exemplo. Especialmente se você tiver uma função responsável pela inicialização dos campos. Mas não se esqueça de removê-los Debug.Logpara evitar sobrecarregar seu console (e por razões de desempenho).

Outro conselho, não hesite em "cortar" suas chamadas de função e adicionar Debug.Logpara fazer algumas verificações.

Ao invés de :

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

Faça isso para verificar se todas as referências estão definidas:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Melhor ainda :

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 ) ;
}

Fontes:

  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-fixfix-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
fonte
Isso exige muito esforço para explicar o "como fazer" do diagnóstico do problema. Eu não consideraria que uma resposta real para a pergunta "qual é o problema". Isso também falha ao abordar as respostas que normalmente aparecem nesse tipo de pergunta. Talvez isso fosse melhor na documentação do StackOverflow? Talvez não.
Gnemlock 02/02
2
Eu não diria que o uso do log de depuração é preguiçoso . Para mim, é muito mais rápido usar o debug.log para restringir o escopo de onde o erro está ocorrendo e, em seguida , use o depurador para realmente encontrar o erro. Mas sempre depende do erro em questão. De qualquer forma, eu não diria que o uso do log de depuração é preguiçoso : P
Vaillancourt
Você também deve ter apontado que nem sempre é uma boa idéia colocar cheques para null. Pior ainda seria usar try/catch. O erro diz muito sobre o problema que você tem lá e, antes que os iniciantes comecem a verificar nulos em todos os lugares, o principal problema está no inspetor, pois você esquece de fazer referência a algum objeto (arraste o objeto para o script). Eu já vi muitos códigos com try/catche verificações nulas em lugares onde é totalmente desnecessário. Depurar e trabalhar com um código como esse é "uma dor no a **". Os iniciantes aprendem sobre os casos de uso dessas verificações e só então os usam.
Candid Moon _Max_
Eu acho que ter uma verificação nula pode ser uma boa idéia se uma mensagem de depuração explícita for fornecida no else. Ter um NullReferenceExceptionnem sempre é auto-explicativo, enquanto No Rigidbody component attached to the gameObjectexplica diretamente o que está errado. Concordo que apenas ter a if( obj != null )mensagem sem apenas "oculta" o problema e você pode ter um projeto funcionando, mas não fazendo o que esperaria sem saber o porquê.
Hellium
4

Embora possamos facilmente fazer uma verificação para garantir que não estamos tentando acessar uma referência nula, essa nem sempre é uma solução adequada. Muitas vezes, na programação do Unity, nosso problema pode derivar do fato de que a referência não deve ser nula. Em algumas situações, simplesmente ignorar referências nulas pode quebrar nosso código.

Por exemplo, pode ser uma referência ao nosso controlador de entrada. É ótimo que o jogo não trava devido à exceção de referência nula, mas precisamos descobrir por que não há um controlador de entrada e corrigir esse problema. Sem ele, temos um jogo que pode não travar, mas também não pode receber informações.

Abaixo, listarei possíveis razões e soluções, conforme as encontro em outras perguntas.


Você está tentando acessar uma classe "gerente"?

Se você está tentando acessar uma classe que age como um "gerente" (ou seja, uma classe que só deve ter uma instância em execução por vez), é melhor usar a abordagem Singleton . Idealmente, uma classe Singleton pode ser acessada de qualquer lugar, diretamente, mantendo uma public staticreferência a si mesma. Dessa maneira, um Singleton pode conter uma referência à instância ativa, que seria acessível sem o problema de configurar a referência real todas as vezes.

Você está fazendo referência à instância do seu objeto?

É comum simplesmente marcar uma referência como public, para que possamos definir a referência para a instância por meio do inspetor. Sempre verifique que você definir a referência a uma instância, através do inspector, como não é incomum perder esta etapa.

Você está instanciando sua instância?

Se estivermos configurando nosso objeto no código, é importante garantir que instanciamos o objeto. Isso pode ser realizado usando a newpalavra - chave e os métodos construtores. Por exemplo, considere o seguinte:

private GameObject gameObject;

Criamos uma referência a GameObject, mas não aponta para nada. O acesso a essa referência como está resultará em uma exceção de referência nula . Antes de referenciarmos nossa GameObjectinstância, podemos chamar um método construtor padrão da seguinte maneira:

gameObject = new GameObject();

O tutorial do Unity sobre classes explica a prática de criar e usar construtores.

Você está usando o GetComponent<t>()método com a suposição de que o componente existe?

Primeiro, verifique se sempre chamamos GetComponent<t>()antes de chamar métodos da instância do componente.

Por motivos que não valem a pena, podemos assumir que nosso objeto de jogo local contém um componente específico e tentar acessá-lo GetComponent<t>(). Se o objeto do jogo local não contiver esse componente específico, retornaremos um nullvalor.

Você pode facilmente verificar se o valor retornado é nullantes de acessá-lo. No entanto, se o seu objeto de jogo deve ter o componente necessário, pode ser melhor para garantir que pelo menos tem um padrão versão desse componente. Podemos marcar um MonoBehaviourcomo [RequireComponent(typeof(t))]para garantir que sempre tem esse tipo de componente.

Aqui está um exemplo de a MonoBehaviourpara um objeto de jogo que deve sempre conter a Rigidbody. Se o script for adicionado a um objeto de jogo que não contenha a Rigidbody, um padrão Rigidbodyserá criado.

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


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

Você tentou recriar seu projeto?

Existem alguns casos em que o Unity pode causar problemas ao tentar fazer referência a uma versão em cache de um objeto de jogo. De acordo com a solução antiga "desligue e ligue novamente", tente excluir a pasta Biblioteca e abra novamente o Unity. A unidade será forçada a reconstruir seu projeto. Isso pode resolver algumas instâncias muito peculiares desse problema e deve apontar para problemas que não surgiriam na versão final.

Gnemlock
fonte
11
Ainda não tenho certeza se esta questão deve estar no tópico. Mas aqui está um wiki da comunidade para os usuários postarem possíveis respostas adicionais; Até o momento, ele é composto dos princípios básicos da primeira meia página de respostas aceitas para perguntas marcadas como unidade e "referência nula" (que realmente atendiam aos critérios da pergunta).
Gnemlock 02/02
-5

Vejo que há uma resposta aceita. Mas, há uma resposta ou sugestão melhor para você manipular NullReferenceException. Se você pode relacionar a programação na linguagem Java como eu, pode impedir o envio de um erro nulo usando o try-catchbloco Experimente você mesmo! ;-)

Se você estiver usando C #, verifique se possui using System;a parte superior do seu arquivo de script. Caso contrário, adicione-o. Agora, você pode usar todos os tipos de Exceptionclasses enquanto tenta capturar uma linha de código.

Se você estiver usando o UnityScript, use import System;

Aqui está um exemplo:

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

        }

    }
}

Também lembre-se, você pode pegar também outras exceções, como MissingReferenceException, MissingComponentException, IndexOutOfRangeException, ou quaisquer outras classes de exceção, desde que você incluir using Systemno seu script.

Isso é tudo.

David Dimalanta
fonte
2
O método de tentativa e captura foi descrita na resposta aceita ....
hellium