NullReferenceException в Unity


11

Оскільки багато користувачів стикаються з NullReferenceException: Object reference not set to an instance of an objectпомилкою в Unity, я подумав, що було б непогано зібрати з декількох джерел деякі пояснення та способи виправити цю помилку.


Симптоми

Я отримую нижче помилку, що з’являється на моїй консолі, що це означає і як це виправити?

NullReferenceException: Посилання на об'єкт не встановлено для екземпляра об'єкта


Це здається загальним питанням програмування, а не специфічним ігровим розробником. Відповідь ОП на власне запитання включає посилання на ТА, що висвітлює цю тему.
Пікалек

3
Хоча "NullReferenceException" справді є загальним питанням програмування, тут питання стосується конкретно винятку в Unity : де його можна зустріти в програмуванні Unity та як їх вирішити (див. Різні приклади).
Гелій

@Pikalek, ми також розширили сферу наших можливостей для загального програмування. Це було з’ясовано, коли я запитав про це в мета . Зараз я усвідомлюю, що це все-таки може відповідати параметрам "занадто загальних", відповідно до відповіді Джоша.
Gnemlock

Поточна відповідь також не відзначає нічого конкретного для Unity (окрім використання конкретних типів Unity у прикладах). Насправді це загальна відповідь на програмування. Ми не використовуємо відповіді у близьких аргументах, але, якщо це відповідь самовідповідача, це дійсно йде на підтримку аргументу наміру.
Gnemlock

3
У Unity є кілька унікальних / характерних способів викликати ці помилки, через непризначені поля Інспектора, невдалі спроби GetComponent або Find, або через його варіант аромат "MissingReferenceException", коли у вас була дійсна посилання, але вона отримала Destroy () ed. Отже, я вважаю, що відповіді на це питання в контексті Єдності можуть мати корисний потенціал для спільноти, навіть якщо саме Виняток є дуже загальним.
DMGregory

Відповіді:


14

Тип значення порівняно з типом посилання

У багатьох мовах програмування змінні мають те, що називається "тип даних". Два основні типи даних - це типи значень (int, float, bool, char, structure, ...) та тип посилань (екземпляр класів). У той час як типи значень містять саме значення , посилання містять адресу пам'яті, що вказує на частину пам'яті, виділену, щоб містити набір значень (аналогічно C / C ++).

Наприклад, Vector3це тип значення (структура, що містить координати та деякі функції), а компоненти, приєднані до вашого GameObject (включаючи власні скрипти, що успадковуються від MonoBehaviour), є типовим типом.

Коли я можу створити NullReferenceException?

NullReferenceException викидаються, коли ви намагаєтесь отримати доступ до посилальної змінної, яка не посилається ні на який об'єкт, отже, вона є нульовою (адреса пам'яті вказує на 0).

NullReferenceExceptionБуде піднято кілька загальних місць а :

Маніпулювання GameObject / компонент, який не вказаний у інспектора

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

Отримання компонента, який не приєднаний до GameObject, а потім, намагаючись маніпулювати ним:

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

Доступ до GameObject, який не існує:

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

Примітка: Будьте обережні, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypeповертати тільки геймобжекти, які включені в ієрархії , коли функція викликається.

Намагаючись використати результат, що повертається 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.

Доступ до елемента неініціалізованого масиву

private GameObject[] myObjects ; // Uninitialized array

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

Рідше, але дратує, якщо ви цього не знаєте про делегатів 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) ;
}

Як виправити?

Якщо ви зрозуміли попередні параграфи, ви знаєте, як виправити помилку: переконайтеся, що ваша змінна посилається (вказує на) екземпляр класу (або містить принаймні одну функцію для делегатів).

Простіше сказати, ніж зробити? Так, справді. Ось кілька порад, щоб уникнути та визначити проблему.

"Брудний" спосіб: метод спробу і лову:

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

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

Шлях «чистіший» (ІМХО): чек

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

Якщо ви зіткнулися з помилкою, яку ви не можете вирішити, завжди корисно знайти причину проблеми. Якщо ви "ліниві" (або якщо проблему можна вирішити легко), використовуйте Debug.Logдля того, щоб показати на консолі інформацію, яка допоможе вам визначити, що може спричинити проблему. Складнішим способом є використання точок зламу та налагоджувача IDE.

Використання Debug.Logє досить корисним, щоб визначити, яка функція викликається, наприклад. Особливо, якщо у вас є функція, відповідальна за ініціалізацію полів. Але не забудьте видалити їх, Debug.Logщоб уникнути захаращення вашої консолі (і з міркувань продуктивності).

Ще одна порада, не соромтеся "скоротити" функціональні дзвінки та додати, Debug.Logщоб зробити кілька перевірок.

Замість :

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

Зробіть це, щоб перевірити, чи встановлені всі посилання:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Навіть краще :

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

Джерела:

  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

Це вимагає великих зусиль для пояснення "як" діагностувати проблему. Я не вважаю , що фактична відповідь на питання «що є проблема». Це також не дозволяє вирішити відповіді, які зазвичай з'являються на подібні питання. Можливо, це буде краще в документації на StackOverflow? Можливо, ні.
Gnemlock

2
Я б не сказав, що використання журналу налагодження ліниво . Для мене набагато швидше використовувати debug.log, щоб звузити область, де виникає помилка, а потім використовувати відладчик, щоб дійсно знайти помилку. Але це завжди залежить від помилки під рукою. У будь-якому випадку, я б не сказав, що використання журналу налагодження ліниво : P
Vaillancourt

Ви також повинні зазначити, що не завжди корисно ставити чеки на нуль. Ще гіршою ідеєю було б користуватися try/catch. Помилка говорить вам багато про проблему, яка у вас є, і перш ніж початківці почнуть всюди ставити нульові перевірки, головна проблема полягає в інспекторі, коли ви забудете посилатися на якийсь об’єкт (перетягуйте об’єкт на скрипт). Я бачив безліч кодів try/catchі нульових перевірок у місцях, де це зовсім непотрібно. Налагодження та робота з таким кодом - це "біль у **". Початківці дізнаються про випадки використання цих перевірок і лише потім використовують їх.
відвертий Місяць _Max_

Я думаю, що наявність нульової перевірки може бути хорошою ідеєю, якщо явне повідомлення про налагодження буде надано в else. Маючи a NullReferenceException- це не завжди пояснюється, тоді як No Rigidbody component attached to the gameObjectбезпосередньо поясніть, що не так. Я погоджуюсь, що просто if( obj != null )повідомлення без повідомлення просто "приховує" проблему, і ви можете мати робочий проект, але не робити те, чого ви очікували, не знаючи чому.
Геліум

4

Хоча ми можемо просто зробити перевірку, щоб переконатися, що ми не намагаємося отримати нульову посилання, це не завжди підходить рішення. У програмах Unity багато разів наша проблема може випливати з того, що посилання не повинно бути нульовим. У деяких ситуаціях просто ігнорування нульових посилань може порушити наш код.

Наприклад, це може бути посилання на наш вхідний контролер. Чудово, що гра не виходить з ладу через виключення з нульовим посиланням, але нам потрібно з’ясувати, чому немає вхідного контролера, і виправити цю проблему. Без цього у нас є гра, яка може не вийти з ладу, але також не може брати участь.

Нижче я перелічу можливі причини та рішення, як я натрапляю на них в інших питаннях.


Ви намагаєтесь отримати доступ до класу "менеджер"?

Якщо ви намагаєтесь отримати доступ до класу, який діє як "менеджер" (тобто клас, який повинен колись мати лише один екземпляр), вам може бути краще використовувати підхід Singleton . До ідеального класу Singleton можна звернутися з будь-якого місця, безпосередньо, зберігаючи public staticпосилання на себе. Таким чином, Singleton може містити посилання на активний екземпляр, який був би доступний без проблем встановлювати фактичну посилання кожен раз.

Ви посилаєтесь на екземпляр свого об'єкта?

Звичайно просто позначити посилання як public, так що ми можемо встановити посилання на екземпляр через інспектора. Завжди переконайтеся , що ви б встановити посилання на екземпляр, з допомогою інспектора, так як це не рідкість , щоб пропустити цей крок.

Ви інстанціюєте свій примірник?

Якщо ми встановлюємо наш об’єкт у коді, важливо переконатися, що ми створили його об'єкт. Це можна здійснити за допомогою newключового слова та методів конструктора. Наприклад, врахуйте наступне:

private GameObject gameObject;

Ми створили посилання на a GameObject, але це ні на що не вказує. Доступ до цієї посилання таким, що є , призведе до виключення нульової посилання . Перш ніж звернутися до нашого GameObjectпримірника, ми можемо викликати метод конструктора за замовчуванням наступним чином:

gameObject = new GameObject();

Навчальний посібник Unity на заняттях пояснює практику створення та використання конструкторів.

Чи використовуєте ви GetComponent<t>()метод з припущенням, що компонент існує?

По-перше, переконайтеся, що ми завжди викликаємо, GetComponent<t>()перш ніж викликати методи з інстанції компонента.

З причин, на які не варто заходити, ми можемо припустити, що наш локальний ігровий об’єкт містить певний компонент, і спробуємо отримати доступ до нього GetComponent<t>(). Якщо локальний ігровий об’єкт не містить цього конкретного компонента, ми повернемо nullзначення.

Ви можете легко перевірити, чи є повернене значення null, перш ніж отримати доступ до нього. Однак якщо ваш ігровий об’єкт повинен мати необхідний компонент, можливо, краще переконатися, що він має принаймні версію цього компонента за замовчуванням . Ми можемо помітити тег MonoBehaviourяк, [RequireComponent(typeof(t))]щоб переконатися, що у нас завжди є тип компонента

Ось приклад MonoBehaviourігрового об’єкта, який завжди повинен містити Rigidbody. Якщо сценарій буде доданий до ігрового об’єкта, який не містить а Rigidbody, Rigidbodyбуде створено за замовчуванням .

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


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

Ви намагалися відновити свій проект?

Є деякі випадки, коли Unity може викликати проблеми, намагаючись посилатися на кешовану версію ігрового об’єкта. Відповідно до вікового рішення "вимкніть і знову", спробуйте видалити папку " Бібліотека" та знову відкрийте Unity. Єдність буде змушена відновити ваш проект. Це може вирішити кілька дуже своєрідних випадків цієї проблеми і повинно вказувати на питання, які не з'явилися в остаточному складі.


1
Я досі не впевнений, чи має це питання бути темою. Але ось вікі спільноти, щоб користувачі могли публікувати додаткові потенційні відповіді; Поки він складається з основ першої половини сторінки прийнятих відповідей на запитання, позначені єдністю та "нульовою посиланням" (що фактично відповідало критеріям запитання).
Gnemlock

-5

Я бачу, що є прийнята відповідь. Але є краща відповідь чи пропозиція для вас NullReferenceException. Якщо ви можете пов’язати програмування на мові Java, як я, ви можете запобігти надсиланню нульової помилки, використовуючи try-catchблок. Спробуйте самі! ;-)

Якщо ви використовуєте C #, перевірте, чи є using System;у верхній частині файлу сценарій. Якщо ні, додайте його. Тепер ви можете використовувати всілякі Exceptionзаняття, намагаючись вловлювати рядок коду.

Якщо ви використовуєте UnityScript, використовуйте import System;

Ось приклад:

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

        }

    }
}

Також пам'ятайте, що ви можете зловити і інші виключення , такі як MissingReferenceException, MissingComponentException, IndexOutOfRangeException, або будь-які інші класи виключень , як якщо ви дозволите using Systemв свій сценарій.

Це все.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.