Voltar para o Blog
Quest Log

MissingReferenceException na Unity: Objeto Destruído

Fio de conexão brilhante ligado a um espaço vazio onde antes havia um objeto de jogo, com fragmentos se desfazendo

Unity MissingReferenceException explicada: objeto destruído ainda referenciado, eventos sem remover, corrotina e async após Destroy, e por que ?. não funciona.

Ferramenta:UnityC#

MissingReferenceException na Unity: Objeto Destruído

A Unity MissingReferenceException aparece quando o seu código acessa um objeto que já foi destruído: um inimigo que morreu, uma UI da cena anterior, um projétil que bateu na parede. A variável continua apontando para ele, só que o objeto não existe mais na engine. A correção é parar de usar a referência depois do Destroy (removendo da lista, cancelando o evento, interrompendo a corrotina) e, quando precisar checar, usar == null do jeito da Unity, nunca ?..

A mensagem completa no Console é parecida com esta:

MissingReferenceException: The object of type 'GameObject' 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.

Abaixo estão as três causas que geram quase todos os casos, com o código que corrige cada uma no Unity 6.3 LTS.

O que é MissingReferenceException na Unity

Todo objeto da Unity (GameObject, Transform, componentes, materiais) tem duas metades: o objeto C# que o seu script segura e o objeto nativo, em C++, que a engine usa para desenhar, simular física e tocar som. Quando você chama Destroy, a Unity apaga a metade nativa. A metade C# fica viva enquanto algum script tiver referência para ela.

A documentação de UnityEngine.Object chama esse estado de "detached": a variável não é null em C#, mas a Unity considera o objeto morto. Qualquer acesso que precise da metade nativa (transform.position, gameObject.SetActive, GetComponent) lança a MissingReferenceException.

Ela também aparece no editor, sem você rodar código seu: um campo no Inspector mostrando Missing (GameObject) em vez de None é uma referência serializada para algo que foi apagado da cena ou do projeto. Arraste o objeto certo de novo ou limpe o campo.

Diferença para NullReferenceException

As duas parecem iguais e têm causas opostas:

NullReferenceExceptionMissingReferenceException
A variável recebeu um objeto?Não, está vaziaSim
O que aconteceu?Esqueceu de atribuir, ou GetComponent não achouO objeto foi destruído depois
Inspector mostraNoneMissing
CorreçãoAtribuir a referênciaParar de usar depois do Destroy

Se você está vendo a NullReferenceException, o problema é de atribuição, e o caminho é outro: o guia de NullReferenceException na Unity cobre esse caso.

Lendo o stack trace para achar o culpado

Antes de corrigir, descubra qual referência morreu e quem tentou usar. A mensagem já traz metade da resposta: o tipo entre aspas ('GameObject', 'Transform', 'SpriteRenderer') diz que tipo de objeto foi destruído. Clique uma vez no erro no Console e olhe o painel de baixo, o stack trace. Ele lista as chamadas de baixo para cima, e a primeira linha com um arquivo seu (Assets/Scripts/...) é onde o acesso aconteceu.

Três perguntas resolvem a maioria dos casos a partir dessa linha:

  1. Quem destruiu o objeto? Procure Destroy( no projeto e nos scripts do próprio objeto. Troca de cena também destrói tudo que não está marcado com DontDestroyOnLoad.
  2. Quem ainda guarda a referência? É o script da linha do stack trace. Veja de onde veio aquela variável: um campo serializado, uma lista, um parâmetro de evento.
  3. Por que esse script ainda está rodando? Um evento que não foi removido, uma corrotina em outro objeto, um async sem cancelamento, ou um objeto persistente entre cenas.

Se o erro acontece em rajada e você não consegue ver o momento, ligue o Error Pause no Console. Ele pausa o jogo quando um erro é registrado, e você inspeciona a Hierarchy e o Inspector no exato quadro do problema. Para saber qual objeto da cena está envolvido, passe o próprio objeto como contexto no log: Debug.Log("Mirando", this). Clicar na mensagem destaca o objeto na Hierarchy.

Causa 1: referência para objeto destruído

O caso mais comum é uma lista ou um campo que guarda um objeto que morreu. Um sistema de mira guarda o inimigo mais próximo, o inimigo leva o último tiro e chama Destroy(gameObject), e no quadro seguinte a mira tenta ler alvo.transform.position.

A correção mais robusta é o objeto se registrar e se remover sozinho, em vez de alguém de fora tentar adivinhar se ele ainda existe.

Arquivo Enemy.cs:

using System.Collections.Generic;
using UnityEngine;

public class Enemy : MonoBehaviour
{
    private static readonly List<Enemy> active = new List<Enemy>();

    public static IReadOnlyList<Enemy> Active => active;

    private void OnEnable()
    {
        active.Add(this);
    }

    private void OnDisable()
    {
        active.Remove(this);
    }
}

OnDisable roda quando o objeto é desativado e também quando é destruído, então a lista nunca guarda inimigo morto. Quem precisa dos inimigos lê Enemy.Active e sempre recebe objetos vivos.

Arquivo Turret.cs:

using System.Collections.Generic;
using UnityEngine;

public class Turret : MonoBehaviour
{
    [SerializeField] private float range = 8f;

    private Enemy target;

    private void Update()
    {
        if (target == null)
        {
            target = FindClosestEnemy();
        }

        if (target != null)
        {
            Vector3 direction = target.transform.position - transform.position;
            transform.up = direction.normalized;
        }
    }

    private Enemy FindClosestEnemy()
    {
        Enemy closest = null;
        float bestDistance = range;
        IReadOnlyList<Enemy> enemies = Enemy.Active;

        for (int i = 0; i < enemies.Count; i++)
        {
            float distance = Vector3.Distance(transform.position, enemies[i].transform.position);
            if (distance < bestDistance)
            {
                bestDistance = distance;
                closest = enemies[i];
            }
        }

        return closest;
    }
}

O target == null no começo do Update usa o == sobrecarregado da Unity, que devolve true também para objeto destruído. É o check que funciona.

Causa 2: eventos e listeners não removidos

Eventos C# guardam referência para quem se inscreveu. Se a UI de vida se inscreve num evento estático do jogador e a cena troca, a UI é destruída, mas o evento ainda guarda o método dela. Na próxima vez que o jogador leva dano, o evento chama um método num objeto morto, e o label.text explode.

Arquivo PlayerHealth.cs:

using System;
using UnityEngine;

public class PlayerHealth : MonoBehaviour
{
    public static event Action<int> HealthChanged;

    [SerializeField] private int maxHealth = 5;

    private int currentHealth;

    private void Awake()
    {
        currentHealth = maxHealth;
    }

    public void TakeDamage(int amount)
    {
        currentHealth = Mathf.Max(currentHealth - amount, 0);
        HealthChanged?.Invoke(currentHealth);
    }
}

Arquivo HealthLabel.cs:

using TMPro;
using UnityEngine;

public class HealthLabel : MonoBehaviour
{
    [SerializeField] private TextMeshProUGUI label;

    private void OnEnable()
    {
        PlayerHealth.HealthChanged += OnHealthChanged;
    }

    private void OnDisable()
    {
        PlayerHealth.HealthChanged -= OnHealthChanged;
    }

    private void OnHealthChanged(int value)
    {
        label.text = value.ToString();
    }
}

A regra: todo += tem um -= simétrico. Inscreveu no OnEnable, remove no OnDisable. Inscreveu no Start, remove no OnDestroy. O mesmo vale para UnityEvent.AddListener e RemoveListener, e para eventos de pacotes como o Input System (action.performed). Repare que o HealthChanged?.Invoke é seguro: ali o ?. está num delegate C#, não num objeto da Unity.

Esse tipo de bug é o que aparece quando o projeto cresce: com uma cena, tudo funciona; com menu, fases e UI persistente, as referências começam a se cruzar. No curso de Unity você monta um jogo completo com troca de cena, UI e eventos organizados desde o início, para esse erro não virar rotina.

Curso de Unity
Quer fazer seu primeiro jogo na Unity?

No curso de Unity 6 da CursoGame.Dev você vai do zero ao primeiro jogo, com um desafio prático em cada level e suporte individual no WhatsApp quando travar.

Conhecer o curso de Unity
Suporte no WhatsAppAcesso vitalícioGarantia de 7 dias

Causa 3: corrotinas e Invoke depois do Destroy

Uma corrotina para quando o objeto que a iniciou é destruído ou desativado. O problema aparece quando a corrotina roda num objeto e mexe em outro: um gerenciador pisca o inimigo de vermelho, espera 0,1 segundo e volta a cor, mas o inimigo morreu nesse meio tempo.

Arquivo HitFlash.cs:

using System.Collections;
using UnityEngine;

public class HitFlash : MonoBehaviour
{
    [SerializeField] private float flashSeconds = 0.1f;

    public void Flash(SpriteRenderer target)
    {
        StartCoroutine(FlashRoutine(target));
    }

    private IEnumerator FlashRoutine(SpriteRenderer target)
    {
        target.color = Color.red;
        yield return new WaitForSeconds(flashSeconds);

        if (target == null)
        {
            yield break;
        }

        target.color = Color.white;
    }
}

Depois de qualquer yield que espera tempo, confira se o objeto de fora ainda existe. Outra opção é iniciar a corrotina no próprio inimigo, assim ela morre junto com ele.

Com async e await o cuidado é maior: um método async não para sozinho quando o objeto é destruído. No Unity 6, use o destroyCancellationToken que todo MonoBehaviour tem.

Arquivo DelayedExplosion.cs:

using System;
using UnityEngine;

public class DelayedExplosion : MonoBehaviour
{
    [SerializeField] private float delaySeconds = 2f;
    [SerializeField] private GameObject explosionPrefab;

    private async void Start()
    {
        try
        {
            await Awaitable.WaitForSecondsAsync(delaySeconds, destroyCancellationToken);
            Instantiate(explosionPrefab, transform.position, Quaternion.identity);
            Destroy(gameObject);
        }
        catch (OperationCanceledException)
        {
            // O objeto foi destruído antes do tempo: não faz nada.
        }
    }
}

Se a bomba for destruída antes dos 2 segundos (o jogador a desarmou, a cena trocou), o token cancela a espera e o código depois do await não roda.

Um detalhe de tempo: Destroy não é imediato. A Unity destrói o objeto depois do loop de Update atual, antes de renderizar. No mesmo quadro do Destroy, objeto == null ainda dá false. Se outro script, no mesmo quadro, precisa saber que o objeto está morrendo, use um campo isDead ou desative o objeto antes.

Por que ?. e ?? não funcionam com objetos Unity

A documentação de UnityEngine.Object no Unity 6.3 é direta: os operadores ?. e ?? não são suportados com objetos da Unity, porque não podem ser sobrecarregados para tratar um objeto destruído como null. Eles olham só a metade C#, que ainda existe.

// Lança MissingReferenceException se target foi destruído:
target?.transform.Translate(Vector3.up);

// Funciona: usa o == sobrecarregado da Unity.
if (target != null)
{
    target.transform.Translate(Vector3.up);
}

O mesmo vale para target is null e ReferenceEquals(target, null): os dois ignoram a sobrecarga e dão falso para objeto destruído. Use == null, != null ou a conversão implícita para bool, if (target). A sua IDE pode sugerir trocar o != null por ?.; para objetos da Unity, ignore a sugestão.

Como prevenir

  • Quem morre avisa. Objetos se removem de listas e eventos no OnDisable ou OnDestroy, em vez de outros scripts checarem se eles existem.
  • Todo += com o -= simétrico. Revise isso em toda classe que se inscreve em evento.
  • Depois de esperar, cheque. Após yield return ou await, confira os objetos de fora com == null, e use destroyCancellationToken em código async.
  • Nada de ?. em UnityEngine.Object.
  • Referências que atravessam cenas são suspeitas. Um gerenciador com DontDestroyOnLoad que guarda referência de objeto da cena antiga é a receita do erro. Limpe essas referências ao carregar cena nova.

A ideia de "quem morre avisa" é um padrão de projeto (observer, com dono claro da inscrição). O post sobre padrões de projeto em jogos explica esse e outros que deixam o código mais previsível.

Próximo passo

Procure no seu projeto por += em eventos e confira se cada um tem o par -=. Depois, busque ?. aplicado a componentes ou GameObjects e troque por != null. Essas duas buscas eliminam a maior parte das MissingReferenceException antes de elas aparecerem. Para os outros tropeços comuns de quem está começando, a lista de erros de iniciante em desenvolvimento de jogos é a leitura seguinte.

Perguntas frequentes

O que significa The object of type has been destroyed but you are still trying to access it?

Significa que o seu script guarda uma referência para um objeto da Unity que já foi destruído com Destroy, por troca de cena ou porque você apagou o objeto. A variável não ficou vazia, ela ainda aponta para o lado C# do objeto, mas o objeto nativo da engine não existe mais.

Qual a diferença entre MissingReferenceException e NullReferenceException na Unity?

NullReferenceException acontece quando a variável nunca recebeu um objeto ou recebeu null. MissingReferenceException acontece quando ela recebeu um objeto válido que depois foi destruído. A correção do primeiro é atribuir; a do segundo é parar de usar a referência depois do Destroy.

Por que o operador ?. não evita o erro com objetos da Unity?

A documentação da Unity diz que ?. e ?? não são suportados com UnityEngine.Object, porque não podem ser sobrecarregados para tratar objeto destruído como null. Eles olham só a referência C#, que ainda existe. Use if (objeto == null) ou if (objeto) em vez disso.

Por que o objeto não é null logo depois do Destroy?

Destroy não é imediato. A Unity marca o objeto e só destrói de fato depois do loop de Update atual, antes de renderizar o quadro. No mesmo quadro, a comparação com null ainda dá falso.