Unity中的NullReferenceException


11

由于许多用户都面临NullReferenceException: Object reference not set to an instance of an objectUnity中的错误,我认为从多个来源收集一些解释和解决此错误的方法是一个好主意。


病征

我的控制台中出现以下错误,这是什么意思,该如何解决?

NullReferenceException:对象引用未设置为对象的实例


这似乎是一个一般的编程问题,而不是特定于游戏开发人员的。OP对自己的问题的答案包括一个涵盖此主题的SO链接。
皮卡列克'17

3
尽管“ NullReferenceException”确实是一个通用的编程问题,但是在这里,该问题专门涵盖了Unity中的异常:在Unity编程中会在哪里遇到异常以及如何解决它们(请参见各种示例)。
Hellium'2

@Pikalek,我们也扩大了我们在通用编程方面所允许的范围。当我在meta中询问此问题时,这一点已得到澄清。现在我意识到,按照乔什的回答,这可能仍然适合“太通用”的参数。
Gnemlock '17

当前答案也没有说明任何特定于Unity的内容(除了在示例中使用特定于Unity的类型)。实际上,它是一个通用的编程响应。我们不会在接近的论点中使用答案,但是考虑到它是一个自我答案,它的确可以支持意图论证。
Gnemlock '17

3
当您拥有有效的引用但获得了Destroy()时,Unity通过未分配的Inspector字段,失败的GetComponent或Find尝试,或通过其变体形式“ MissingReferenceException”触发这些错误的几种独特/特征方式。因此,我认为,即使Exception本身非常普遍,在Unity上下文中对该问题的答案也很有可能对社区有用。
DMGregory

Answers:


14

值类型与参考类型

在许多编程语言中,变量具有所谓的“数据类型”。两种主要的数据类型是值类型(int,float,bool,char,struct,...)和引用类型(类的实例)。值类型包含值本身时,引用包含一个内存地址,该内存地址指向分配来包含一组值的内存部分(类似于C / C ++)。

例如,Vector3是值类型(包含坐标和一些函数的结构),而与GameObject相连的组件(包括从中继承的自定义脚本MonoBehaviour)是引用类型。

什么时候可以有NullReferenceException?

NullReferenceException 当您尝试访问未引用任何对象的引用变量时抛出,因此该变量为null(内存地址指向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.FindGameObject.FindWithTagGameObject.FindObjectOfType只返回被gameObjects 启用层次结构中,当函数被调用。

尝试使用返回的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.

访问未初始化数组的元素

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/zh-CN/articles/206369473-NullReferenceException
  4. https://unity3d.com/fr/learn/tutorials/topics/scripting/data-types

这将花费大量的精力来解释诊断问题的“方法”。我不认为这是“什么问题” 的实际答案。这也无法解决通常出现在此类问题上的答案。也许这比StackOverflow文档更好?也许不是。
Gnemlock '17

2
我不会说使用调试日志是懒惰的。对我来说,使用debug.log缩小发生错误的范围的速度要快得多然后使用调试器真正地查找错误。但这始终取决于手头的错误。无论如何,我不会说使用调试日志是懒惰的:P
Vaillancourt

您还应该指出,对null进行检查并非总是一个好主意。更糟糕的主意是使用try/catch。该错误告诉您很多有关那里存在的问题的信息,在初学者开始对任何地方进行空检查之前,您的主要问题是在检查器中,因为您忘记了引用某个对象(将对象拖到脚本上)。我已经在很多try/catch不必要的地方看到了很多带有null检查的代码。调试和使用这样的代码是“ a **中的痛苦”。初学者了解这些检查的用例,然后才使用它们。
坦率的月亮_Max_

我认为,如果在中提供了明确的Debug消息,则进行null检查可能是一个好主意else。在直接解释问题的NullReferenceException同时,拥有a 并不总是自我解释No Rigidbody component attached to the gameObject。我同意,只有if( obj != null )不带消息只会“隐藏”问题,并且您可以拥有一个正在运行的项目,但在不知道为什么的情况下就无法做自己希望做的事情。
Hellium '18

4

尽管我们可以轻松地进行检查以确保我们不尝试访问空引用,但这并不总是合适的解决方案。很多时候,在Unity编程中,我们的问题可能源于引用不应为null 的事实。在某些情况下,仅忽略空引用可能会破坏我们的代码。

例如,它可能是对我们的输入控制器的引用。很好的是,游戏不会由于空引用异常而崩溃,但是我们需要弄清楚为什么没有输入控制器,然后解决问题。没有它,我们的游戏可能不会崩溃,但也无法接受输入。

下面,当我在其他问题中遇到它们时,我将列出可能的原因和解决方案。


您是否要访问“经理”类?

如果您尝试访问充当“经理”的类(即,一次只能运行一个实例的类),那么使用Singleton方法可能会更好。理想情况下,通过保留public static对自身的引用,可以从任何地方直接访问Singleton类。这样,单例可以包含对活动实例的引用,可以访问该实例,而不必每次都设置实际引用。

您是否在引用对象的实例?

通常将引用标记为public,这很常见,因此我们可以通过检查器将引用设置为实例。经常检查你通过检查器设置了对实例的引用,因为经常会错过此步骤。

您要实例化实例吗?

如果要在代码中设置对象,那么确保实例化该对象很重要。这可以使用new关键字和构造函数方法来执行。例如,考虑以下内容:

private GameObject gameObject;

我们创建了对的引用GameObject,但未指向任何内容。按原样访问此引用将导致null引用异常。在引用GameObject实例之前,我们可以按如下方式调用默认的构造方法:

gameObject = new GameObject();

关于类的Unity教程介绍了创建和使用构造函数的实践。

您是否GetComponent<t>()在假设组件存在的情况下使用该方法?

首先,请确保我们始终致电 GetComponent<t>()在调用组件实例的方法之前先进行调用。

出于不值得探讨的原因,我们可以假定本地游戏对象包含特定组件,并尝试使用进行访问GetComponent<t>()。如果本地游戏对象并没有包含具体的组成部分,我们将返回一个null值。

null在访问它之前,您可以轻松地检查返回值是否为。但是,如果您的游戏对象应该具有必需的组件,则最好确保它至少具有该组件的默认版本。我们可以标记为MonoBehaviouras [RequireComponent(typeof(t))]以确保我们始终具有该类型的组件。

这是MonoBehaviour一个游戏对象的示例,该对象应始终包含Rigidbody。如果将脚本添加到包含的游戏对象中,则会创建Rigidbody默认值Rigidbody

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


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

您是否尝试过重建项目?

在某些情况下,Unity可能会通过尝试引用游戏对象的缓存版本来引起问题。与古老的“关闭并重新打开”解决方案一致,尝试删除您的Library文件夹,然后重新打开Unity。Unity将被迫重新构建您的项目。这可以解决此问题的一些非常特殊的情况,并且应指出最终版本中不会出现的问题。


1
我仍然不确定这个问题是否应该成为话题。但是,这里有一个社区Wiki,供用户发布其他潜在答案。到目前为止,它是一个由的基础知识第一的半页接受答案的问题打上统一“空引用”(实际遇到的问题的标准)。
Gnemlock '17

-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

        }

    }
}

还记得,你还可以赶上其他异常,如MissingReferenceExceptionMissingComponentExceptionIndexOutOfRangeException,或任何其他异常类,只要你有using System你的脚本。

就这些。


2
在尝试和捕获方法已接受的答案被描述....
Hellium
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.