- Home /
How to make duplicate unique?
So I have created a fully working quiz script attached to a small rotating sphere. When I collide with the sphere I get a quiz popping up on my screen and I have to answer the correct question.
Here is a gif of how the script works right now.
https://gyazo.com/8d0df7329ca0afe7ab5f12ac3dc559da
However, when I create a duplicate of this sphere. I am thrown a null pointer exception, and it doesn't work.
https://gyazo.com/4199d436f12190a5a03062f70492c274
Under you will see 2 of the 3 scripts I am using. I think the last one is irrelevant. It is just to make a timer work when Time.Scale = 0. Any help is very appreciated!
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class QuizManager : MonoBehaviour {
public Question[] questions;
[SerializeField]
private GameObject Quiz;
private Question currentQuestion;
[SerializeField]
private GameObject displayText;
private bool clickTrue;
private bool clickFalse;
[SerializeField]
private GameObject displayTimer;
[SerializeField]
private GameObject trueButton;
[SerializeField]
private GameObject falseButton;
private string correct = "Correct";
private string fals = "Wrong";
private static float delay = 2.0f;
private bool Answer;
[SerializeField]
private GameObject knight;
void OnTriggerEnter(Collider col) {
if (col.gameObject.name == "Knight") {
knight.GetComponent<ClickMove>().enabled = false;
Quiz.SetActive(true);
Time.timeScale = 0.0f;
getRandomQuestion();
displayText.GetComponent<Text>().text = currentQuestion.fact;
StartCoroutine("Counter", 1);
}
}
void getRandomQuestion()
{
int RandomIndex = Random.Range(0, questions.Length);
currentQuestion = questions[RandomIndex];
}
public void TrueButton() {
Answer = true;
clickTrue = true;
trueButton.GetComponent<Button>().enabled = false;
falseButton.GetComponent<Button>().enabled = false;
if (currentQuestion.isTrue && clickTrue) //Nullpointer exception! Object reference not set to an instance of the object.
{
StartCoroutine(WaitCorrect(delay));
}
else
{
StartCoroutine(WaitFalse(delay));
}
}
public void FalseButton() {
Answer = true;
clickFalse = true;
trueButton.GetComponent<Button>().enabled = false;
falseButton.GetComponent<Button>().enabled = false;
if (!currentQuestion.isTrue && clickFalse)
{
StartCoroutine(WaitCorrect(delay));
}
else
{
StartCoroutine(WaitFalse(delay));
}
}
private IEnumerator WaitCorrect(float delay)
{
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay-1));
displayText.GetComponent<Text>().text = correct;
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
knight.GetComponent<Playerstat>().addHealthEnergy();
DestroyObject(gameObject);
}
private IEnumerator WaitFalse(float delay)
{
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay-1));
displayText.GetComponent<Text>().text = fals;
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
DestroyObject(gameObject);
}
private IEnumerator Counter(float delay)
{
for (int Count = 8; Count >= 0; Count--)
{
if (Answer)
{
StopCoroutine("Counter");
}
displayTimer.GetComponent<Text>().text = Count.ToString();
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
}
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
DestroyObject(gameObject);
}
}
The quiz script.
using UnityEngine;
using System.Collections;
[System.Serializable]
public class Question {
public string fact;
public bool isTrue;
}
Please detail of the error itself. It gave you information about it which you seem to be withholding from us ;)
Hi, meat thanks for your replay. Object reference not set to an instance of an object at line 54.
Firstly, you cant really duplicate anything that has declared static fields or members. Statics are meant to be Unique and singular...and Global.
Secondly, what you experiencing is a loss of Reference.
Object A looks at Object B as it knows where to look. It has a reference, like an address.
Object B gets copied.
Object A still looks to Object B and knows nothing about Object B-clone as it has been provided with no new reference, no address. This is not automatic.
"Im going to send them a letter."
"Oh, you know theyve got a new building thats identical to the last one"
"So Ill send it there"
"You cant"
"Why not?"
"We dont know the address"
"Well then we'll have to LOO$$anonymous$$ IT UP"
Hope this makes sense. I've given you the Generic answer. It should be enough to help you fix your code. Do come back if you dont get it. ($$anonymous$$aybe then I'll try the code).
Wait, if I didn't use drag and drop this would have fixed itself? If I used Gameobject.find, or findwithtag this wouldn't have been a problem? I don't 100% understand the explanation but I am taking a look at all the links you provided at this moment in time. All the https://gyazo.com/0fac099249fe2fe701e7c3127a95963e objects are linked to the copy as well so I don't quite understand :P I am sorry :(
You mean drag and drop in the editor, for variables? Yeah that is #1 reason why people experience NullRefs. Coding 'Find' or alternative methods of grabbing references to your scripts is the way to go for Reusable and versatile code. I must admit, I stopped dropping variables in Editor a very long time ago and never missed it one bit.
I dont think I've had a NullRef since.
Answer by meat5000 · May 13, 2016 at 04:34 PM
OK I've butchered your scripts for you.
The Gist of this is : If you feel like you should call something "Manager" there should probably be only 1 of them.
I separated out parts between two scripts.
QuizManager, which does what it did before (but modified) and QuizPrompt which is the new script to go on the Orb. The Orb pokes the QuizManager to do its job...and thats that. Note that the QuizPrompt sends a reference to the calling Orb so that the QuizManager can destroy it once its finished its process.
QuizManager script now goes on to an Empty GameObject called QuizManager (surprise surprise).
This approach means that you can now HardCode/ Drag And Drop your buttons etc in the Editor and have no fear of losing references.
Dont let the line
Quiz = GameObject.Find("CanvasGroup").transform.Find("Quiz").gameObject;
baffle you. GO.Find doesnt find inactive objects but T.Find does so i found the parent with GO.Find and used T.Find to find the inactive Quiz child.
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class QuizManager : MonoBehaviour {
public Question[] questions;
[SerializeField]
public GameObject Quiz;
public Question currentQuestion;
[SerializeField]
private GameObject displayText;
private bool clickTrue;
private bool clickFalse;
[SerializeField]
private GameObject displayTimer;
[SerializeField]
private GameObject trueButton;
[SerializeField]
private GameObject falseButton;
private string correct = "Correct";
private string fals = "Wrong";
private static float delay = 2.0f;
private bool Answer;
[SerializeField]
private GameObject knight;
public GameObject currentOrbReference;
void Start()
{
knight = GameObject.Find("Knight");
Quiz = GameObject.Find("CanvasGroup").transform.Find("Quiz").gameObject;
}
public void FireMe() {
Answer = false;
knight.GetComponent<ClickMove>().enabled = false;
Quiz.SetActive(true);
Time.timeScale = 0.001f;
getRandomQuestion();
displayText.GetComponent<Text>().text = currentQuestion.fact;
trueButton.GetComponent<Button>().enabled = true;
falseButton.GetComponent<Button>().enabled = true;
StartCoroutine("Counter", 1);
}
public void getRandomQuestion()
{
int RandomIndex = Random.Range(0, questions.Length);
currentQuestion = questions[RandomIndex];
}
public void TrueButton() {
Answer = true;
clickTrue = true;
trueButton.GetComponent<Button>().enabled = false;
falseButton.GetComponent<Button>().enabled = false;
if (currentQuestion.isTrue && clickTrue)
{
StartCoroutine(WaitCorrect(delay));
}
else
{
StartCoroutine(WaitFalse(delay));
}
}
public void FalseButton() {
Answer = true;
clickFalse = true;
trueButton.GetComponent<Button>().enabled = false;
falseButton.GetComponent<Button>().enabled = false;
if (!currentQuestion.isTrue && clickFalse)
{
StartCoroutine(WaitCorrect(delay));
}
else
{
StartCoroutine(WaitFalse(delay));
}
}
public IEnumerator WaitCorrect(float delay)
{
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay-1));
displayText.GetComponent<Text>().text = correct;
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
knight.GetComponent<Playerstat>().addHealthEnergy();
Destroy(currentOrbReference);
}
public IEnumerator WaitFalse(float delay)
{
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay-1));
displayText.GetComponent<Text>().text = fals;
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
Destroy(currentOrbReference);
}
public IEnumerator Counter(float delay)
{
for (int Count = 8; Count >= 0; Count--)
{
if (Answer)
{
StopCoroutine("Counter");
}
displayTimer.GetComponent<Text>().text = Count.ToString();
yield return StartCoroutine(CoroutineUtilities.WaitForRealTime(delay));
}
Quiz.SetActive(false);
Time.timeScale = 1.0f;
knight.GetComponent<ClickMove>().enabled = true;
Destroy(currentOrbReference);
}
}
using UnityEngine;
using System.Collections;
using UnityEngine.UI;
public class QuizPrompt : MonoBehaviour {
[SerializeField]
private QuizManager quizManager;
void Start()
{
quizManager = GameObject.Find("QuizManager").GetComponent<QuizManager>();
}
void OnTriggerEnter(Collider col)
{
if (col.gameObject.name == "Knight")
{
quizManager.currentOrbReference = this.gameObject;
quizManager.FireMe();
}
}
}
By the way, when you export a Unity Package it tend to forget references. If you ZIP the entire Project folder, everything is retained.
If you cant get it to work, Ill send the project back to you, or I can screenshot the setup.
Also, use Destroy as DestroyObject is deprecated apparently.
Okey, thanks. While I don't 100% understand why I have to do this, I can see that now the Quizmanager "can" differentiate between the objects. I will definitely do something similar next time, I make something like this.
I see that you put Time.Scale = 0.001? and not 0, why? I think it worked good when it was 0? But if there is a reason that 0.001 is better I am ready to learn :)
It was just to make sure I could unpause it
All 'like' things should be kept together. It makes no sense to have many objects all handling the Quiz. The single Quiz$$anonymous$$anager should handle the Quiz, manage all the relevant objects (text etc) and only be subject to prompts from the activating object. It doesnt make sense for these other objects to be responsible for the workings of the Quiz in any other way than saying "Go".
Did you get it all working with the new scripts? If it works, click Accept ;)