Trouble instantiating prefab object by calling function in external script (Unity/C#) - c#

I am having trouble getting one script to call an instantiation function from another script. The following image illustrates how I have my game objects set up.
I have 2 object things, Thing1 and Thing2, which move around the screen based on a movement script that is attached to each of them. Thing1 also has attached a reaction script. When Thing1 and Thing2 collide, Thing 3 should appear. Thing3 has an instantiation script attached to it that contains a function with an instantiation command.
When I call the instantiation function within the instantiation script (I put it in the Start function of Thing3), it works fine. However, when I take it out of there and try to put it in the Start function of the reaction script attached to Thing2 I cannot get it to work. Most currently, I get no error on compiling but as soon as the game starts I get is the following (The behavior of Thing1 and Thing2 also seems to be adversely affected):
"NullReferenceException: Object reference not set to an instance of an object"
When I look up the reason for that error I find that the most typical reason for that is a prefab object not being attached to the script. However, I DO have a prefab attached to the script. It is attached to the instantiation script that contains the instantiation function.
Below is the code for the instantiation script and the reaction script that is calling the instantiation function within the instatiation script.
//Instantiation
public class Thing3Instantiation : MonoBehaviour
{
public GameObject thing3Obj;
void Start()
{
//CreateThing3();
}
public void CreateThing3()
{
Instantiate(thing3Obj);
}
}
And
//Reaction
public class Reaction : MonoBehaviour
{
private Thing3Instatiation thing3instantiation;
void Awake()
{
thing3instantiation = GetComponent<Thing3Instantiation>();
}
void Start()
{
thing3instantiation.CreateThing3(); //This line triggers the null error
}
Any ideas on what I'm doing wrong?

If I understand your setup correctly:
Reaction is on Thing1
Thing3Instantiation is on Thing3
So when Reaction awakes, it tries to find the component called Thing3Instantiation on its own gameObject (Thing1). But it isn't on Thing1 it is on Thing3, so GetComponent returns null.
Hence, your NullReferenceException.

Your problem is actually at the line of Instantiate(thing3Obj);.
In other words... When you Instantiate a script, you won't have variables assigned, in your case thing3Obj. No matter if you've assigned them through Inspector, when Instanciated, you get a clean copy of that class, unallocated. You need to assign it before calling, that's why it's giving the Null exception.

Related

Use script for GameObject creation / changes outside of runtime

I have a prefab, which is used multiple times in my game (it will not be instatiated during runtime, I only use the prefab by dragging it into scene view to build my level).
Inside this prefab are many locations, where a specific text is written. This text is unique for every prefab.
So, it is like:
InstanceA
TextA (multiple times inside the instance)
InstanceB
TextB (multiple times inside the instance)
and so on ...
At the moment, I see two options to do it:
I change every text manually in every instance. This would work, but is a little annoying, also I can see me making mistakes (like a misspelling or forget to change one of the texts).
I solve it by a script and change the texts in the Awake or Start method. This would work, but it must be done every time, I start the game.
What I would hope for would be a solution like 2) but not every time (because nothing changes, it es every time the same) but only once - so it is generated by a script but after that, it will be always there. Even when I'm not in runtime and just work in the scene view.
Do you know any approach to get something done like this?
Thank you!
If you wish to run a piece of code outside the run-time, you can use one of the following two options.
OnValidate method
OnValidate is triggered when your MonoBehaviour script is loaded or an inspector value has changed.
using UnityEngine;
public class TestScript : MonoBehaviour
{
private void OnValidate()
{
Debug.Log("test");
}
}
ExecuteInEditMode attribute
Adding ExecuteInEditMode to a MonoBehaviour class will make it function both during and outside the runtime. If you do not wish the script to function during the runtime, feel free to also use Application.isPlaying.
using UnityEngine;
[ExecuteInEditMode]
public class TestScript : MonoBehaviour
{
private void Update()
{
if (!Application.isPlaying)
Debug.Log("test");
}
}

I'm studying the C# constructor and destructor with Unity, but I don't know why it works like this

I'm studying C#'s Constructer and destructor with unity component system.
I'm sorry if the English of this question is weird. I used a translator cause I am not good at English.
The log output came out like this.
The Constructor log was displayed without pressing the play button. Why?
When I pressed the play button, I saw a log of something being created and immediately disappearing. I didn't write a code to create an object after the game started, where does this phrase run?
This is my code.
project working structure
Pressing the space bar brings the pre-made pre-fab to the game world,
and Prefab has a component that attached to test the constructor and
destructor.
CubeFactory.cs / when Press the space bar, It creates Prefab.
this component was attached to "GameObject" Gameobject
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CubeFactory : MonoBehaviour
{
public GameObject obj;
private int pos = 1;
void Start()
{
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
Instantiate(obj,new Vector3(pos,0,0),Quaternion.identity);
pos++;
}
}
}
ClassTest.cs / Component for Testing Constructor and Destructor. It attached Cube prefab.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class ClassTest : MonoBehaviour
{
public ClassTest(){
Debug.Log("I was born!");
}
~ClassTest(){
Debug.Log("I Died x0x");
}
}
This is my project file. (I'm sorry to compress it up. I haven't learned how to use Git yet.)<
https://github.com/Scincy/UnityStudy
Please change your ClassTest.cs script to use simpler functions. Never use constructors and finalizers in Unity. What I understood from your logic is that when you press Spacebar, an object is instantiated. The ClassTest.cs is attached to this object. Better use:
void Start()
{
Debug.Log("I was born!"); //Will be called when the gameObject is active for the first time
}
Now you also want to know if the gameObject is destroyed, you can do this with another script attached to an always-active gameObject such as:
public GameObject cube;
void Update()
{
cube= GameObject.Find("Cube(Clone)"); //Depending on how your object is called
if(cube == null)
{
Debug.Log("Either not created or is destroyed");
}
}
I have used Update() which is called every frame. You could use a different function that you would like to call from another script only once.
Ye olde wisdom says that you should only use C# finalizers if your class contains a handle (IntPtr) to an object in unmanaged memory. If you're unclear what that means, then you probably don't need to be using finalizers; either way, you should never need to use them in a UnityEngine.Object-derived class.
Instead, prefer either of these Unity messages:
void OnDestroy()
{
// (1) called when the Object is about to be fully destroyed.
}
void OnDisable()
{
// (1) called when the Object is disabled in the Scene hierarchy.
// OR
// (2) called right before OnDestroy() would be called.
}
These Unity messages are of course available on MonoBehaviours, but also on all ScriptableObjects too! (For the latter, OnDisable essentially resolves to the same thing as OnDestroy since they are not Scene-bound Objects.)
Similarly, you should also avoid using C# constructors on MonoBehaviours and ScriptableObjects. The messages to prefer instead are:
void Awake()
void Start()
void OnEnable()
Each has its own quirks, so you may want to familiarize yourself with the differences and write some tests.
You can refer to the relevant documentation here: (see section header "Messages")
MonoBehaviour docs
ScriptableObject docs

OnTriggerEnter2D not working Unity Problem

so i've got this problem thats bugging me for hours now.
I try to use OnTriggerEnter2D(); to get the data of other.DataHolder.itemType.
But Unity states that other does not contain whatever I'm trying to access.
My first Question is, how should unity know that? As in the moment I write the code there's nothing colliding with my player so the code inside of OnTrigger2D(); shouldn't be executed and therefore not asking for components of nothing that obviously not exist.
void OnTriggerEnter2D(Collider2D other)
{
other.gameObject.GetComponent<DataHolder>();
if(other.DataHolder.itemType == "bagCoins")
{
goldCount++;
other.GameObject.SetActive(false);
}
}
How should unity know that?
Well this is c# and any compiler will know the existing types and that a Collider2D has no member called DataHolder.
As in the moment I write the code there's nothing colliding with my player so the code inside of OnTrigger2D(); shouldn't be executed and therefore not asking for components of nothing that obviously not exist.
I'll try it in simple words: As with any other application your entire code is/has to be compiled by Unity before you can even enter the PlayMode/execute it!
So if there is an error in your code of course you will get the according compiler error before it is actually executed since the compiler doesn't even understand how to compile your code for running it.
What you speak about would be a runtime error that only occurres e.g. because some reference is null but principally means that your code structure itself is correct.
GetComponent returns a reference. Just calling
other.gameObject.GetComponent<DataHolder>();
itself does nothing. And in particular it does not change the type of other which still is a Collider2D and has no such member like a .DataHolder!
Instead you have to store that returned reference and use it like
void OnTriggerEnter2D(Collider2D other)
{
// no need to go through the gameObject here btw.
// the Collider2D inherits from Component which also implements GetComponent directly
var dataHolder = other.GetComponent<DataHolder>();
if(dataHolder.itemType == "bagCoins")
{
goldCount++;
// Here you want the property gameObject not GameObject
other.gameObject.SetActive(false);
}
}

Calling function from another script attatched to another object not working

I am trying to call a function in another c# script, that is attatched to another gameObject in my scene. I am creating an instance of the script LevelChanger in the Grounded one like this:
LevelChanger levelChanger;
Then, in the Awake() function:
GameObject gameObject = new GameObject("LevelChanger");
levelChanger = gameObject.AddComponent<LevelChanger>();
then calling like this in an IEnumerator:
levelChanger.FadeOut(true); // line 178
Then this is the LevelChanger class:
using System.Collections;
using UnityEngine;
public class LevelChanger : MonoBehaviour
{
public Animator animator;
public void FadeOut(bool fadeIn)
{
animator.SetBool("Fade", true); // line 10
if (fadeIn) StartCoroutine(FadeInAsWell());
}
IEnumerator FadeInAsWell()
{
yield return new WaitForSeconds(0.9f);
animator.SetBool("Fade", false);
}
}
I've been trying to solve this problem for quite a while now, I checked everywhere (here, here, here, here and on other sites). I saw that because my LevelChanger script is attatched to a gameobject, so it is MonoBehaviour, it is not possible to create an instance of the class from another script, like this:
LevelChanger levelChanger = new LevelChanger();
or either "normally" like I was doing at first like this:
LevelChanger levelChanger;
Then just calling its functions like
levelChanger.FadeOut(true);
Most of the time I was getting a NullReferenceException at runtime (at line 176), now directly in the LevelChanger script (at line 10).
At this moment, I truly have no idea how to fix this: does anybody know how?
(I'm still a beginner).
Thanks in advance!
Based on the conversation we've had in comments, I'll write an answer here to be more thorough.
So, let's recap. What you want to do is call a function from LevelChanger. But, you are unsure of how to get a reference to LevelChanger. This is the core problem every programmer faces: How do i get that stuff over there, and what's the best way to do?
Based on what you've said, your prefab exists in the scene, so you want to grab a reference to it.
One simple, lazy way is to call FindObjectOfType<LevelChanger>. This will search through the entire scene for components of that type, and will return your LevelChanger instance. I don't recommend this because it's pretty lazy and inefficient. This is only needed if one or both of your objects exist at run-time but not at edit-time
Another way is to treat LevelChanger like a singleton.
add a public static LevelChanger Instance; field to your LevelChanger. Then, on Awake() set Instance to this. ie Instance = this;
public static LevelChanger Instance;
public void Awake()
{
Instance = this;
}
Then, in any other script, you can call LevelChanger.FadeOut(false);
This is a similarly lazy way, but not quite inefficient. It does make your code a little harder to follow and there are a lot of devs that have problems with static instances like this. Just so you know.
One other way is to create an instance of the prefab using GameObject.Instantiate() and calling the function after getting a reference to the component. This is a bit more involved, but it might be a cleaner way for you.
There are two ways to do this, so lets do the cleanest way. In your project, create a folder called Resources if you don't already have one. Resources is a name unity will specifically look for when calling Resources.Load(). Drag your prefab object into that folder to create a new prefab. Call it LevelChanger
//Spawn the prefab gameobject
GameObject gameObject = GameObject.Instantiate(Resources.Load("LevelChanger")) as GameObject;
//Get a reference to its component LevelChanger
LevelChanger levelChanger = gameObject.GetComponent<LevelChanger>();
//Call the function
levelChanger.FadeOut(false);
After your prefab finishes the fade, you can have it destroy itself via Destroy(gameObject);
Finally, since you've indicated both scripts exist at edit time, you can simply add an inspector reference. This is the easiest way, but don't go crazy with inspector references. It makes the code difficult to follow.
public LevelChanger levelChanger;
Quick and dirty because little has been shared about the code and scene in question.
In Awake, get a reference to the gameObject with the already existing LevelChanger on it:
GameObject levelChangerGO = GameObject.Find("LevelChanger");
levelChanger = levelChangerGO.GetComponent<LevelChanger>();
then calling like this in an IEnumerator:
levelChanger.FadeOut(true); // line 178

Unity3D C# - Text UI

I have the following piece of code. I referenced the text component in the editor to no_lives. The gamemanager (singleton) is being instantiated a scene before. The debug.log() shows 5 in the console. But when I try to set the text I get that the reference is not set to an instance of the object. Why is that?
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;
public class level1_script : MonoBehaviour {
public Text no_lives;
// Use this for initialization
void Start () {
no_lives = GetComponent<Text>();
}
// Update is called once per frame
void Update () {
int lives_n = gamemanager.lives_f ();
Debug.Log (lives_n);
no_lives.text = lives_n + " x";
}
}
Are you certain that Start() was called before Update and that the GetComponent is actually setting a value to no_lives?
If not then code defensively. Also lives_n needs to be converted to a string lives_n.ToString() + " x" or use a format.
void Update () {
int lives_n = gamemanager.lives_f ();
Debug.Log (lives_n);
if(no_lives != null) {
no_lives.text = string.Format("{0} x", lives_n);
}
}
EDIT:
But when I try to set the text I get that the reference is not set to
an instance of the object.
That's because when you do GetComponent<Text>‌​(), it will look for the instance of Text component on the-same GameObject your level1_script script is attached to.
You have two choices:
1.Attach Text component that GameObject your level1_script script is atached to then your current code should work.
2.If you already have the Text component attached to another GameObject, let's say an Object called "MyText", use GameObject.Find to find "MyText" then perform GetComponent on it to get the Text component.
no_lives = GameObject.Find("MyText").GetComponent<Text>‌​()
Edit:
Well, both Text and script are attached to the same object
prntscr.com/f7p7vx
It's likely attached to multiple GameObject and the other GameObject does not have the Text script attached to it.
Select the level1_script script, go to Assets --> Find References in Scene then remove the duplicated script from other objects.
As the script logs '5' in the debug log, it seems to me that the variable lives_n is used correctly.
My guess is that the component "no_lives" isn't filled (that's what is often causing the "reference is not set to an instance of the object" error)
I suggest that you try to add the script to the UI Text object in your scene, this way the script finds the "Text" component on that object.
It's important to know the differences between objects and components, because they're both essential parts of unity. Objects are "things" in your scene, like cubes, cameras and stuff like that. Components on the other hand are part of object and define the properties of that object. For example: a camera object has a camera component to make it a camera and to make it actually viewable by the user.
The GetComponent<>() method searches for a (in your case) Text component on the object the script is attached to.
Hope this helps!
(oh and don't forget to remove the script from the other object you've placed it on, in case this works. Otherwise you'll still get errors)

Categories