Transform.Find发送空值

时间:2016-09-17 15:17:07

标签: c# unity3d transform gameobject

我正在制作一个简单的套牌/卡片项目。我的项目有一个卡片预制件,带有一个Canvas子,它包含套装/卡片信息的UI版本。信息可以用一张卡显示,但在涉及多张卡时会出现问题。我对此问题的理论归结于GameObject.FindGameObjectWithTag(string)查找保存标记的gameObject的第一个实例。所以,当我画多张牌时,它只会重写第一张牌,而不是在其他牌上画画。

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

以下卡片代码:

func imgtagAction(sender: UIButton!) {
    let face = self.faces[selectedItem]
}

非常感谢任何和所有帮助。谢谢你的时间。

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

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

3 个答案:

答案 0 :(得分:1)

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

Unity 5.3.4f1 及以下版本中,Find函数在从构造函数调用时会无声地失败并且您不会知道。这是Unity中难以跟踪的错误之一。

Unity 5.4 及更高版本中,Unity decided添加错误消息以提醒您此问题。您现在还没有看到它,因为您仍在使用 5.3 。错误就是同伴:

  

不允许从MonoBehaviour调用FindGameObjectWithTag   构造函数(或实例字段初始化程序),在Awake或Start中调用它   代替。来自MonoBehaviour&#39; Card&#39;在游戏对象&#39; Cube&#39;。

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

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

继承MonoBehaviour vs不继承自MonoBehaviour

继承MonoBehaviour

1 。您可以将脚本附加到GameObject。

2 。您无法使用new关键字创建从MonoBehaviour继承的脚本的新实例。由于deckOfCards.Add(new Card(i, j));继承自Card,因此MonoBehaviour在这种情况下出错了。

3.您使用gameobject.AddComponent<Card>()Instantiate(克隆预制)功能来创建新的脚本实例。最后有一个例子。

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

1 。不要在继承自MonoBehaviour的脚本中使用构造函数,除非您了解Unity内部的内容。

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

3 。如果您违反规则#2,请不要在继承自MonoBehaviour的类的构造函数中使用任何Unity API。

为什么?

您无法从其他线程调用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);
    }
}

附加到GameObject时的输出

  

构造函数线程ID:1

     

构造函数线程ID:20

     

Start()函数线程ID 1

     

Update()函数线程ID:1

正如您所看到的,Start()Update()函数是从相同的Thread ID 1 )调用的,这是{{1} }}。构造函数也从主Thread调用,然后再从另一个Thread ID 20 )调用。

BAD代码示例:因为脚本中有一个继承自Thread的构造函数。同样糟糕,因为使用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);

1 。您无法将脚本附加到GameObject,但您可以从其他脚本中使用它。

2 。当您不从MonoBehaviour继承时,您可以使用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();继承

解决方案:

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

2 。如果您决定继承MonoBehaviour并且需要将脚本附加到GameObject ,您可以使用与当前代码中相同的构造函数,并且可以在这些构造函数中使用Unity的API。您现在可以使用MonoBehaviour关键字来创建脚本的实例,因为它没有&#39 ; t继承自new

答案 1 :(得分:0)

根据我对代码和问题的理解,每张卡都从同一个GameObject获取它的文本,因为它们都是按文本搜索相同的Object。 我相信如果您在每个对象的Start()方法中实际初始化要获取的值,则可能会解决您的问题。

因此,对于绘图卡而言,每个正在绘制的游戏对象都会收到相同的文本。

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

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

但是,对于你所要问的问题,我可能完全错了。 这就是我要做的,因为你说所有的帮助都是受欢迎的。)

答案 2 :(得分:0)

Transform.Find()/ Transform.FindChild()正在搜索直接儿童。 我相信,你试图找到一些subChild,这就是它返回null的原因。

所以如果你必须找到具有位置的“心脏”gameObject:room / human / body / heart 在“room”元素内,你需要执行:

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

但不是

Transform.Find("heart")

因为“心脏”不是“房间”游戏对象的子游戏