transform.Find() sends null value的解决方案

转自:https://stackoverflow.com/questions/39548422/transform-find-sends-null-value

This is because you are calling the Unity API functions from a constructor function. Basically, you are doing this during deserialization and in another Thread.

In Unity 5.3.4f1 and below, the Find function will silently fail when called from a constructor and you won’t know. This is one of the mistakes that is complicated to track in Unity.

In Unity 5.4 and above, Unity decided to add error message to alert you about this problem. You won’t see it now because you are still using 5.3. The error is as fellow:

FindGameObjectWithTag is not allowed to be called from a MonoBehaviour constructor (or instance field initializer), call it in Awake or Start instead. Called from MonoBehaviour ‘Card’ on game object ‘Cube’.
Similar error message will appear when the Find function is called in a constructor function.

Continue reading for more descriptive information and solution:

Inheriting from MonoBehaviour vs not inheriting from MonoBehaviour

Inheriting from MonoBehaviour:

1.You can attach the script to a GameObject.

2.You can’t use the new keyword to create a new instance of a script that inherits from MonoBehaviour. Your deckOfCards.Add(new Card(i, j)); is wrong in this case since Card inherits from MonoBehaviour.

3.You use gameobject.AddComponent() or the Instantiate(clone prefab) function to create new instance of script. There is an example at the end of this.

Rules for using a constructor function in Unity:

1.Do not use a constructor in a script that inherits from MonoBehaviour unless you understand what’s going on under the hood in Unity.

2.If you are going to use a constructor, do not inherit the script from MonoBehaviour.

3.If you break Rule #2, do not use any Unity API in a constructor function of a class that inherits from MonoBehaviour.

Why?

You cannot call Unity API from another Thread. It will fail. You will either get an exception or it will silently fail.

What does this have to do with Threads?

A constructor function is called from another Thread in Unity.

When a script is attached to a GameObject and that script inherits from MonoBehaviour and has a constructor, that constructor is first called from Unity’s main Thread(which is fine) then it is called again from another Thread (non Unity’s main Thread). This breaks rule #3. You cannot use Unity API from another function.

You can prove this by running the code below:

using UnityEngine;
using System.Threading;

public class Card : MonoBehaviour
{
    public Card()
    {
        Debug.Log("Constructor Thread ID: " + Thread.CurrentThread.ManagedThreadId);
    }

    void Start()
    {
        Debug.Log("Start() function Thread ID: " + Thread.CurrentThread.ManagedThreadId);
    }
    // Update is called once per frame
    void Update()
    {
        Debug.Log("Update() function Thread ID: " + Thread.CurrentThread.ManagedThreadId);
    }
}

Output when attached to a GameObject:
Constructor Thread ID: 1

Constructor Thread ID: 20

Start() function Thread ID 1

Update() function Thread ID: 1
As you can see, the Start() and Update() functions are called from the-same Thread (ID 1)which is the main Thread. The Constructor function is also called from the main Thread but then called again from another Thread (ID 20).

Example of BAD code: Because there is a constructor in a script that inherits from MonoBehaviour. Also bad because new instance is created with the new keyword.

public class Card : MonoBehaviour
{
    Text topText;

   //Bad, because  `MonoBehaviour` is inherited
    public Card()
    {
        topText = GameObject.FindGameObjectWithTag("topText").GetComponent<Text>();
    }
}

then creating new instance with the new keyword:

Card card = new Card(); //Bad, because MonoBehaviour is inherited

Example of Good code:

public class Card : MonoBehaviour
{
    Text topText;

    public Awake()
    {
        topText = GameObject.FindGameObjectWithTag("topText").GetComponent<Text>();
    }
}

then creating new instance with the AddComponent function:

Card card = gameObject.AddComponent()

OR clone from prefab with the Instantiate function:

public Card cardPrfab;
Card card = (Card)Instantiate(cardPrfab);

Not inheriting from MonoBehaviour:

1.You can’t attach the script to a GameObject but you can use it from another script.

2.You can simply use the new keyword to create new instance of the script when it doesn’t inherit from MonoBehaviour.

public class Card
{
    Text topText;
    //Constructor

   //Correct, because no `MonoBehaviour` inherited
    public Card()
    {
        topText = GameObject.FindGameObjectWithTag("topText").GetComponent<Text>();
    }
}

Then you can create new instance with the new keyword like:

Card card = new Card(); //Correct, because no MonoBehaviour inherited

Solution:

1.If you decide to inherit from MonoBehaviour and have to attach the script to a GameObject , you must remove all your constructor functions and put code inside them into Awake() or Start() function. The Awake() and Start() functions are automatically called by Unity once and you use them initialize your variables. You don’t have to call them manually. Do not use the new keyword to create instance of scripts that inherit from MonoBehaviour.

2.If you decide not to inherit from MonoBehaviour and you are not required to attach the script to a GameObject, you can have a constructor function like you did in your current code and you can use Unity’s API in those constructor functions.You can now use new keyword to create instance of the script since it doesn’t inherit from MonoBehaviour.

猜你喜欢

转载自blog.csdn.net/dreamiond/article/details/73039550