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
.
我正在制作一个简单的 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
.