Unity 4.6 Editor, Create a Script with Predefined Data - c#

I am trying to make an easy to use button inside the Unity Editor for Character and Item creation.
I will throw out a little extra info here to help explain my issue.
My game is structured like this;
Game Controller >> Character Script >> (PlayerName)Script
A character object has both the character script and a script named after it, on it.
I want to be able to click "Create New Character" in the Unity editor and it do the following;
1) Prompt for a Name to use.
2) Create Empty Game Object named Name from whatever the user typed in.
3) Create a new C# Script named the same, and add it to the object.
-I want the generated script to have some pre-determined "Character Template" code in it.
4) Attach the new Script to the new empty game object, and attach a "Character Script" to it as well.
Thanks in advance.
One last sub-question.
Would it be better to Access the PlayerNamedScript from the GameController by a public monobehaviour on the Character Script?
Or can the CharacterScript Dynamically extend the PlayerNamedScript, sibling.
I hope that is clear. Thanks again.

Try this out
Put the CharacterCreatorEditor.cs in a folder named Editor somewhere in your project.
CharacterCreatorEditor.cs
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
using System.Text.RegularExpressions;
public class CharacterCreatorEditor : EditorWindow {
#region Character Fields
//Add as many character specific fields / variables you want here.
//Remember to update the same thing in the "CharacterTemplate.txt"!
public string characterName = "John Doe";
public float characterHealth = 10;
public int characterCost = 1000;
public bool isBadGuy = false;
#endregion
private bool needToAttach = false; //A boolean that checks whether a newly created script has to be attached
private float waitForCompile = 1; //Counter for compile
GameObject tempCharacter; //A temporary GameObject that we assign the new chracter to.
//A Menu Item when clicked will bring up the Editor Window
[MenuItem ("AxS/Create New Character")]
public static void CreateNewChar () {
EditorWindow.GetWindow(typeof(CharacterCreatorEditor));
}
void OnGUI () {
GUILayout.Label("Here's a sample Editor Window. Put in more variables as you need below.");
GUILayout.Space(10);
//Note on adding more fields
//The code below is broken into groups, one group per variable
//While it's relatively long, it keeps the Editor Window clean
//Most of the code should be fairly obvious
GUILayout.BeginHorizontal();
GUILayout.Label("Character Name", new GUILayoutOption[0]);
characterName = EditorGUILayout.TextField(characterName, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
GUILayout.Label("Character Health", new GUILayoutOption[0]);
characterHealth = EditorGUILayout.FloatField(characterHealth, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
GUILayout.Label("Character Cost", new GUILayoutOption[0]);
characterCost = EditorGUILayout.IntField(characterCost, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUILayout.BeginHorizontal();
GUILayout.Label(string.Format("Is {0} a Bad Guy?", new object[] { characterName }), new GUILayoutOption[0]);
isBadGuy = EditorGUILayout.Toggle(isBadGuy, new GUILayoutOption[0]);
GUILayout.EndHorizontal();
GUILayout.Space(10);
GUI.color = Color.green;
//If we click on the "Done!" button, let's create a new character
if(GUILayout.Button("Done!", new GUILayoutOption[0]))
CreateANewCharacter();
}
void Update () {
//We created a new script below (See the last few lines of CreateANewCharacter() )
if(needToAttach) {
//Some counter we just keep reducing, so we can give the
//EditorApplication.isCompiling to kick in
waitForCompile -= 0.01f;
//So a few frames later, we can assume that the Editor has enough
//time to "catch up" and EditorApplication.isCompiling will now be true
//so, we wait for the newly created script to compile
if(waitForCompile <= 0) {
//The newly created script is done compiling
if(!EditorApplication.isCompiling) {
//Lets add the script
//Here we add the script using the name as a string rather than
//it's type in Angled braces (As done below)
tempCharacter.AddComponent(characterName.Replace(" ", ""));
//Reset the control variables for attaching these scripts.
needToAttach = false;
waitForCompile = 1;
}
}
}
}
private void CreateANewCharacter () {
//Instantiate a new GameObject
tempCharacter = new GameObject();
//Name it the same as the Character Name
tempCharacter.name = characterName;
//Add the ChracterScript component. Note the use of angle braces over quotes
tempCharacter.AddComponent<CharacterScript>();
//Loading the template text file which has some code already in it.
//Note that the text file is stored in the path PROJECT_NAME/Assets/CharacterTemplate.txt
TextAsset templateTextFile = AssetDatabase.LoadAssetAtPath("Assets/CharacterTemplate.txt",
typeof(TextAsset)) as TextAsset;
string contents = "";
//If the text file is available, lets get the text in it
//And start replacing the place holder data in it with the
//options we created in the editor window
if(templateTextFile != null) {
contents = templateTextFile.text;
contents = contents.Replace("CHARACTERCLASS_NAME_HERE", characterName.Replace(" ", ""));
contents = contents.Replace("CHARACTER_NAME_HERE", characterName);
contents = contents.Replace("CHARACTER_HEALTH_HERE", characterHealth.ToString());
contents = contents.Replace("CHARACTER_COST_HERE", characterCost.ToString());
contents = contents.Replace("CHARACTER_BAD_GUY_HERE", isBadGuy.ToString().ToLower());
}
else {
Debug.LogError("Can't find the CharacterTemplate.txt file! Is it at the path YOUR_PROJECT/Assets/CharacterTemplate.txt?");
}
//Let's create a new Script named "CHARACTERNAME.cs"
using(StreamWriter sw = new StreamWriter(string.Format(Application.dataPath + "/{0}.cs",
new object[] { characterName.Replace(" ", "") }))) {
sw.Write(contents);
}
//Refresh the Asset Database
AssetDatabase.Refresh();
//Now we need to attach the newly created script
//We can use EditorApplication.isCompiling, but it doesn't seem to kick in
//after a few frames after creating the script. So, I've created a roundabout way
//to do so. Please see the Update function
needToAttach = true;
}
}
Put the below text file into the path "YOUR_PROJECT/Assets/CharacterTemplate.txt" If you don't, the code WON'T WORK!
CharacterTemplate.txt
using UnityEngine;
using System.Collections;
public class CHARACTERCLASS_NAME_HERE : MonoBehaviour {
public string characterName = "CHARACTER_NAME_HERE";
public float characterHealth = CHARACTER_HEALTH_HERE;
public int characterCost = CHARACTER_COST_HERE;
public bool isBadGuy = CHARACTER_BAD_GUY_HERE;
public void SomeMethod () {
}
}
Explanation of the code
First, the editor script takes all the input variables (should be fairly obvious what they are)
Once you click the done button, the following happen
A new GameObject is Instantiated
The instantiated GameObject is named the same as the Character Name in the Editor (eg. John Doe)
The CharacterScript (your common script) is attached
The template text file ("CharacterTemplate.txt") is read, and all the data is replaced with the data you entered in the Editor Window
This is then written to a new script file
We refresh the Asset Database, and wait until the newly created script is compiled (eg. JohnDoe.cs)
Lastly attach the script to the GameObject instantiated in Step 1
For your second question, what you'll need to do is to have all your PlayerNamedClass extend the same base class. This way, you can type the variable you'll expose in CharacterScript
So, for example, if you call the base class "NamedCharacterScripts"
In JohnDoe.cs
public class JohnDoe : NamedCharacterScripts
In JaneDoe.cs
public class JaneDoe : NamedCharacterScripts
In CharacterScript.cs
public NamedCharacterScripts namedCharacterScript;
void Awake () {
//This will assign JohnDoe.cs for the GameObject named "John Doe" &
//JaneDoe.cs to the GameObject named "Jane Doe"
namedCharacterScript = GetComponent<NamedCharacterScripts>();
}
Hope this answers your questions. If you have trouble, just leave a comment

My script is not production-ready like Venkat's answer, but it should be easier to understand for educational purposes.
using UnityEngine;
using System.Collections;
using UnityEditor;
using System.IO;
[ExecuteInEditMode]
public class CharacterTools : MonoBehaviour
{
[SerializeField, HideInInspector]
private string className;
private bool waitForCompile = false;
private void Update()
{
if (string.IsNullOrEmpty(className))
return;
if (waitForCompile && EditorApplication.isCompiling)
waitForCompile = false;
if (!waitForCompile && !EditorApplication.isCompiling)
{
var gameObject = new GameObject(className);
Debug.Log("Attempting to add " + className);
var c = gameObject.AddComponent(className);
className = null;
}
}
[ContextMenu("Create character")]
private void CreateCharacter()
{
string name = "Number" + Random.Range(0, 100).ToString();
string nameTemplate = "{0}Character";
string contentTemplate = #"using UnityEngine;
public class {0} : MonoBehaviour
{{
}}
";
var className = string.Format(nameTemplate, name);
var path = Application.dataPath + "/" + className + ".cs";
var scriptFile = new StreamWriter(path);
scriptFile.Write(string.Format(contentTemplate, className));
scriptFile.Close();
AssetDatabase.ImportAsset(path, ImportAssetOptions.ForceSynchronousImport);
AssetDatabase.Refresh();
this.className = className;
this.waitForCompile = true;
}
}
Usage:
Add this script to any object on scene.
Right click on just added component in inspector.
Choose “Create character”.
Wait few seconds.

Related

Acquiring Text of a label from scene1 to scene2 Unity

I have created a simple 2d card game in Unity , While the user is playing the game , certain information is getting captured like (score,tries,time), When the user wins or loses the game then the appropriate scene (game over or Win scene) is getting triggered, in these 2 scenes id like to copy the info from the score, tries, time fields of the game to the appropriate labels of gameOver or Win, But it seems I'm doing something wrong..
I tried to find similar questions on StackOverflow, Youtube, the web; I checked the documentation and I tried different approaches.
Here is the code I'm working with at the moment, which seems totally correct to me, I don't get any errors, just nothings appears in the labels.
'''
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class win_results : MonoBehaviour
{
// public options script;
public Text score_final_text;
public Text tries_final_text;
public Text time_final_text;
public void player_win_results()
{
// script = a.getComponent<options>();
if (options.user_lvl_choice=="easy")
{
GameObject tries_final = GameObject.Find("tries_text_lvl1"); //finds the object named score_results
tries_final_text.text = tries_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
GameObject time_final = GameObject.Find("TimeCounter_lvl1"); //finds the object named score_results
time_final_text.text = time_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
GameObject score_final = GameObject.Find("score_lvl1"); //finds the object named score_results
score_final_text.text = score_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
}
else if (options.user_lvl_choice == "hard")
{
GameObject tries_final = GameObject.Find("tries_text_lvl2"); //finds the object named score_results
tries_final_text.text = tries_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
GameObject time_final = GameObject.Find("TimeCounter_lvl2"); //finds the object named score_results
time_final_text.text = time_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
GameObject score_final = GameObject.Find("score_lvl2"); //finds the object named score_results
score_final_text.text = score_final.GetComponent<Text>().text + "a"; //gets the Text property of it.
}
}
As the code above shows I'm using GameObject.Find as the documentation states, I have double checked the names and typos, the scripts are correctly attached, I get no errors in the output, just nothing happens.
As you can see above I have added an extra "a" character at the end for debugging purposes, the "a" character also doesn't appear in the output, meaning that the gameObject find doesn't work correctly in my example.
What am I doing wrong?
Thanks in advance.
When you load a scene without the second parameter
SceneManager.LoadScene("scene2");
The old scene will be destroyed, all the GameObjects in that scene will also be destroyed except you've called DontDestroyOnLoad on it.
So if you haven't done this job yet, what you get in the new scene are empty data. You can print the values to check.
The easiest way to pass data through scenes is use static field. Create a class and add some static fields:
class Global
{
public static string user_lvl_choice;
public static string tries_final;
....
}
Then you can set or get these values from any scene.
Global.user_lvl_choice = "easy"
print(Global.user_lvl_choice);
Read more:
static
SceneManager.LoadScene
Object.DontDestroyOnLoad

How to display list in gui in runtime

I'm trying to create a search interface similar to Facebook's. That is, you type in all or part of a name, and the matches are displayed in a list below.
I know how to extract the input from the InputField (SearchBar) but I don't know how to display the matching results in the panel below during runtime.
Create a new label/button for each match and append to... the panel?
What container should I use?
How do I actually "add/append"?
Any help would be much appreciated.
Here is my scene:
And here is my code:
using UnityEngine;
using UnityEngine.UI;
using System;
public class SearchScript : MonoBehaviour {
public InputField SearchBar;
public GameObject Panel;
public List<String> myList;
public void Start() {
myList = new List <String>();
myList.Add("Andre");
myList.Add("Angela");
myList.Add("Temi");
myList.Add("Tupac");
myList.Add("Graham");
myList.Add("Grandpa");
myList.Add("Michael");
myList.Add("Miguel");
SearchBar.onValueChanged.AddListener(delegate {ValueChangeCheck(myList); });
}
public void ValueChangeCheck(List<string> myList) {
string contents = SearchBar.text;
List<String> outList = new List <String> ();
for (int i = 0; i < myList.Count; i++) {
if (myList [i].Contains (contents)) {
outList.Add (myList [i]);
}
}
for (int i = 0; i < outList.Count; i++) {
>>HELP<<
}
}
}
This page in the Unity manual describes what you'll need to do in general terms. The first thing you'll want to do is (in the editor) make a blank button/label or whatever it is you want your final product to look like. For this example, I'm going to act like it's going to be a button. Once you've got the button looking the way you want, set the position to (0,0) and make it a prefab. Make a public field in your MonoBehaviour and drag the prefab into it in the editor. That will look like this:
public GameObject ButtonPrefab;
Next, in your loop, you'll need to instantiate each button, making sure that you parent the new object to your canvas (which is your SearchBar's parent, so we have an easy way to get at it). Then assign your text and shift it down and you'll be golden!
for (int i = 0; i < outList.Count; i++) {
var newButton = Instantiate(ButtonPrefab,SearchBar.transform.parent,false) as GameObject;
// This assumes you have a Text component in your prefab
newButton.GetComponent<Text>().text = outList[i];
// You'll just have to experiment to find the value that works for you here
newButton.transform.position += Vector2.down * 20 * (i + 1);
}

Unity c# Code working in one script but not in other?

Hey guys so I have this really strange problem, so basically what happens is the player runs into the box (OnTriggerEnter function) and a weapon will show on the character and the box will spawn to another location. Except I tried to make a separate script because the script that it works on is for spawning boxes. I didn't want it in the same script because not as neat.
So this is my code for the script where it doesn't work:
public class ChangeGun : MonoBehaviour {
public Sprite gunSprite;
private string[] weapons = { "Pistol", "Shotgun", "Ak47", "Bazooka" };
public GameObject currentGun;
public void AddGunToPlayer()
{
//int randomNumber = Random.Range(0, 4);
currentGun.GetComponent<SpriteRenderer>().sprite = gunSprite;
}
}
And this is the script where it works (keep in mind I used the EXACT same code, ignore all the code in this script except the for the 2 variables and the OnTriggerEnter Function.)
public class BoxGenerator : MonoBehaviour
{
public GameObject currentGun;
public Sprite gunSprite;
public Vector3[] boxPositions;
public GameObject box;
public GameObject startingBox;
int randomNumber;
// Use this for initialization
void Start()
{
//random number between 0-10 thats vector size
randomNumber = Random.Range(0, 10);
//instantiate the starting box
startingBox = Instantiate(box);
//set starting box to new location
startingBox.transform.position = boxPositions[randomNumber];
//set tag to box
startingBox.tag = "box";
}
void Update()
{
randomNumber = Random.Range(0, 10);
}
void OnTriggerEnter(Collider col)
{
if (col.GetComponent<Collider>().tag == "box")
{
startingBox.transform.position = boxPositions[randomNumber];
currentGun.GetComponent<SpriteRenderer>().sprite = gunSprite;
}
}
}
Keep in mind when I tried to use the old script under this line "currentGun.GetComponent().sprite = gunSprite;"
I pasted this code and got the null reference exception.
ChangeGun cg = new ChangeGun();
cg.AddGunToPlayer();
ChangeGun is null since you didn't set it. You said you set it in the inspector, but in your question you created in in the code so it will have the default values.
ChangeGun cg = new ChangeGun(); // cg.currentGun is null
cg.AddGunToPlayer(); // Trying to access cg.currentGun
I suppose you have set in in the inspector, so instead of creating new ChangeGun, you need to get the component you created in Inspector:
ChangeGun cg = GetComponent<ChangeGun>();
cg.AddGunToPlayer();

Transform.Find sends null value

I am making a simple deck/card project. My project has a card prefab, with a Canvas child which would contain the UI version of the suit/card information. The information can be displayed with one card, but has problems when it comes to multiple cards. My theory on the matter is due to GameObject.FindGameObjectWithTag(string) finds the first instance of the gameObject that holds the tag. So, when I draw multiple cards, it will just rewrite the first card, rather than draw on the other cards.
I have tried Transform.Find(string), but it has turned up null, despite having a game object set in the Editor. One solution would be to use multiple tags with numbers after the name, ala topNum1, topNum2, etc., but doing that for 52 different numbers 3 times each sounds very repetitive, and frustrating. Is there a better way to do this?
Card code below:
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;
}
}
Any and all help would be greatly appreciated. Thank you for your time.
EDIT: Since a few people asked for it, I have edited this question to include the code that this class is called in:
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);
}
}
}
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<Card>() 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<Card>()
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.
From my understanding of the code and question, every card gets it's text from one and the same GameObject as all of them are searching for the same Object by text.
I believe your issue might be resolved if you actually initialize the values you want to get, in the Start() method of each object.
Thus in reverse for drawing cards, every gameObject that is beeing drawn receives the same text.
And as stated in the answer quicker than me, try not to use Find() as it get's really slow when it comes to more cards or even just objects.
Try setting the text to display from a Start() method inside of a script on the prefab, then you have easy access to all properties of that object and if needed a database of all cards from where you can reference the text to draw.
Might be that I am completely wrong on what you're asking however.
This is just how I would do it, since you said all help is welcome :)
Transform.Find()/Transform.FindChild() is searching direct CHILD.
I believe, you trying to find some subChild, that's why its returns null.
So if you have to find "heart" gameObject that have location: room/human/body/heart
inside of "room" element, you need to perform:
Transform.Find("human/body/heart")
but not
Transform.Find("heart")
as "heart" is not subChild of "room" game object

Change a Text dynamically using PointerEnter

As I'm currently developing a Unity Engine based Game right now I need a PointerEnter EventTrigger to change my Text dynamically. Specifically:
If the user hovers with the Mouse in the MainMenu over a Text, I want an indicator on which Option he is pointing at.
So from Options the Text should turn to ▶ Options.
What I did is the following:
Text text;
string ContinueText = "▶ Continue";
void Awake()
{
// Set up the reference.
text = GetComponent<Text>();
}
public void Test()
{
text.text = ContinueText;
}
But if I hover over the Text I get
NullReferenceException: Object reference not set to an instance of an object
pointing at text.text = ContinueText;
So I searched around the Web and found that void Update() is sometimes called before Awake(), the Error stays the same anyway. The Canvas-Text is named "Text_Options", in case you need that.
Thanks for helping me out!
Here is an working example.
A canvas with an empty gameobject (has a vertical layout group, but thats not relevant) that is a container for two text objects.
I've added two event triggers each, OnPointerEnter and OnPointerExit. Both text objects have my script HoverText on them:
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class HoverText : MonoBehaviour
{
Text text;
public string content = "Text A";
public string contentHighlighted = "▶ Text A";
void Awake()
{
text = GetComponent<Text>();
text.text = content;
}
public void Highlight()
{
text.text = contentHighlighted;
}
public void UnHighlight()
{
text.text = content;
}
}
Text_A has itself as gameobject for it's both event triggers and Text_B itself respectively. The public strings for the two different text contents are set via inspector (default value from script is actually matching Text_A in my example).
That's it, works fine.

Categories