NullReferenceException dans Unity

11

Étant donné que de nombreux utilisateurs sont confrontés à l' NullReferenceException: Object reference not set to an instance of an objecterreur dans Unity, j'ai pensé que ce serait une bonne idée de rassembler à partir de plusieurs sources des explications et des moyens de corriger cette erreur.


Symptômes

Je reçois l'erreur ci-dessous qui apparaît dans ma console, qu'est-ce que cela signifie et comment puis-je la corriger?

NullReferenceException: référence d'objet non définie sur une instance d'un objet

Hellium
la source
Cela semble être une question de programmation générale et non spécifique au développeur de jeu. La réponse d'OP à sa propre question comprend un lien vers SO qui couvre ce sujet.
Pikalek
3
Alors que "NullReferenceException" est, en effet, une question de programmation générale, ici, la question couvre spécifiquement l'exception dans Unity : où peut-elle être rencontrée dans la programmation Unity , et comment les résoudre (voir les différents exemples).
Hellium
@Pikalek, nous avons également élargi notre champ d'action pour ce que nous permettons en termes de programmation générale. Cela a été clarifié lorsque j'ai posé des questions à ce sujet dans Meta . Je me rends compte, maintenant, que cela pourrait toujours correspondre aux paramètres de «trop générique», selon la réponse de Josh.
Gnemlock
La réponse actuelle ne fait également aucune remarque sur quoi que ce soit de spécifique à Unity (autre que l'utilisation de types spécifiques à Unity dans les exemples). Il s'agit en fait d'une réponse de programmation générique. Nous n'utilisons pas de réponses dans les arguments fermés, mais étant donné qu'il s'agit d'une auto-réponse, cela va vers le soutien d'un argument d'intention.
Gnemlock
3
Unity a plusieurs façons uniques / caractéristiques de déclencher ces erreurs, via des champs Inspector non attribués, des tentatives GetComponent ou Find échouées, ou via sa variante de variante "MissingReferenceException" lorsque vous disposiez d'une référence valide mais qu'elle a obtenu Destroy () ed. Donc, je pense que les réponses à cette question dans le contexte de l'unité ont un bon potentiel pour être utiles à la communauté, même si l'exception elle-même est très générale.
DMGregory

Réponses:

14

Type de valeur vs type de référence

Dans de nombreux langages de programmation, les variables ont ce qu'on appelle un "type de données". Les deux principaux types de données sont les types de valeur (int, float, bool, char, struct, ...) et le type de référence (instance de classes). Alors que les types de valeurs contiennent la valeur elle - même , les références contiennent une adresse mémoire pointant vers une partie de la mémoire allouée pour contenir un ensemble de valeurs (similaire à C / C ++).

Par exemple, Vector3est un type de valeur (une structure contenant les coordonnées et certaines fonctions) tandis que les composants attachés à votre GameObject (y compris vos scripts personnalisés héritant de MonoBehaviour) sont de type référence.

Quand puis-je avoir une NullReferenceException?

NullReferenceException sont levées lorsque vous essayez d'accéder à une variable de référence qui ne fait référence à aucun objet, elle est donc nulle (l'adresse mémoire pointe vers 0).

Quelques lieux communs a NullReferenceExceptionseront soulevés:

Manipulation d'un GameObject / Component qui n'a pas été spécifié dans l'inspecteur

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

Récupérer un composant qui n'est pas attaché au GameObject puis essayer de le manipuler:

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

Accéder à un GameObject qui n'existe pas:

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

Note: Attention, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTyperetour seulement GameObjects qui sont activés dans la hiérarchie lorsque la fonction est appelée.

Essayer d'utiliser le résultat d'un getter qui revient 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.

Accéder à un élément d'un tableau non initialisé

private GameObject[] myObjects ; // Uninitialized array

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

Moins commun, mais ennuyeux si vous ne le savez pas sur les délégués 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) ;
}

Comment réparer ?

Si vous avez compris les paragraphes précédents, vous savez comment corriger l'erreur: assurez-vous que votre variable référence (pointe vers) une instance d'une classe (ou contient au moins une fonction pour les délégués).

Plus facile à dire qu'à faire? Oui en effet. Voici quelques conseils pour éviter et identifier le problème.

La manière "sale": La méthode 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 voie "plus propre" (IMHO): Le chèque

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

Lorsque vous faites face à une erreur que vous ne pouvez pas résoudre, c'est toujours une bonne idée de trouver la cause du problème. Si vous êtes "paresseux" (ou si le problème peut être résolu facilement), utilisez Debug.Logpour afficher sur la console des informations qui vous aideront à identifier la cause du problème. Une manière plus complexe consiste à utiliser les points d'arrêt et le débogueur de votre IDE.

L'utilisation Debug.Logest très utile pour déterminer quelle fonction est appelée en premier par exemple. Surtout si vous avez une fonction chargée d'initialiser les champs. Mais n'oubliez pas de les supprimer Debug.Logpour éviter d'encombrer votre console (et pour des raisons de performances).

Un autre conseil, n'hésitez pas à "couper" vos appels de fonction et à les ajouter Debug.Logpour faire quelques vérifications.

Au lieu de :

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

Procédez ainsi pour vérifier si toutes les références sont définies:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Encore mieux :

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

Sources:

  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
la source
Cela demande beaucoup d'efforts pour expliquer le "comment" du diagnostic du problème. Je ne considérerais pas cela comme une réponse à la question "quel est le problème". Cela échoue également à répondre aux réponses qui apparaissent généralement sur ce type de questions. Peut-être que ce serait mieux dans la documentation StackOverflow? Peut-être pas.
Gnemlock
2
Je ne dirais pas que l'utilisation du journal de débogage est paresseuse . Pour moi, il est beaucoup plus rapide d'utiliser le fichier debug.log pour réduire la portée de l'erreur, puis utilisez le débogueur pour vraiment trouver l'erreur. Mais cela dépend toujours de l'erreur à portée de main. En tout cas, je ne dirais pas que l'utilisation du journal de débogage est paresseuse : P
Vaillancourt
Vous auriez également dû souligner que ce n'est pas toujours une bonne idée de mettre des chèques pour null. Une idée encore pire serait d'utiliser try/catch. L'erreur vous en dit long sur le problème que vous rencontrez, et avant que les débutants commencent à mettre des vérifications nulles partout, votre problème principal est dans l'inspecteur car vous oubliez de référencer un objet (faites glisser l'objet sur le script). J'ai vu beaucoup de code avec try/catchet des contrôles nuls dans des endroits où c'est totalement inutile. Déboguer et travailler avec un code comme celui-ci est «une douleur dans le **». Les débutants se renseignent sur les cas d'utilisation de ces vérifications et les utilisent ensuite.
Candid Moon _Max_
Je pense qu'avoir une vérification nulle peut être une bonne idée si un message de débogage explicite est fourni dans le else. Avoir un NullReferenceExceptionn'est pas toujours explicite tout No Rigidbody component attached to the gameObjecten expliquant directement ce qui ne va pas. Je suis d'accord que le simple fait d'avoir le if( obj != null )message sans "cache" le problème, et vous pouvez avoir un projet de travail mais ne pas faire ce que vous attendez sans savoir pourquoi.
Hellium
4

Bien que nous puissions facilement vérifier simplement que nous n'essayons pas d'accéder à une référence nulle, ce n'est pas toujours une solution appropriée. Plusieurs fois, dans la programmation Unity, notre problème peut provenir du fait que la référence ne doit pas être nulle. Dans certaines situations, le simple fait d'ignorer les références nulles peut briser notre code.

Par exemple, il peut s'agir d'une référence à notre contrôleur d'entrée. Il est formidable que le jeu ne plante pas en raison de l'exception de référence nulle, mais nous devons comprendre pourquoi il n'y a pas de contrôleur d'entrée et résoudre ce problème. Sans cela, nous avons un jeu qui ne peut pas planter, mais ne peut pas non plus prendre de données.

Ci-dessous, je vais énumérer les raisons et les solutions possibles, au fur et à mesure que je les rencontre dans d'autres questions.


Essayez-vous d'accéder à une classe "manager"?

Si vous essayez d'accéder à une classe qui agit en tant que «gestionnaire» (c'est-à-dire une classe qui ne devrait avoir qu'une seule instance en cours d'exécution à la fois), vous feriez mieux d'utiliser l'approche Singleton . Une classe Singleton est idéalement accessible de n'importe où, directement, en gardant une public staticréférence à elle-même. De cette façon, un Singleton peut contenir une référence à l'instance active, qui serait accessible sans avoir à configurer la référence réelle à chaque fois.

Faites-vous référence à l'instance de votre objet?

Il est courant de simplement marquer une référence comme public, afin que nous puissions définir la référence à l'instance via l'inspecteur. Vérifiez toujours que vous avez défini la référence à une instance, via l'inspecteur, car il n'est pas rare de manquer cette étape.

Instanciez-vous votre instance?

Si nous mettons en place notre objet dans le code, il est important de s'assurer que nous instancions l'objet. Cela peut être effectué en utilisant le newmot - clé et les méthodes constructeur. Par exemple, tenez compte des éléments suivants:

private GameObject gameObject;

Nous avons créé une référence à a GameObject, mais cela ne signifie rien. L'accès à cette référence telle quelle entraînera une exception de référence nulle . Avant de référencer notre GameObjectinstance, nous pouvons appeler une méthode constructeur par défaut comme suit:

gameObject = new GameObject();

Le didacticiel Unity sur les classes explique la pratique de la création et de l'utilisation de constructeurs.

Utilisez-vous la GetComponent<t>()méthode en supposant que le composant existe?

Tout d'abord, assurez-vous que nous appelons toujours GetComponent<t>()avant d'appeler des méthodes à partir de l'instance de composant.

Pour des raisons qui ne valent pas la peine, nous pouvons supposer que notre objet de jeu local contient un composant particulier et essayer d'y accéder avec GetComponent<t>(). Si l'objet de jeu local ne contient pas ce composant particulier, nous retournerons une nullvaleur.

Vous pouvez facilement vérifier si la valeur renvoyée est null, avant d'y accéder. Cependant, si votre objet de jeu doit avoir le composant requis, il peut être préférable de s'assurer qu'il a au moins une version par défaut de ce composant. Nous pouvons marquer un MonoBehaviouras [RequireComponent(typeof(t))]pour nous assurer que nous avons toujours ce type de composant.

Voici un exemple de MonoBehaviourpour un objet de jeu qui devrait toujours contenir un Rigidbody. Si le script est ajouté à un objet de jeu qui ne contient pas de Rigidbody, une valeur par défaut Rigidbodysera créée.

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


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

Avez-vous essayé de reconstruire votre projet?

Dans certains cas, Unity peut provoquer des problèmes en essayant de référencer une version mise en cache d'un objet de jeu. Conformément à la vieille solution «éteignez-le et rallumez-la», essayez de supprimer votre dossier Bibliothèque et de rouvrir Unity. Unity sera obligé de reconstruire votre projet. Cela peut résoudre certains cas très particuliers de ce problème et devrait signaler des problèmes qui ne se poseraient pas dans une version finale.

Gnemlock
la source
1
Je ne sais toujours pas si cette question devrait être sur le sujet. Mais voici un wiki communautaire pour que les utilisateurs puissent poster des réponses potentielles supplémentaires; Jusqu'à présent, il comprend les bases de la première demi-page de réponses acceptées pour les questions marquées unité et "référence nulle" (qui répondaient en fait aux critères de la question).
Gnemlock
-5

Je vois qu'il y a une réponse acceptée. Mais, il y a une meilleure réponse ou suggestion pour la manipulation NullReferenceException. Si vous pouvez relier la programmation en langage Java comme moi, vous pouvez empêcher d'envoyer une erreur nulle en utilisant le try-catchbloc. Essayez-le vous-même! ;-)

Si vous utilisez en C #, vérifiez si vous avez using System;en haut de votre fichier de script. Sinon, ajoutez-le. Maintenant, vous pouvez utiliser toutes sortes de Exceptionclasses tout en essayant d'attraper une ligne de code.

Si vous utilisez UnityScript, utilisez import System;

Voici un exemple:

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

        }

    }
}

Rappelez - vous aussi, vous pouvez prendre également d' autres exceptions comme MissingReferenceException, MissingComponentException, IndexOutOfRangeException, ou toute autre classe d'exception tant que vous incluez using Systemdans votre script.

C'est tout.

David Dimalanta
la source
2
La méthode try & catch a été décrite dans la réponse acceptée ....
Hellium