NullReferenceException trong Unity


11

Vì nhiều người dùng đang phải đối mặt với NullReferenceException: Object reference not set to an instance of an objectlỗi trong Unity, tôi nghĩ rằng sẽ tốt hơn nếu thu thập từ nhiều nguồn một số giải thích và cách khắc phục lỗi này.


Triệu chứng

Tôi nhận được lỗi bên dưới xuất hiện trong bảng điều khiển của tôi, nó có nghĩa là gì và làm cách nào để khắc phục nó?

NullReferenceException: Tham chiếu đối tượng không được đặt thành phiên bản của đối tượng


Đây có vẻ như là một câu hỏi lập trình chung & không phải trò chơi dev cụ thể. Câu trả lời của OP cho câu hỏi riêng bao gồm một liên kết đến SO bao gồm chủ đề này.
Pikalek

3
Trong khi "NullReferenceException" thực sự là một câu hỏi lập trình chung, thì câu hỏi này bao gồm ngoại lệ cụ thể trong Unity : nó có thể gặp ở đâu trong lập trình Unity và cách giải quyết chúng (xem các ví dụ khác nhau).
Hellium

@Pikalek, chúng tôi cũng đã mở rộng phạm vi của chúng tôi cho những gì chúng tôi cho phép về mặt lập trình chung. Điều này đã được làm rõ khi tôi hỏi về nó trong meta . Bây giờ, tôi nhận ra rằng điều này vẫn có thể phù hợp với các tham số 'quá chung chung', theo câu trả lời của Josh.
Gnemlock

Câu trả lời hiện tại cũng không ghi chú bất cứ điều gì cụ thể cho Unity (ngoài việc sử dụng các loại cụ thể của Unity trong các ví dụ). Trên thực tế, đó là một phản ứng lập trình chung. Chúng tôi không sử dụng câu trả lời trong các cuộc tranh luận chặt chẽ, nhưng cho rằng đó là một câu trả lời tự, nó đi theo hướng ủng hộ lập luận về ý định.
Gnemlock

3
Unity có một vài cách duy nhất / đặc trưng để kích hoạt các lỗi này, thông qua các trường Inspector chưa được gán, các lần thử GetComponent hoặc Find không thành công hoặc thông qua hương vị biến thể của nó "MissingReferenceException" khi bạn có một tham chiếu hợp lệ nhưng nó đã bị phá hủy (). Vì vậy, tôi nghĩ rằng câu trả lời cho câu hỏi này trong bối cảnh Unity có tiềm năng tốt để hữu ích cho cộng đồng, ngay cả khi bản thân Ngoại lệ rất chung chung.
DMGregory

Câu trả lời:


14

Loại giá trị so với loại tham chiếu

Trong nhiều ngôn ngữ lập trình, các biến có cái gọi là "kiểu dữ liệu". Hai kiểu dữ liệu chính là kiểu giá trị (int, float, bool, char, struct, ...) và kiểu tham chiếu (thể hiện của các lớp). Mặc dù các loại giá trị chứa chính giá trị , các tham chiếu chứa địa chỉ bộ nhớ trỏ đến một phần bộ nhớ được phân bổ để chứa một tập hợp các giá trị (tương tự như C / C ++).

Ví dụ: Vector3là một loại giá trị (một cấu trúc có chứa tọa độ và một số hàm) trong khi các thành phần được đính kèm với GameObject của bạn (bao gồm các tập lệnh tùy chỉnh kế thừa từ MonoBehaviour) là loại tham chiếu.

Khi nào tôi có thể có NullReferenceException?

NullReferenceException được ném khi bạn cố truy cập vào một biến tham chiếu không tham chiếu đến bất kỳ đối tượng nào, do đó nó là null (địa chỉ bộ nhớ được trỏ đến 0).

Một số nơi phổ biến NullReferenceExceptionsẽ được nâng lên:

Thao tác với GameObject / Thành phần chưa được chỉ định trong thanh tra

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

Lấy một thành phần không được đính kèm với GameObject và sau đó, cố gắng thao túng nó:

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

Truy cập GameObject không tồn tại:

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

Lưu ý: Hãy cẩn thận, GameObject.Find, GameObject.FindWithTag, GameObject.FindObjectOfTypechỉ trả lại gameObjects được kích hoạt trong hệ thống phân cấp khi hàm được gọi.

Cố gắng sử dụng kết quả của một getter đang trở lại 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.

Truy cập một phần tử của mảng không khởi tạo

private GameObject[] myObjects ; // Uninitialized array

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

Ít phổ biến hơn, nhưng gây phiền nhiễu nếu bạn không biết về đại biểu 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) ;
}

Làm thế nào để khắc phục ?

Nếu bạn đã hiểu các đoạn trước, bạn sẽ biết cách sửa lỗi: đảm bảo biến của bạn đang tham chiếu (trỏ đến) một thể hiện của một lớp (hoặc chứa ít nhất một hàm cho các đại biểu).

Nói dễ hơn làm? Vâng, thực sự. Dưới đây là một số lời khuyên để tránhxác định vấn đề.

Cách "bẩn": Phương pháp thử & bắt:

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

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

Cách "sạch" hơn (IMHO): Kiểm tra

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

Khi gặp lỗi bạn không thể giải quyết, luôn luôn là một ý tưởng tốt để tìm ra nguyên nhân của vấn đề. Nếu bạn "lười biếng" (hoặc nếu vấn đề có thể được giải quyết dễ dàng), hãy sử dụng Debug.Logđể hiển thị trên thông tin bảng điều khiển sẽ giúp bạn xác định những gì có thể gây ra sự cố. Một cách phức tạp hơn là sử dụng Breakpoint và Debugger của IDE của bạn.

Sử dụng Debug.Loglà khá hữu ích để xác định chức năng nào được gọi đầu tiên chẳng hạn. Đặc biệt là nếu bạn có một chức năng chịu trách nhiệm khởi tạo các trường. Nhưng đừng quên loại bỏ chúng Debug.Logđể tránh làm lộn xộn bảng điều khiển của bạn (và vì lý do hiệu suất).

Một lời khuyên khác, đừng ngần ngại "cắt" các cuộc gọi chức năng của bạn và thêm Debug.Logđể thực hiện một số kiểm tra.

Thay vì :

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

Làm điều này để kiểm tra nếu mọi tham chiếu được đặt:

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

Debug.Log( myObject ) ;

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

Debug.Log( superComponent ) ;

superComponent.value = "foo" ;

Thậm chí còn tốt hơn :

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

Nguồn:

  1. http://answers.unity3d.com/questions/47830/what-is-a-null-reference-exception-in-unity.html
  2. /programming/218384/what-is-a-nullpulumexception-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

Điều này đi đến rất nhiều nỗ lực để giải thích "làm thế nào" để chẩn đoán vấn đề. Tôi sẽ không xem xét rằng một câu trả lời thực sự cho câu hỏi " vấn đề gì ". Điều này cũng không giải quyết được các câu trả lời thường xuất hiện trên các loại câu hỏi này. Có lẽ điều này sẽ tốt hơn ở tài liệu StackOverflow? Có lẽ không.
Gnemlock

2
Tôi sẽ không nói rằng sử dụng nhật ký gỡ lỗi là lười biếng . Đối với tôi, việc sử dụng debug.log sẽ nhanh hơn rất nhiều để thu hẹp phạm vi xảy ra lỗi, sau đó sử dụng trình gỡ lỗi để thực sự tìm ra lỗi. Nhưng nó luôn phụ thuộc vào lỗi trong tầm tay. Trong mọi trường hợp, tôi sẽ không nói sử dụng nhật ký gỡ lỗi là lười biếng : P
Vaillancourt

Bạn cũng nên chỉ ra rằng không phải lúc nào cũng nên kiểm tra null. Thậm chí ý tưởng tồi tệ hơn sẽ được sử dụng try/catch. Lỗi cho bạn biết rất nhiều về vấn đề bạn gặp phải và trước khi người mới bắt đầu đặt kiểm tra null ở mọi nơi, vấn đề chính của bạn là ở thanh tra khi bạn quên tham chiếu một số đối tượng (kéo đối tượng vào tập lệnh). Tôi đã thấy rất nhiều mã với try/catchvà kiểm tra null ở những nơi hoàn toàn không cần thiết. Gỡ lỗi và làm việc với một mã như thế là "một nỗi đau trong **". Người mới bắt đầu tìm hiểu về các trường hợp sử dụng của các kiểm tra đó và chỉ sau đó sử dụng chúng.
Mặt trăng thí sinh _Max_

Tôi nghĩ rằng có một kiểm tra null có thể là một ý tưởng tốt nếu một thông báo Debug rõ ràng được cung cấp trong else. Có một NullReferenceExceptionkhông phải luôn luôn tự giải thích trong khi No Rigidbody component attached to the gameObjecttrực tiếp giải thích những gì sai. Tôi đồng ý rằng chỉ cần có if( obj != null )tin nhắn mà không "che giấu" vấn đề, và bạn có thể có một dự án làm việc nhưng không làm những gì bạn mong đợi mà không biết tại sao.
Hellium

4

Mặc dù chúng tôi có thể dễ dàng thực hiện kiểm tra để đảm bảo chúng tôi không cố gắng truy cập tài liệu tham khảo null, nhưng đây không phải lúc nào cũng là một giải pháp phù hợp. Nhiều lần, trong lập trình Unity, vấn đề của chúng tôi có thể xuất phát từ thực tế là tài liệu tham khảo không nên rỗng. Trong một số tình huống, chỉ cần bỏ qua các tham chiếu null có thể phá vỡ mã của chúng tôi.

Ví dụ, nó có thể là một tham chiếu đến bộ điều khiển đầu vào của chúng tôi. Thật tuyệt khi trò chơi không gặp sự cố do ngoại lệ tham chiếu null, nhưng chúng ta cần tìm hiểu tại sao không có bộ điều khiển đầu vào và khắc phục vấn đề đó . Không có nó, chúng tôi có một trò chơi có thể không bị sập, nhưng cũng không thể lấy đầu vào.

Dưới đây, tôi sẽ liệt kê các lý do và giải pháp có thể, khi tôi gặp chúng trong các câu hỏi khác.


Bạn đang cố gắng truy cập một lớp "người quản lý"?

Nếu bạn cố gắng truy cập một lớp hoạt động như một "người quản lý" (nghĩa là một lớp chỉ nên có một cá thể chạy tại một thời điểm), bạn có thể sử dụng phương pháp Singleton tốt hơn . Một lớp Singleton lý tưởng có thể được truy cập từ bất cứ đâu, trực tiếp, bằng cách giữ một public statictham chiếu đến chính nó. Theo cách này, một Singleton có thể chứa một tham chiếu đến thể hiện hoạt động, có thể truy cập được mà không gặp sự cố khi thiết lập tham chiếu thực tế mỗi lần.

Bạn đang tham khảo ví dụ về đối tượng của bạn?

Thông thường chỉ đơn giản là đánh dấu một tham chiếu là public, vì vậy chúng ta có thể đặt tham chiếu đến thể hiện thông qua trình kiểm tra. Luôn kiểm tra xem bạn đã đặt tham chiếu đến một thể hiện, thông qua trình kiểm tra, vì sẽ không có gì lạ khi bỏ qua bước này.

Bạn đang khởi tạo ví dụ của bạn?

Nếu chúng ta đang thiết lập đối tượng của mình trong mã, điều quan trọng là phải đảm bảo rằng chúng ta khởi tạo đối tượng. Điều này có thể được thực hiện bằng cách sử dụng newtừ khóa và các phương thức constructor. Ví dụ, hãy xem xét những điều sau đây:

private GameObject gameObject;

Chúng tôi đã tạo một tham chiếu đến một GameObject, nhưng nó không chỉ ra bất cứ điều gì. Truy cập tài liệu tham khảo này sẽ dẫn đến một ngoại lệ tham chiếu null . Trước khi chúng ta tham chiếu GameObjectthể hiện của mình, chúng ta có thể gọi một phương thức constructor mặc định như sau:

gameObject = new GameObject();

Hướng dẫn Unity về các lớp giải thích việc thực hành tạo và sử dụng các hàm tạo.

Bạn đang sử dụng GetComponent<t>()phương thức với giả định rằng thành phần tồn tại?

Đầu tiên, đảm bảo rằng chúng ta luôn gọi GetComponent<t>()trước khi chúng ta gọi các phương thức từ thể hiện thành phần.

Vì những lý do không đáng để tham gia, chúng tôi có thể giả sử đối tượng trò chơi cục bộ của chúng tôi có chứa một thành phần cụ thể và cố gắng truy cập nó GetComponent<t>(). Nếu đối tượng trò chơi cục bộ không chứa thành phần cụ thể đó, chúng tôi sẽ trả về một nullgiá trị.

Bạn có thể dễ dàng kiểm tra nếu giá trị trả về là null, trước khi truy cập nó. Tuy nhiên, nếu đối tượng trò chơi của bạn nên có thành phần bắt buộc, có thể tốt hơn để đảm bảo rằng ít nhất nó có phiên bản mặc định của thành phần đó. Chúng tôi có thể tag một MonoBehaviour[RequireComponent(typeof(t))]để đảm bảo chúng tôi luôn có mà loại thành phần.

Dưới đây là một ví dụ về một MonoBehaviourđối tượng trò chơi phải luôn chứa a Rigidbody. Nếu tập lệnh được thêm vào đối tượng trò chơi không chứa a Rigidbody, mặc định Rigidbodysẽ được tạo.

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


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

Bạn đã cố gắng xây dựng lại dự án của bạn?

Có một số trường hợp Unity có thể gây ra sự cố bằng cách thử tham chiếu phiên bản được lưu trong bộ nhớ cache của đối tượng trò chơi. Để phù hợp với giải pháp "tắt và bật lại" cũ, hãy thử xóa thư mục Thư viện của bạn và mở lại Unity. Unity sẽ buộc phải xây dựng lại dự án của bạn. Điều này có thể giải quyết một số trường hợp rất đặc biệt của vấn đề này và nên chỉ ra những vấn đề không xuất hiện trong bản dựng cuối cùng.


1
Tôi vẫn không chắc chắn nếu câu hỏi này nên được về chủ đề. Nhưng đây là một wiki cộng đồng để người dùng đăng các câu trả lời tiềm năng bổ sung; Cho đến nay, nó bao gồm những điều cơ bản của nửa trang đầu của câu trả lời được chấp nhận cho các câu hỏi được đánh dấu là sự thống nhất"tham chiếu null" (thực sự đáp ứng các tiêu chí của câu hỏi).
Gnemlock

-5

Tôi thấy rằng có một câu trả lời được chấp nhận. Nhưng, có một câu trả lời hoặc đề nghị tốt hơn cho bạn để xử lý NullReferenceException. Nếu bạn có thể liên kết lập trình bằng ngôn ngữ Java như tôi, bạn có thể ngăn không gửi lỗi null bằng cách sử dụng try-catchkhối. Hãy thử nó cho chính mình! ;-)

Nếu bạn đang sử dụng trong C #, hãy kiểm tra xem bạn có using System;ở đầu tệp tập lệnh không. Nếu không, thêm nó. Bây giờ, bạn có thể sử dụng tất cả các loại Exceptionlớp trong khi thử bắt một dòng mã.

Nếu bạn đang sử dụng UnityScript, hãy sử dụng import System;

Đây là một ví dụ:

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

        }

    }
}

Cũng nên nhớ, bạn có thể bắt cũng ngoại lệ khác như MissingReferenceException, MissingComponentException, IndexOutOfRangeException, hoặc bất kỳ lớp học ngoại lệ khác miễn là bạn bao gồm using Systemtrong kịch bản của bạn.

Đó là tất cả.


2
Phương pháp thử và bắt đã được mô tả trong câu trả lời được chấp nhận ....
Hellium
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.