NullReferenceException in Unity

11

Ponieważ wielu użytkowników napotyka NullReferenceException: Object reference not set to an instance of an objectbłąd w Unity, pomyślałem, że dobrym pomysłem byłoby zebranie z wielu źródeł wyjaśnień i sposobów naprawienia tego błędu.


Objawy

W konsoli pojawia się poniższy błąd, co to znaczy i jak go naprawić?

NullReferenceException: Odwołanie do obiektu nie jest ustawione na instancję obiektu

Hel
źródło
Wydaje się, że to ogólne pytanie programistyczne, a nie specyficzne dla twórców gier. Odpowiedź OP na własne pytanie zawiera link do pisemnego zgłoszenia zastrzeżeń obejmującego ten temat.
Pikalek
3
Podczas gdy „NullReferenceException” jest rzeczywiście ogólnym pytaniem programistycznym, tutaj pytanie dotyczy konkretnie wyjątku w Unity : gdzie można go spotkać w programowaniu Unity i jak je rozwiązać (zobacz różne przykłady).
Hellium,
@Pikalek, rozszerzyliśmy również zakres tego, na co zezwalamy w zakresie programowania ogólnego. Zostało to wyjaśnione, gdy zapytałem o to w meta . Zdaję sobie teraz sprawę, że może to nadal pasować do parametrów „zbyt ogólnych”, zgodnie z odpowiedzią Josha.
Gnemlock
W obecnej odpowiedzi nie ma też wzmianki o niczym szczególnym dla Unity (innym niż użycie typów specyficznych dla Unity w przykładach). W rzeczywistości jest to ogólna odpowiedź programowa. Nie używamy odpowiedzi w ścisłych argumentach, ale biorąc pod uwagę, że jest to odpowiedź własna, idzie w kierunku poparcia argumentu intencji.
Gnemlock
3
Unity ma kilka unikatowych / charakterystycznych sposobów wyzwalania tych błędów, poprzez nieprzypisane pola Inspektora, nieudane próby GetComponent lub Find, lub poprzez swój wariant smakowy „MissingReferenceException”, gdy masz prawidłowe odwołanie, ale otrzymałeś Destroy () ed. Myślę więc, że odpowiedzi na to pytanie w kontekście Jedności mają duży potencjał, aby być użytecznym dla społeczności, nawet jeśli sam wyjątek jest bardzo ogólny.
DMGregory

Odpowiedzi:

14

Typ wartości a typ odniesienia

W wielu językach programowania zmienne mają tak zwany „typ danych”. Dwa podstawowe typy danych to typy wartości (int, float, bool, char, struct, ...) i typ referencyjny (instancja klas). Podczas gdy typy wartości zawierają samą wartość , odwołania zawierają adres pamięci wskazujący na część pamięci przydzieloną na zbiór wartości (podobny do C / C ++).

Na przykład Vector3jest typem wartości (strukturą zawierającą współrzędne i niektóre funkcje), podczas gdy komponenty dołączone do GameObject (w tym dziedziczące skrypty niestandardowe MonoBehaviour) są typem referencyjnym.

Kiedy mogę otrzymać wyjątek NullReferenceException?

NullReferenceException są rzucane, gdy próbujesz uzyskać dostęp do zmiennej referencyjnej, która nie odwołuje się do żadnego obiektu, dlatego jest pusta (adres pamięci wskazuje 0).

Niektóre typowe miejsca NullReferenceExceptionzostaną podniesione:

Manipulowanie GameObject / Component, który nie został określony w inspektorze

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

Pobieranie komponentu, który nie jest dołączony do GameObject, a następnie próba manipulowania nim:

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

Dostęp do GameObject, który nie istnieje:

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

Uwaga: Należy zachować ostrożność, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypetylko wrócić gameObjects które są włączone w hierarchii, gdy funkcja jest wywoływana.

Próba użycia wyniku zwracającego getter 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.

Dostęp do elementu niezainicjowanej tablicy

private GameObject[] myObjects ; // Uninitialized array

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

Mniej powszechne, ale irytujące, jeśli nie wiesz o delegatach 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) ;
}

Jak naprawić ?

Jeśli zrozumiałeś poprzednie paragrafy, wiesz, jak naprawić błąd: upewnij się, że twoja zmienna odwołuje się (wskazuje) na instancję klasy (lub zawiera co najmniej jedną funkcję dla delegatów).

Łatwiej powiedzieć niż zrobić? W rzeczy samej. Oto kilka wskazówek, jak uniknąć i zidentyfikować problem.

„Brudny” sposób: metoda try & catch:

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

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

„Czystszy” sposób (IMHO): czek

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

W obliczu błędu, którego nie można rozwiązać, zawsze dobrze jest znaleźć przyczynę problemu. Jeśli jesteś „leniwy” (lub jeśli problem można łatwo rozwiązać), użyj przycisku, Debug.Logaby wyświetlić na konsoli informacje, które pomogą ci zidentyfikować przyczynę problemu. Bardziej złożonym sposobem jest użycie punktów przerwania i debuggera IDE.

Użycie Debug.Logjest na przykład przydatne do określenia, która funkcja jest wywoływana jako pierwsza. Zwłaszcza jeśli masz funkcję odpowiedzialną za inicjowanie pól. Ale nie zapomnij usunąć tych, Debug.Logaby uniknąć zaśmiecania konsoli (i ze względu na wydajność).

Kolejna rada, nie wahaj się „wyciąć” wywołania funkcji i dodaj, Debug.Logaby sprawdzić.

Zamiast :

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

Zrób to, aby sprawdzić, czy ustawione są wszystkie odwołania:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Nawet lepiej :

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

Źródła:

  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
Hel
źródło
Dużo wysiłku wymaga wyjaśnienie, jak „zdiagnozować” problem. Nie uważałbym tej odpowiedzi za pytanie „w czym tkwi problem”. To również nie uwzględnia odpowiedzi, które zwykle pojawiają się na tego rodzaju pytaniach. Być może lepiej byłoby w dokumentacji StackOverflow? Może nie.
Gnemlock
2
Nie powiedziałbym, że korzystanie z dziennika debugowania jest leniwe . Dla mnie znacznie szybciej jest użyć debug.log, aby zawęzić zakres występowania błędu, a następnie użyć debugera, aby naprawdę znaleźć błąd. Ale zawsze zależy to od rodzaju błędu. W każdym razie nie powiedziałbym, że używanie dziennika debugowania jest leniwe : P
Vaillancourt
Powinieneś także zauważyć, że sprawdzanie wartości zerowej nie zawsze jest dobrym pomysłem. Jeszcze gorszym pomysłem byłoby użycie try/catch. Błąd mówi ci wiele o twoim problemie, a zanim początkujący zaczną wszędzie sprawdzać wartości zerowe, głównym problemem jest inspektor, ponieważ zapominasz odwołać się do jakiegoś obiektu (przeciągnij obiekt na skrypt). Widziałem dużo kodu z try/catchzerowymi kontrolami w miejscach, w których jest to całkowicie niepotrzebne. Debugowanie i praca z takim kodem to „ból w **”. Początkujący uczą się o przypadkach użycia tych czeków i dopiero wtedy ich używają.
Candid Moon _Max_
Myślę, że sprawdzanie wartości zerowej może być dobrym pomysłem, jeśli w pliku else. Posiadanie NullReferenceExceptionnie zawsze jest oczywiste, ale No Rigidbody component attached to the gameObjectbezpośrednio wyjaśnia, co jest nie tak. Zgadzam się, że sama if( obj != null )wiadomość „bez” po prostu „ukrywa” problem i możesz mieć działający projekt, ale nie robić tego, czego byś się nie spodziewał, nie wiedząc, dlaczego.
Hellium
4

Chociaż możemy łatwo sprawdzić, czy nie próbujemy uzyskać dostępu do pustego odwołania, nie zawsze jest to odpowiednie rozwiązanie. Wiele razy, w programowaniu Unity, nasz problem może wynikać z faktu, że referencja nie powinna być zerowa. W niektórych sytuacjach po prostu zignorowanie pustych referencji może uszkodzić nasz kod.

Na przykład może to być odniesienie do naszego kontrolera wejściowego. Wspaniale jest, że gra nie ulega awarii z powodu wyjątku zerowego odniesienia, ale musimy dowiedzieć się, dlaczego nie ma kontrolera wejściowego i naprawić ten problem. Bez tego mamy grę, która nie może ulec awarii, ale nie może też brać udziału.

Poniżej wymienię możliwe przyczyny i rozwiązania, które napotykam w innych pytaniach.


Czy próbujesz uzyskać dostęp do klasy „manager”?

Jeśli próbujesz uzyskać dostęp do klasy, która działa jak „menedżer” (to znaczy, klasy, która powinna mieć zawsze tylko jedną instancję na raz), możesz lepiej zastosować metodę Singleton . Do klasy Singleton można idealnie uzyskać dostęp z dowolnego miejsca, bezpośrednio, zachowując public staticodniesienie do siebie. W ten sposób Singleton może zawierać odwołanie do aktywnej instancji, które byłoby dostępne bez problemu z ustawieniem rzeczywistego odwołania za każdym razem.

Czy odwołujesz się do instancji swojego obiektu?

Zazwyczaj wystarczy zaznaczyć oznaczenie jako public, abyśmy mogli ustawić odwołanie do instancji za pośrednictwem inspektora. Zawsze sprawdź, czy nie ustawić odwołanie do instancji, przez inspektora, gdyż nie jest rzadkością przegapić ten krok.

Czy tworzysz instancję?

Jeśli konfigurujemy nasz obiekt w kodzie, ważne jest, aby utworzyć jego instancję . Można to zrobić za pomocą newsłowa kluczowego i metod konstruktora. Rozważ na przykład:

private GameObject gameObject;

Stworzyliśmy odniesienie do GameObject, ale nic nie wskazuje. Uzyskanie dostępu do tego odwołania w obecnej postaci spowoduje wyjątek zerowy . Zanim odwołamy się do naszego GameObjectwystąpienia, możemy wywołać domyślną metodę konstruktora w następujący sposób:

gameObject = new GameObject();

Samouczek Unity dotyczący klas wyjaśnia praktykę tworzenia i używania konstruktorów.

Czy używasz GetComponent<t>()metody przy założeniu, że składnik istnieje?

Po pierwsze, upewnij się, że zawsze wywołujemy GetComponent<t>()przed wywołaniem metod z instancji komponentu.

Z powodów, dla których nie warto wchodzić, możemy założyć, że nasz lokalny obiekt gry zawiera określony komponent i spróbować uzyskać do niego dostęp GetComponent<t>(). Jeśli lokalny obiekt gry nie zawiera tego konkretnego komponentu, zwrócimy nullwartość.

Możesz łatwo sprawdzić, czy zwracana jest wartość null, przed uzyskaniem do niej dostępu. Jeśli jednak obiekt gry powinien mieć wymagany komponent, lepiej upewnić się, że ma przynajmniej domyślną wersję tego komponentu. Możemy oznaczyć MonoBehaviourjako, [RequireComponent(typeof(t))]aby upewnić się, że zawsze mamy ten typ komponentu.

Oto przykład MonoBehaviourobiektu gry, który zawsze powinien zawierać Rigidbody. Jeśli skrypt zostanie dodany do obiektu gry, który nie zawiera a Rigidbody, Rigidbodyzostanie utworzony domyślny .

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


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

Czy próbowałeś odbudować swój projekt?

W niektórych przypadkach Unity może powodować problemy, próbując odwołać się do buforowanej wersji obiektu gry. Zgodnie z odwiecznym rozwiązaniem „wyłącz i włącz ponownie”, spróbuj usunąć folder Library i ponownie otwórz Unity. Unity będzie zmuszony przebudować twój projekt. Może to rozwiązać niektóre bardzo szczególne przypadki tego problemu i powinno wskazywać na problemy, które nie pojawią się w ostatecznej wersji.

Gnemlock
źródło
1
Nadal nie jestem pewien, czy to pytanie powinno dotyczyć tematu. Ale tutaj jest wiki społeczności dla użytkowników do zamieszczania dodatkowych potencjalnych odpowiedzi; Jak dotąd składa się z podstaw pierwszej połowy strony zaakceptowanych odpowiedzi na pytania oznaczone jako jedność i „zerowe odniesienie” (które faktycznie spełniało kryteria pytania).
Gnemlock
-5

Widzę, że istnieje akceptowana odpowiedź. Ale jest lepsza odpowiedź lub sugestia dotycząca obsługi NullReferenceException. Jeśli możesz powiązać programowanie w języku Java, takim jak ja, możesz zapobiec wysyłaniu błędu zerowego, używając try-catchbloku. Wypróbuj sam! ;-)

Jeśli używasz w C #, sprawdź, czy masz using System;na górze pliku skryptu. Jeśli nie, dodaj go. Teraz możesz używać różnego rodzaju Exceptionklas, próbując złapać wiersz kodu.

Jeśli używasz UnityScript, użyj import System;

Oto przykład:

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

        }

    }
}

Należy również pamiętać, można złapać także inne wyjątki takie jak MissingReferenceException, MissingComponentException, IndexOutOfRangeException, lub innych klas wyjątków tak długo, jak to using Systemw skrypcie.

To wszystko.

David Dimalanta
źródło
2
Metoda try & catch została opisana w zaakceptowanej odpowiedzi ....
Hellium