Transform.Find 发送空值

Transform.Find sends null value

我正在制作一个简单的 deck/card 项目。我的项目有一个卡片预制件,带有一个 Canvas 子项,其中包含 suit/card 信息的 UI 版本。一张卡可以显示信息,多张卡显示有问题。我对此事的理论是由于 GameObject.FindGameObjectWithTag(string) 找到了包含标签的 gameObject 的第一个实例。所以,当我抽多张牌时,它只会重写第一张牌,而不是抽其他牌。

我已尝试 Transform.Find(string),但它已变为空,尽管在编辑器中设置了游戏对象。一种解决方案是在名称后使用多个带有数字的标签,如 topNum1、topNum2 等,但是对 52 个不同的数字重复 3 次,每个都听起来非常重复且令人沮丧。有更好的方法吗?

卡片代码如下:

using UnityEngine;
using System.Collections;
using UnityEngine.UI;

/// <summary>
/// This class sets up the card object, which can be used in a variety of games.
/// </summary>

public class Card: MonoBehaviour
{

int cardNum;//The Value of the card.
int cardType;//The ‘Suit’ of the card. Hearts, Spades, Diamonds, Clubs
//Text topText;
Text botText;
Text faceText;
Text topText;

//Creates a default card.
public Card()
{
    cardNum = -1;
    cardType = -1;
    topText = GameObject.FindGameObjectWithTag("topText").GetComponent<Text>();
    //topText = gameObject.transform.FindChild("Canvas").gameObject.transform.FindChild("TopCardValue").GetComponent<Text>();
    botText = GameObject.FindGameObjectWithTag("botText").GetComponent<Text>();
    faceText = GameObject.FindGameObjectWithTag("faceText").GetComponent<Text>();
}
//Creates a custom card, with the provided values.
public Card(int cN, int cT)
{
    cardNum = cN;
    cardType = cT;
    topText = GameObject.FindGameObjectWithTag("topText").GetComponent<Text>();
    //topText = gameObject.transform.FindChild("Canvas").gameObject.transform.FindChild("TopCardValue").GetComponent<Text>();
    botText = GameObject.FindGameObjectWithTag("botText").GetComponent<Text>();
    faceText = GameObject.FindGameObjectWithTag("faceText").GetComponent<Text>();
}

//returns the card’s value.
public int getCardNum()
{
    return cardNum;
}

//returns the card’s suit.
public int getCardType()
{
    return cardType;
}

//Sets the card’s value.
public void setCardNum(int newNum)
{
    cardNum = newNum;
}

//Sets the card’s suit.
public void setCardType(int newType)
{
    cardType = newType;
}

//Checks if the card’s value is a face card (Jack, Queen, King, or Ace)
public bool checkIfFace()
{
    if (getCardNum() > 10 && getCardNum() < 15 || getCardNum() == 0)
        return true;
    else
        return false;
}

//Checks if the card is a valid card.
public bool checkifValid()
{
    if (getCardType() < -1 || getCardType() > 4)
    {
        Debug.LogError("Error: Card Type not valid. Card type is: " + getCardType());
        return false;
    }
    if (getCardNum() < 0 || getCardNum() > 15)
    {
        Debug.LogError("Error: Card Value not valid. Card value is : " + getCardNum());
        return false;
    }
    return true;
}
//Prints out the card information.
public void printOutCardInfo()
{
    string value = "";
    string suit = "";


    if (getCardNum() == 1)
        value = (" Ace");
    else if (getCardNum() > 1 && getCardNum() < 11)
        value = (getCardNum().ToString());
    else if (getCardNum() == 11)
       value = ("Jack");
    else if (getCardNum() == 12)
       value = ("Queen");
    else if (getCardNum() == 13)
       value = ("King");
    else
        Debug.LogError("Error: No Num Found! The number in question is: " + getCardNum());
    switch(getCardType())
    {
        case 0:
            suit = ("Hearts");
            break;
        case 1:
            suit = ("Spades");
            break;
        case 2:
            suit = ("Diamonds");
            break;
        case 3:
            suit = ("Clubs");
            break;
        default:
            Debug.LogError("Error: Suit not found.");
            break;
    }
    topText.text = value;
    botText.text = value;
    faceText.text = suit;
}
}

我们将不胜感激任何帮助。谢谢你的时间。

编辑:由于有几个人要求,我编辑了这个问题以包含调用此 class 的代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

public class ListDeck : MonoBehaviour
{

    //Card[] deckOfCards;
    List<Card> deckOfCards;

   // public GameObject spawner;
    //public GameObject card;

    //The default constructor.
    public ListDeck()
    {
        deckOfCards = new List<Card>();
        setUpDeck(4, 14);
        randomizeDeck();
    }

    //Sets up the deck
    public void setUpDeck(int numSuits, int numValues)
    {
        int counter = 0;

        for (int i = 1; i < numValues; i++)//the thirteen values.
        {
            for (int j = 0; j < numSuits; j++)//The four suits. 
            {
                Debug.Log("I value: " + i + " j value: " + j);
                deckOfCards.Add(new Card(i, j));
                counter++;//Increments the counter.
            }
        }
    }
    //Randomizes the deck so that the card dealout is random.
    //http://answers.unity3d.com/questions/486626/how-can-i-shuffle-alist.html
    public void randomizeDeck()
    {
        for (int i = 0; i < deckOfCards.Count; i++)
        {
            Debug.Log(i);
            Card temp = deckOfCards[i];
            int randomIndex = Random.Range(i, deckOfCards.Count);
            deckOfCards[i] = deckOfCards[randomIndex];
            deckOfCards[randomIndex] = temp;
        }
    }
    //Prints out the deck for the game.
    public void printOutDeck()
    {
        for (int i = 0; i < deckOfCards.Count; i++)
        {
            Debug.Log("Card " + i + ": ");
            deckOfCards[i].printOutCardInfo();
        }
    }

    public List<Card> getDeck()
    {
        return deckOfCards;
    }

    public void transferCards(List<Card> deckTo, int numCards)
    {

        for (int i = 0; i < numCards; i++)
        {
            deckTo.Add(deckOfCards[0]);
            deckOfCards.RemoveAt(0);
        }
    }
}

根据我对代码和问题的理解,每张卡片都从一个相同的游戏对象中获取文本,因为所有卡片都通过文本搜索相同的对象。 我相信如果您在每个对象的 Start() 方法中实际初始化您想要获得的值,您的问题可能会得到解决。

因此与绘制卡片相反,每个正在绘制的游戏对象都会收到相同的文本。

正如比我更快的回答中所述,尽量不要使用 Find(),因为当涉及到更多卡片甚至只是对象时,它会变得非常慢。

尝试将文本设置为从预制脚本中的 Start() 方法显示,然后您可以轻松访问该对象的所有属性,如果需要,还可以访问包含所有卡片的数据库,您可以从中引用要绘制的文本。

可能我对你的要求完全错了。 这就是我要做的,因为你说欢迎所有帮助:)

Transform.Find()/Transform.FindChild() 正在搜索 直接 CHILD。 我相信,您试图找到一些 subChild,这就是为什么它 returns null.

因此,如果您必须找到具有位置的 "heart" 游戏对象:room/human/body/heart 在 "room" 元素内,你需要执行:

Transform.Find("human/body/heart")

但不是

Transform.Find("heart")

因为 "heart" 不是 "room" 游戏对象的子对象

这是因为您从构造函数调用 Unity API 函数。基本上,您在反序列化期间和另一个 Thread.

中执行此操作

Unity 5.3.4f1 及更低版本中,Find 函数在从构造函数调用时会自动失败,您不会知道。这是在 Unity 中难以跟踪的错误之一。

Unity 5.4 及更高版本中,Unity decided 添加错误消息以提醒您此问题。您现在不会看到它,因为您仍在使用 5.3。错误如下:

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'.

在构造函数中调用Find函数时会出现类似的错误信息。

继续阅读以获取更多描述性信息和解决方案

继承自 MonoBehaviour 与不继承自 MonoBehaviour

继承自MonoBehaviour:

1。您可以将脚本附加到游戏对象。

2。您不能使用 new 关键字创建从 MonoBehaviour 继承的脚本的新实例。在这种情况下,您的 deckOfCards.Add(new Card(i, j)); 是错误的,因为 Card 继承自 MonoBehaviour

3.You 使用 gameobject.AddComponent<Card>()Instantiate(克隆预制件)函数创建新的脚本实例。文末有例子

在Unity中使用构造函数的规则:

1。不要在继承自 MonoBehaviour 的脚本中使用构造函数,除非您了解 Unity 的幕后工作。

2。如果要使用构造函数,请不要从 MonoBehaviour 继承脚本。

3。如果您违反规则 #2,请不要在继承自 [=] 的 class 的构造函数中使用任何 Unity API 18=].

为什么?

您不能从另一个线程调用 Unity API。它会失败。你要么得到一个异常,要么它会默默地失败。

这与线程有什么关系?

在 Unity 中从另一个 Thread 调用构造函数。

当脚本附加到 GameObject 并且该脚本继承自 MonoBehaviour 并具有构造函数时,该构造函数首先从 Unity 的主线程调用(这很好)然后再次从 另一个 Thread(非Unity的主要Thread)。这违反了规则 #3。您不能从其他函数使用 Unity API。

你可以通过运行下面的代码来证明这一点:

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

附加到游戏对象时的输出

Constructor Thread ID: 1

Constructor Thread ID: 20

Start() function Thread ID 1

Update() function Thread ID: 1

如您所见,Start()Update() 函数是从相同的 Thread (ID 1) 调用的主要Thread。构造函数也从主 Thread 调用,但随后又从另一个 Thread (ID 20).

调用

错误代码示例:因为脚本中有一个构造函数继承自MonoBehaviour。也很糟糕,因为新实例是使用 new 关键字创建的。

public class Card : MonoBehaviour
{
    Text topText;

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

然后使用 new 关键字创建新实例:

Card card = new Card(); //不好,因为MonoBehaviour是继承的

好的代码示例:

public class Card : MonoBehaviour
{
    Text topText;

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

然后使用 AddComponent 函数创建新实例:

Card card = gameObject.AddComponent<Card>()

或使用 Instantiate 函数从预制件克隆:

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

不继承自 MonoBehaviour:

1。您不能将脚本附加到游戏对象,但可以从另一个脚本使用它。

2。您可以简单地使用 new 关键字来创建不继承自 MonoBehaviour 的脚本的新实例。

public class Card
{
    Text topText;
    //Constructor

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

然后您可以使用 new 关键字创建新实例,例如:

Card card = new Card(); //正确,因为没有MonoBehaviour继承

解决方案:

1。如果您决定从 MonoBehaviour 继承并且必须将脚本附加到 GameObject ,则必须 remove 你所有的构造函数并将代码放入 Awake()Start() 函数中。 Awake()Start() 函数由 Unity 自动调用一次,您可以使用它们初始化变量。您不必手动调用它们。 不要 使用 new 关键字来创建从 MonoBehaviour.

继承的脚本实例

2。如果您决定继承MonoBehaviour,而您 需要将脚本附加到游戏对象,您可以像在当前代码中那样使用构造函数,并且可以在那些构造函数中使用 Unity 的 API functions.You 现在可以使用 new 关键字来创建脚本的实例,因为它不继承自 MonoBehaviour.