NullReferenceException в Unity

11

Поскольку многие пользователи сталкиваются с NullReferenceException: Object reference not set to an instance of an objectошибкой в ​​Unity, я подумал, что было бы неплохо собрать из нескольких источников некоторые объяснения и способы исправления этой ошибки.


симптомы

Я получаю сообщение об ошибке ниже, появляющееся в моей консоли, что это значит и как я могу это исправить?

NullReferenceException: ссылка на объект не установлена ​​на экземпляр объекта

Hellium
источник
Это похоже на общий вопрос программирования, а не на разработку игр. Ответ OP на собственный вопрос включает ссылку на SO, которая охватывает эту тему.
Пикалек
3
Хотя «NullReferenceException» действительно является общим вопросом программирования, здесь этот вопрос касается конкретно исключения в Unity : где его можно встретить в программировании Unity и как их решить (см. Различные примеры).
Hellium
@Pikalek, мы также расширили наши возможности для общего программирования. Это было разъяснено, когда я спросил об этом в мета . Теперь я понимаю, что это может соответствовать параметрам «слишком общего», согласно ответу Джоша.
Гнемлок
В текущем ответе также не упоминается ничего специфического для Unity (кроме использования специфических типов Unity в примерах). На самом деле это общий ответ программирования. Мы не используем ответы в тесных аргументах, но, учитывая, что это ответ на себя, он идет в поддержку аргумента намерения.
Гнемлок
3
У Unity есть несколько уникальных / характерных способов вызвать эти ошибки с помощью неназначенных полей инспектора, неудачных попыток GetComponent или Find или с помощью варианта варианта «MissingReferenceException», когда у вас была допустимая ссылка, но он получил Destroy () ed. Поэтому я думаю, что ответы на этот вопрос в контексте Unity имеют хороший потенциал, чтобы быть полезными для сообщества, даже если само исключение носит очень общий характер.
DMGregory

Ответы:

14

Тип значения против Тип ссылки

Во многих языках программирования переменные имеют так называемый «тип данных». Двумя основными типами данных являются типы значений (int, float, bool, char, struct, ...) и ссылочный тип (экземпляр классов). В то время как типы значений содержат само значение , ссылки содержат адрес памяти, указывающий на часть памяти, выделенную для хранения набора значений (аналогично C / C ++).

Например, Vector3это тип значения (структура, содержащая координаты и некоторые функции), в то время как компоненты, прикрепленные к вашему GameObject (включая ваши собственные сценарии, наследуемые от MonoBehaviour), являются ссылочным типом.

Когда я могу получить исключение NullReferenceException?

NullReferenceException сбрасываются, когда вы пытаетесь получить доступ к ссылочной переменной, которая не ссылается на какой-либо объект, поэтому она имеет значение null (адрес памяти указывает на 0).

Некоторые общие места NullReferenceExceptionбудут подняты:

Управление GameObject / Component, который не был указан в инспекторе

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

Как исправить ?

Если вы поняли предыдущие параграфы, вы знаете, как исправить ошибку: убедитесь, что ваша переменная ссылается (указывает на) на экземпляр класса (или содержит хотя бы одну функцию для делегатов).

Проще сказать, чем сделать? Да, в самом деле. Вот несколько советов, чтобы избежать и определить проблему.

«Грязный» способ: метод try & catch:

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
Hellium
источник
Это требует значительных усилий для объяснения того, как диагностировать проблему. Я не считаю , что фактический ответ на вопрос «что есть проблема». Здесь также не рассматриваются ответы, которые обычно появляются на вопросы такого рода. Возможно, это было бы лучше в документации StackOverflow? Возможно нет.
Гнемлок
2
Я бы не сказал, что использование журнала отладки лениво . Для меня гораздо быстрее использовать debug.log, чтобы сузить область, в которой происходит ошибка, а затем использовать отладчик, чтобы действительно найти ошибку. Но это всегда зависит от ошибки под рукой. В любом случае, я бы не сказал, что использование журнала отладки лениво : P
Vaillancourt
Вы должны были также указать, что не всегда хорошая идея ставить проверки на нуль. Еще хуже идея будет использовать try/catch. Ошибка многое говорит вам о вашей проблеме, и прежде чем новички начнут везде ставить нулевые проверки, ваша главная проблема в инспекторе, поскольку вы забываете ссылаться на некоторый объект (перетащите объект в скрипт). Я видел много кода с try/catchпустыми проверками в местах, где это совершенно не нужно. Отладка и работа с подобным кодом - это «боль в а **». Новички узнают о случаях использования этих проверок и только затем используют их.
Candid Moon _Max_
Я думаю, что наличие нулевой проверки может быть хорошей идеей, если в else. Наличие 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. Unity будет вынуждена пересобрать ваш проект. Это может решить некоторые очень специфические случаи этой проблемы и должно указывать на проблемы, которые не возникнут в окончательной сборке.

Gnemlock
источник
1
Я все еще не уверен, должен ли этот вопрос быть по теме. Но вот вики-сообщество для пользователей, чтобы публиковать дополнительные потенциальные ответы; Пока что он состоит из основ первой половины страницы принятых ответов на вопросы, обозначенные как « единство» и «нулевая ссылка» (которые фактически соответствовали критериям вопроса).
Гнемлок
-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в свой сценарий.

Это все.

Дэвид Дималанта
источник
2
Метод попробовать и улов был описан в общепринятом ответ ....
Hellium