NullReferenceException in Unity

11

Da viele Benutzer mit dem NullReferenceException: Object reference not set to an instance of an objectFehler in Unity konfrontiert sind , hielt ich es für eine gute Idee, aus mehreren Quellen einige Erklärungen und Möglichkeiten zur Behebung dieses Fehlers zu sammeln.


Symptome

Ich erhalte den folgenden Fehler in meiner Konsole. Was bedeutet das und wie behebe ich ihn?

NullReferenceException: Objektreferenz nicht auf eine Instanz eines Objekts festgelegt

Hellium
quelle
Dies scheint eine allgemeine Programmierfrage zu sein und ist nicht spielentwicklungsspezifisch. Die Antwort von OP auf die eigene Frage enthält einen Link zu SO, der dieses Thema behandelt.
Pikalek
3
Während die "NullReferenceException" in der Tat eine allgemeine Programmierfrage ist, behandelt die Frage hier speziell die Ausnahme in Unity : Wo kann sie in der Unity-Programmierung auftreten und wie können sie gelöst werden (siehe die verschiedenen Beispiele).
Hellium
@Pikalek, wir haben auch unseren Spielraum für das erweitert, was wir in Bezug auf die allgemeine Programmierung zulassen. Dies wurde klargestellt, als ich in Meta danach fragte . Mir ist jetzt klar, dass dies nach Joshs Antwort immer noch zu den Parametern „zu allgemein“ passen könnte.
Gnemlock
Die aktuelle Antwort macht auch keine Kenntnis von etwas spezifisch auf Unity (außer Unity bestimmte Arten in den Beispielen verwendet wird ). Es ist in der Tat eine generische Programmierantwort. Wir verwenden Antworten nicht in engen Argumenten, aber da es sich um eine Selbstantwort handelt, wird das Argument der Absicht unterstützt.
Gnemlock
3
Unity verfügt über einige eindeutige / charakteristische Möglichkeiten, um diese Fehler durch nicht zugewiesene Inspector-Felder, fehlgeschlagene GetComponent- oder Find-Versuche oder durch die Variantenvariante "MissingReferenceException" auszulösen, wenn Sie eine gültige Referenz hatten, aber Destroy () ed. Daher denke ich, dass Antworten auf diese Frage im Kontext der Einheit ein gutes Potenzial haben, für die Gemeinschaft nützlich zu sein, selbst wenn die Ausnahme selbst sehr allgemein ist.
DMGregory

Antworten:

14

Werttyp vs Referenztyp

In vielen Programmiersprachen haben Variablen einen sogenannten "Datentyp". Die beiden primären Datentypen sind Werttypen (int, float, bool, char, struct, ...) und Referenztyp (Instanz von Klassen). Während Werttypen den Wert selbst enthalten , enthalten Referenzen eine Speicheradresse, die auf einen Teil des Speichers verweist, der eine Reihe von Werten enthält (ähnlich wie C / C ++).

Beispielsweise Vector3handelt es sich um einen Werttyp (eine Struktur, die die Koordinaten und einige Funktionen enthält), während an Ihr GameObject angehängte Komponenten (einschließlich Ihrer benutzerdefinierten Skripts, von denen geerbt wird MonoBehaviour) Referenztypen sind.

Wann kann ich eine NullReferenceException haben?

NullReferenceException werden ausgelöst, wenn Sie versuchen, auf eine Referenzvariable zuzugreifen, die auf kein Objekt verweist. Daher ist sie null (die Speicheradresse zeigt auf 0).

Einige gemeinsame Orte NullReferenceExceptionwerden angesprochen:

Bearbeiten eines GameObject / einer Komponente, die im Inspektor nicht angegeben wurde

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

Rufen Sie eine Komponente ab, die nicht an das GameObject angehängt ist, und versuchen Sie dann, sie zu manipulieren:

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

Zugriff auf ein GameObject, das nicht existiert:

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

Hinweis: Seien Sie vorsichtig, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypenur zurückkehren , die Gameobjects sind aktiviert in der Hierarchie , wenn die Funktion aufgerufen wird.

Der Versuch, das Ergebnis eines zurückkehrenden Getters zu verwenden 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.

Zugriff auf ein Element eines nicht initialisierten Arrays

private GameObject[] myObjects ; // Uninitialized array

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

Weniger verbreitet, aber ärgerlich, wenn Sie nichts über C # -Delegierte wissen:

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

Wie repariert man ?

Wenn Sie die vorherigen Absätze verstanden haben, wissen Sie, wie Sie den Fehler beheben können: Stellen Sie sicher, dass Ihre Variable auf eine Instanz einer Klasse verweist (oder auf diese zeigt) (oder mindestens eine Funktion für Delegaten enthält).

Leichter gesagt als getan? Ja, in der Tat. Hier sind einige Tipps, um das Problem zu vermeiden und zu identifizieren .

Der "schmutzige" Weg: Die Try & Catch-Methode:

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

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

Der "sauberere" Weg (IMHO): Der Scheck

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

Wenn Sie auf einen Fehler stoßen, den Sie nicht lösen können, ist es immer eine gute Idee, die Ursache des Problems zu finden. Wenn Sie "faul" sind (oder wenn das Problem leicht gelöst werden kann), verwenden SieDebug.Log zeigen Sie auf der Konsole Informationen an, anhand derer Sie ermitteln können, was das Problem verursachen könnte. Eine komplexere Möglichkeit besteht darin, die Haltepunkte und den Debugger Ihrer IDE zu verwenden.

Die Verwendung Debug.Logist sehr nützlich, um beispielsweise zu bestimmen, welche Funktion zuerst aufgerufen wird. Insbesondere, wenn Sie eine Funktion haben, die für die Initialisierung von Feldern verantwortlich ist. Aber vergessen Sie nicht, diese zu entfernenDebug.Log , um ein Überladen Ihrer Konsole (und aus Leistungsgründen) zu vermeiden.

Ein weiterer Rat, zögern Sie nicht, Ihre Funktionsaufrufe zu "kürzen" und hinzuzufügen Debug.Log, um einige Überprüfungen durchzuführen.

Anstatt :

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

Überprüfen Sie hiermit, ob alle Referenzen festgelegt sind:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Noch besser :

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

Quellen:

  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
quelle
Dies ist sehr aufwändig, um das "Wie" zur Diagnose des Problems zu erklären. Ich würde das nicht als tatsächliche Antwort auf die Frage "Was ist das Problem" betrachten. Dies geht auch nicht auf die Antworten ein, die normalerweise auf diese Art von Fragen erscheinen. Vielleicht wäre dies in der StackOverflow-Dokumentation besser? Vielleicht nicht.
Gnemlock
2
Ich würde nicht sagen, dass die Verwendung des Debug-Protokolls faul ist . Für mich ist es viel schneller , das debug.log zu verwenden, um den Umfang des Auftretens des Fehlers einzugrenzen, und dann den Debugger zu verwenden, um den Fehler wirklich zu finden. Aber es kommt immer auf den vorliegenden Fehler an. Auf jeden Fall würde ich nicht sagen, dass die Verwendung des Debug-Protokolls faul ist : P
Vaillancourt
Sie sollten auch darauf hingewiesen haben, dass es nicht immer eine gute Idee ist, auf Null zu prüfen. Noch schlimmer wäre die Verwendung try/catch. Der Fehler sagt viel über das Problem aus, das Sie dort haben, und bevor Anfänger überall Nullprüfungen durchführen, liegt das Hauptproblem im Inspektor, da Sie vergessen, auf ein Objekt zu verweisen (Objekt auf das Skript ziehen). Ich habe viel Code mit try/catchund Nullprüfungen an Stellen gesehen, an denen dies völlig unnötig ist. Das Debuggen und Arbeiten mit einem solchen Code ist "ein Schmerz im A **". Anfänger lernen die Anwendungsfälle dieser Prüfungen kennen und verwenden sie erst dann.
Candid Moon _Max_
Ich denke, dass eine Nullprüfung eine gute Idee sein kann, wenn eine explizite Debug-Nachricht in der bereitgestellt wird else. Ein zu haben NullReferenceExceptionist nicht immer selbsterklärend, während es No Rigidbody component attached to the gameObjectdirekt erklärt, was falsch ist. Ich bin damit einverstanden, dass nur das Fehlen der if( obj != null )Nachricht das Problem "verbirgt" und Sie ein funktionierendes Projekt haben können, aber nicht das tun, was Sie erwarten würden, ohne zu wissen warum.
Hellium
4

Wir können zwar einfach eine Überprüfung durchführen, um sicherzustellen, dass wir nicht versuchen, auf eine Nullreferenz zuzugreifen, dies ist jedoch nicht immer eine geeignete Lösung. In der Unity-Programmierung kann unser Problem häufig auf die Tatsache zurückzuführen sein, dass die Referenz nicht null sein sollte. In einigen Situationen kann das einfache Ignorieren von Nullreferenzen unseren Code beschädigen.

Dies könnte beispielsweise ein Verweis auf unseren Eingangscontroller sein. Es ist großartig, dass das Spiel aufgrund der Nullreferenzausnahme nicht abstürzt, aber wir müssen herausfinden, warum es keinen Eingabecontroller gibt, und dieses Problem beheben . Ohne sie haben wir ein Spiel, das möglicherweise nicht abstürzt, aber auch keine Eingaben entgegennimmt.

Im Folgenden werde ich mögliche Gründe und Lösungen auflisten, auf die ich in anderen Fragen stoße.


Versuchen Sie, auf eine "Manager" -Klasse zuzugreifen?

Wenn Sie versuchen, auf eine Klasse zuzugreifen, die als "Manager" fungiert (dh eine Klasse, auf der immer nur eine Instanz gleichzeitig ausgeführt werden sollte), ist es möglicherweise besser, den Singleton-Ansatz zu verwenden . Auf eine Singleton-Klasse kann idealerweise direkt von überall aus zugegriffen werden, indem ein public staticVerweis auf sich selbst beibehalten wird. Auf diese Weise kann ein Singleton einen Verweis auf die aktive Instanz enthalten, auf den zugegriffen werden kann, ohne dass jedes Mal der eigentliche Verweis eingerichtet werden muss.

Verweisen Sie auf die Instanz Ihres Objekts?

Es ist üblich, eine Referenz einfach als zu markieren public, damit wir die Referenz über den Inspektor auf die Instanz setzen können. Prüfen Sie immer , dass Sie haben den Verweis auf eine Instanz festgelegt, über den Inspektor, da es nicht ungewöhnlich ist , diesen Schritt zu verpassen.

Instanziieren Sie Ihre Instanz?

Wenn wir unser Objekt im Code einrichten, ist es wichtig sicherzustellen, dass wir das Objekt instanziieren . Dies kann mit dem newSchlüsselwort und den Konstruktormethoden durchgeführt werden. Betrachten Sie beispielsweise Folgendes:

private GameObject gameObject;

Wir haben einen Verweis auf a erstellt GameObject, der jedoch auf nichts verweist. Der Zugriff auf diese Referenz können wie in einer Folge NULL - Verweis Ausnahme . Bevor wir auf unsere GameObjectInstanz verweisen , können wir eine Standardkonstruktormethode wie folgt aufrufen:

gameObject = new GameObject();

Das Unity-Lernprogramm für Klassen erläutert die Vorgehensweise beim Erstellen und Verwenden von Konstruktoren.

Verwenden Sie die GetComponent<t>()Methode unter der Annahme, dass die Komponente vorhanden ist?

Stellen Sie zunächst sicher, dass wir immer anrufen GetComponent<t>() bevor wir Methoden von der Komponenteninstanz aufrufen.

Aus Gründen, die es nicht wert sind, untersucht zu werden, können wir davon ausgehen, dass unser lokales Spielobjekt eine bestimmte Komponente enthält, und versuchen, mit darauf zuzugreifen GetComponent<t>(). Wenn das lokale Spielobjekt diese bestimmte Komponente nicht enthält, geben wir einen nullWert zurück.

Sie können leicht überprüfen, ob der Rückgabewert ist null, bevor Sie darauf zugreifen. Wenn Sie Ihr Spiel Objekt jedoch sollte die erforderliche Komponente hat, kann es besser sein , um sicherzustellen , dass es mindestens eine hat Standard - Version dieser Komponente. Wir können ein MonoBehaviourals kennzeichnen, [RequireComponent(typeof(t))]um sicherzustellen, dass wir immer diese Art von Komponente haben.

Hier ist ein Beispiel für ein MonoBehaviourfür ein Spielobjekt, das immer ein enthalten sollte Rigidbody. Wenn das Skript einem Spielobjekt hinzugefügt wird, das kein enthält Rigidbody, wird ein Standard Rigidbodyerstellt.

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


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

Haben Sie versucht, Ihr Projekt neu aufzubauen?

In einigen Fällen kann Unity Probleme verursachen, wenn versucht wird, auf eine zwischengespeicherte Version eines Spielobjekts zu verweisen . Versuchen Sie im Einklang mit der uralten Lösung "Aus- und wieder einschalten", Ihren Bibliotheksordner zu löschen und Unity erneut zu öffnen. Unity wird gezwungen sein, Ihr Projekt neu aufzubauen. Dies kann einige sehr eigenartige Fälle dieses Problems lösen und sollte auf Probleme hinweisen, die in einem endgültigen Build nicht auftreten würden.

Gnemlock
quelle
1
Ich bin mir immer noch nicht sicher, ob diese Frage zum Thema gehören soll. Hier ist jedoch ein Community-Wiki, in dem Benutzer zusätzliche potenzielle Antworten veröffentlichen können. Bisher besteht es aus den Grundlagen der ersten halben Seite akzeptierter Antworten auf Fragen, die als Einheit und "Nullreferenz" gekennzeichnet sind (die tatsächlich die Kriterien der Frage erfüllten).
Gnemlock
-5

Ich sehe, dass es eine akzeptierte Antwort gibt. Es gibt jedoch eine bessere Antwort oder einen besseren Vorschlag für die Handhabung NullReferenceException. Wenn Sie die Programmierung in Java-Sprache wie ich beziehen können, können Sie verhindern, dass ein Nullfehler gesendet wird, indem Sie dietry-catch Blocks . Probieren Sie es aus! ;-);

Wenn Sie in C # verwenden, überprüfen Sie, ob Sie using System;oben in Ihrer Skriptdatei haben. Wenn nicht, fügen Sie es hinzu. Jetzt können Sie alle Arten von ExceptionKlassen verwenden, während Sie versuchen, eine Codezeile abzufangen.

Wenn Sie UnityScript verwenden, verwenden Sie import System;

Hier ist ein Beispiel:

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

        }

    }
}

Denken Sie auch daran, können Sie auch andere Ausnahmen wie fangen MissingReferenceException, MissingComponentException, IndexOutOfRangeException, oder jede andere Ausnahmeklassen, solange Sie umfassenusing System in Ihrem Skript.

Das ist alles.

David Dimalanta
quelle
2
Die Try & Catch-Methode wurde in der akzeptierten Antwort beschrieben ....
Hellium