Accessing a script from another script at runtime in Unity C# - c#

Like in here, but the difference is that it's supposed to be done from an instantiated prefab, so I can not drag the GameObject, that has the script with the variable I want to access, into this script.
This was working
public ScriptA script;
void Update() {
if (script.varX < 0) {
// . . .
}
}
But now I'm getting "Object reference not set to an instance of an object" error, which I think comes from the fact that the script trying to access ScriptA, is attached to an instantiated prefab.
How do I attach scripts and/or GameObjects at runtime?

Looks like you need to find your script type first, if it already exists in the scene:
public ScriptA script;
void Start()
{
script = GameObject.FindObjectOfType<ScriptA>();
}
void Update()
{
if(script.variable...)
}

You want to use AddComponent, like:
ScriptA script = gameObject.AddComponent<ScriptA>() as ScriptA;
See the docs here:
https://docs.unity3d.com/ScriptReference/GameObject.AddComponent.html

Best way to satify the links is fill the fields in the very next lines after you instantiate, that way you can avoid ugly and expenstive Find* calls (I am assuming the script that does the instancing can be made aware of what the target objects are, after all it knows what and where to instantiate)
Its worth noting that newly instantiated scripts' Awake() method will be called before Instantiate() returns, while its Start() will be called at the start of following frame, this is a major difference between the two calls, so if your instantiated script needs the refectences in Awake() you should either refactor (move stuff to Start()) or use Find* as suggested earlier.

Related

Awake Method not starting

Im trying to use the Awake method without attaching it to an object, but it never initializes the class. It was supposed to initialize the class so that it could run an OnEnable method that added a subscriber to an event, but it did nothing. The only thing that works is attaching the script to a game object, but I don't want to do that, I tried changing the acmes modifier from private to protected virtual and a bunch more...
This is the code:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovementAnimationParameterControl : MonoBehaviour
{
private Animator animator;
private void Awake()
{
animator = GetComponent<Animator>();
}
private void OnEnable()
{
EventHandler.MovementEvent += SetAnimationParameters;
Debug.Log("Enable. Im running, its not me");
}
private void OnDisable()
{
EventHandler.MovementEvent -= SetAnimationParameters;
Debug.Log("Im running its not me");
}
private void SetAnimationParameters(float xInput, float yInput, bool isWalking)
{
animator.SetFloat(Settings.xInput, xInput);
animator.SetFloat(Settings.yInput, yInput);
animator.SetBool(Settings.isWalking, isWalking);
}
}
It was supposed to initialize the class so that it could run an OnEnable method that added a subscriber to an event, but it did nothing. The only thing that works is attaching the script to a game object
When you create a class that inherits from MonoBehaviour (Aka a 'Unity Script'. You're creating a script that allows the use of certain functions at runtime, such as OnEnable and OnAwake among others.
It might not seem too intuitive, but MonoBehaviour actual inherits indirectly from GameObject. The reason for this is because GameObject is the "target" of all of these methods like Update() for example.
Without an actual GameObject for your MonoBehavior to access it's properties at runtime, your script wont do anything.
The best example would be from your code.
Take the OnAwake() method for example
private void Awake()
{
animator = GetComponent<Animator>();
}
What this method does is set the field animator to the reference of Animator. That reference is found using the inherited method called GetComponent. Get component is an inherited method from the MonoBehaviour class, which like a russian nesting doll, is inherited from GameObject.
So what you're really doing is saying "hey GameObject that this script is attached to, please give me your Animator component, if you have one."
This is where we see the issue at hand, that without an actual GameObject to access none of the methods like OnAwake() or even the method calls contained within will work, since they all(or most) come from GameObject not actually from MonoBehavior.
There is also another major obstacle to overcome specifically with the events like OnEnable(), OnAwake(), Start() etc.. This issue comes from how Unity processes scripts during runtime. Only scripts with a actual GameObject is ever scheduled to have it's components scripts to run.
Think of it like a big pile of paper work on some body's desk. This big pile of paperwork consists of all of the scripts on all gameobjects in the scene. Unity has to pull a sheet of paperwork(a script) from the pile, run it's contents, and put it in the done pile on the opposite side of the desk. With this analogy, a script that doesn't have any attached GameObject, would be a sheet a paper with no name on it. Unity will look at this paper and say "This doesn't even have a name on it, I'm not even going to look at it" and then proceeds to throw it in the trash because it's not a script that any GameObject needs to run - and that's all Unity cares about. This is because it would be inefficient to look at any scripts that no GameObject needs.

How to: Get instance of gameObject

Looking to get an instance of this game object so I can successfully use .enabled to hide it in a scene.
PlayPortalLoginButton loginButton = gameObject.GetComponent<PlayPortalLoginButton>();
Fairly new to C# and I believe I am close to achieving my goal with the line above. What needs changed? Want to understand how to correctly do this.
Here is one way you could find a component on a GameObject in the scene, where "PortalLoginButton" is the name of the GameObject as seen in the editor:
var loginButton = GameObject.Find("PortalLoginButton");
loginButton.enabled = false;
However, GameObject.Find("...") searches the name of every GameObject in the scene, and this is not usually the best way to reference a GameObject since it is not very efficient. So make sure not to use GameObject.Find("...") or similar function calls in the Update() function because it will execute every frame and slow your game down. If the GameObject is not instantiated while the game is running, it is usually better to make a global reference to any GameObject or Component that you use in your script and then drag-and-drop the GameObject with the Component you are looking for into the field in the editor. Alternatively, you can use GameObject.Find("...") in the Start() or Awake() functions to store a reference to the GameObject that you are looking for, so that the search only happens once at the start of your game.
Here is an example of how to store the reference in global field (it will show up in the editor and you can drag-and-drop the GameObject into it). The differences between using a public field vs a private field are explained in the comments (you can decide on using public or private):
// By default, private fields are not viewable in the editor,
// but the [SerializeField] attribute, placed before
// the field declaration, allows them to be visible.
[SerializeField]
private GameObject loginButtonPrivateReference;
// If you need to access loginButton from another script,
// you can make it a public field.
// Public fields are viewable in the editor by default.
public GameObject loginButtonPublicReference;
Here is an example of how you can use GameObject.Find("...") in the Awake() function:
private GameObject loginButton;
private void Awake() {
loginButton = GameObject.Find("PortalLoginButton");
}
If you need to search for GameObjects in your scene by type or by tag name, see the GameObject documentation here for more information. Searching by type is less efficient and searching by tag is more efficient than searching by name because type searches check each component on each GameObject, and tag searches search only an organized GameObject subset.
GameObject button;
void Start() {
button = GameObject.Find ("yourButtom");
}
void SomeEvent() {
button.SetActive(false);
}
I think you have to help to you
There are several ways to get access to a gameObject in a script depending on exactly which gameObject you are trying to get.
If you are trying to access a GameObject of a behavior in the behavior script then simply using gameObject should suffice since that is the gameObject that behavior is attached to.
Accessing a script that is attached to another GameObject is where it can be tricky. For scenes such as a MainMenu, there is nothing wrong with giving it a public reference as an example:
public PlayPortalLoginButton loginButton;
void setLoginState(bool activate) {
loginButton.enabled = activate;
}
With this example you would just drag and drop the gameObject with the PlayPortalLoginButton script on your manager script, the script that would control whether it is enabled or not.
If you need a more modular approach to it for example in a gameplay scene where you are populating objects at run time the approach you can take is:
PlayPortalLoginButton loginButton;
void setLoginState(bool activate) {
if(loginButton == null)
{
loginButton = FindObjectOfType<PlayPortalLoginButton>();
}
// Requires the correct version of .net otherwise you can reuse the check above...
loginButton?.enabled = activate;
}
In the above script if you needed to gameObject of loginButton, now you can access it through loginButton.gameObject.
There are some more examples you could use and other approaches however I feel the above should suffice if for a menu.
I will also mention I am avoiding example of GameObject.Find and GameObject.FindGameObjectsWithTag as those are more prone to error. They will only work if your name or tag are correctly marked, if you rename them for any reason then your scripts will fail to find the Object, and this can cause an issue with troubleshooting.
Using FindObjectOfType, you are less likely to have an error, and if you remove that script type these scripts will populate an error indicating the script no longer exists. When refactoring, if you right click on the behaviors name and use the rename option it will automatically update references to the script throughout your code.

How do you properly address another scripts function?

I'm trying to call a function called dieAnim() in one script from another script, called Magnum.
This is in my Fire function in the Magnum script:
if (hit.collider.tag == "Alien1")
{
Alien.dieAnim();
}
In the Alien script:
public void dieAnim()
{
Destroy(gameObject);
}
Everytime i try to run this, it says Object reference not set to an instance of an object.
To run this more efficiently do
if(hit.collider.CompareTag("Alien1")){
//either
Destroy(hit.gameObject);
//or if there is logic in dieAnim()
hit.collider.GetComponent<Alien>().dieAnim();
}
The reason your code was not working was because you were calling the dieAnim() function as if it were on a static component. That means you were trying to call it on all scripts basically. You have to have a reference to that instance of the alien that you hit. The CompareTag is just a special function that allows you to compare tags of gameobjects more quickly and efficiently than a string == string comparison.
In the above it looks like you're trying to call dieAnim() from the Class name of Alien
You'll want to use
hit.collider.gameobject.GetComponent<desiredcomponent>().dieAnim();
In this you're accessing the instance of the Alien class as a component.
Edit for clarity.
In the editor when you add a script you're adding a new monobehavior. These are attached to a game object as components. So when you want to access one, you have the access the components of the game object.
So in your case to get the other script you need to call get component on your the game object your colliider hit. The tag is also attached to the game object itself
Just re-write your code to the following
if (hit.collider.tag == "Alien1")
{
hit.collider.GetComponent<Alien>().dieAnim();
}
But if the the only thing that dieAnim is doing is destroying the alien that was hit, you don't need to call a function inside of another script for that - that's overkill.
Just do this
if (hit.collider.tag == "Alien1")
{
Destroy(hit.gameObject);
}

What is better: link to a GameObject or to a Class in Unity?

Example: GameObject A has a script attached to it called MakeItRain. Inside that script is a public void Drizzle();
GameObject B also has a script and wants to tell MakeItRain to do Drizzle();
Inside the script of GameObject B, I can do this:
public GameObject makeitrain;
and then I have to use GetComponent to reach Drizzle(); in my code.
In the inspector, I drop GameObject A into the slot of makeitrain and I'm done.
However, I could also do this in the script of GameObject B:
public MakeItRain makeitrain;
and then just call makeitrain.Drizzle(); in my code of GameObject B's script, without GetComponent.
In both cases, in the Inspector, I have to drag and drop GameObject A into the slot of GameObject B.
Is there a difference or reason why I should definitely not do the last option? I understand that the first method gives me more flexibility because I could call other components of GameObject A as well and not just the script's stuff. Just wondering if there is any other rationale for not doing the second method.
The answer depends if you need to call any function or use variable from the MakeItRain script.
If you don't need to to call any function in the MakeItRain script or access any variables from it then it is better to use GameObject as the reference. Also, if what you need to do is activate, de-active, rotate the GameObject then use the GameObject as reference.
On the other hand, if you need to be able to call a function such as Drizzle or access a variable from the MakeItRain script from multiple places, then you need to use the MakeItRain reference. At this time, it doesn't make sense to use the GameObject reference since by using it, it's required to use GetComponent every-time you need to call a function or access a variable from the MakeItRain script attached to it.
Finally, when using the MakeItRain script to reference your object, you can directly and easily access the GameObject it is attached to without using the makeitrain.gameObject. This doesn't require the use of the GetComponent function.
Just wondering if there is any other rationale for not doing the
second method.
Performance issue due to the required use of the GetComponent function is the reason. Using it once in the Start or Awake function and initializing your MakeItRain variable is better.
For example, this is better:
public MakeItRain makeitrain;
void Start()
{
makeitrain = GetComponent<MakeItRain>();
}
void Update()
{
makeitrain.Drizzle();
}
than this:
public GameObject makeitrain;
void Update()
{
makeitrain.GetComponent<MakeItRain>().Drizzle();
}
And should be used to avoid having to search for the component on the native side every frame.
Using MakeItRain and explicitly defining the type is better than using GameObject.
As #hacksalot commented, using MakeItRain offers strong typing. One of the benefits of this is related to your comment:
In both cases, in the Inspector, I have to drag and drop GameObject A
into the slot of GameObject B.
If you explicitly set the public variable type to MakeItRain rather than GameObject, it is not possible to drag and drop a GameObject A into the slot of GameObject B unless GameObject A is has a script of the correct type. This gives you a compile-time/editor-time check that you are linking to the correct GameObject in the Unity Editor inspector.
Also, while not necessarily so, using GameObject references often encourages messier code, whether because of unnecessary chaining of methods together (e.g. GetComponent) just because the type wasn't specified, or because it adds a bit of friction to writing & using helper methods. Consider even in a simple example which one reads better:
makeitrain.Drizzle()
makeitrain.GetComponent<MakeItRain>().Drizzle()
I understand that the first method gives me more flexibility because I
could call other components of GameObject A as well and not just the
script's stuff.
Note that you still have the flexibility to access GameObject, it's just a bit more verbose (which is one downside of this approach):
public MakeItRain makeitrain;
void Start()
{
makeitrain.gameObject.SetActive(false)
}
However, you'll likely be using helper methods anyway (for anything more than basic calls), or even wrapper methods (which are inconvenient to write but sometimes helpful for readability).
In most cases, the benefits of linking to the class rather than the GameObject outweigh the downsides.
If u don't want to use GetComponent(), you can simply use SendMeassage(), like this
public Gameobject makeItRain;
void Start(){
makeitrain.SendMeassage("Drizzle");
}
Another way to link a script is that use FindObjectOfType(), which do not need to drag and drop GameObject into the slot, here is the sample
void Start(){
MakeItRain makeitrain = FindObjectOfType("MakeItRain");
}
Also you can use Gameobject.Find() to link a GameObject instead of dragging into slot, but I don't recommand this way, it cost a lot performance since you need to find every single GameObject in scene.

How to randomly generate GameObjects programmatically?

I'd like some help with the following issue. I'm making a simple game where you have a character running and he has to jump over obstacles coming at him.
And I'm currently stuck when it comes to creating GameObjects and randomly generating them within the game scene at run time.
I've written a class to help accomplish this:
using UnityEngine;
using System.Collections;
public class randomObstacles : MonoBehaviour {
public GameObject myCube;
public Vector3 spawnLocation = new Vector3(0,2,0);
// Use this for initialization
void Start () {
GameObject SpawnLocation = (GameObject)Instantiate(myCube, spawnLocation, Quaternion.identity);
}
// Update is called once per frame
void Update () {
}
}
The above code is what I wrote to simply create Objects, one after the other. But when I run the game, it comes up empty -_- !
Can anyone please tell me where am going wrong, and from the look of it my code doesn't seem to do what I am hoping to achieve :(
I've attached the above script into an empty GameObject as I saw in a tutorial from the Unity Community forum, but that did not help either.
(I've looked around and it seems like no one has come across such an issue - I could bee wrong)
It would seem that your myCube variable is the root of your problems, having tested it in a scene of my own. By subsituting
(GameObject)Instantiate(myCube, ...
with
(GameObject)Instantiate(GameObject.CreatePrimitive(PrimitiveType.Cube), ...
I was able to produce a cube at (0,2,0) with no qualms. Perhaps your myCube GameObject is missing a Mesh Renderer, in which case it would appear in the Hierarchy during runtime even though it would not appear visible in Game View. Perhaps you are not assigning it in the Inspector before you run the game, in which case the myCube variable would refer to null and thus not be created. Additionally, though you may be assigning the GameObject you instantiate to SpawnLocation, it is an unused local variable (something MonoDevelop or your code editor should notify you of). Make sure you either provide a reference to a myCube GameObject in the Inspector before runtime or through script referencing to a loaded prefab/GameObject during runtime. It would also be helpful to provide a fallback (say, to PrimitiveType.Cube perhaps?), which will make your code more robust and able to handle errors in referencing should they arise.
Regardless, in order to achieve the functionality you have described, first make sure yo have properly prepared whatever you would desire myCube to be. Also, for future posterity, you may want to initialize your spawnLocation in the Start routine, and assign the variable coordinates by substituting Random.value * yourValueCeiling in for each random coordinate you would like myCube to spawn on. You could even go as far to make a helper method externalized and thus independent from the start routine, such that you will not have to have a hundred instances of a single script to create a hundred instances of what you need; rather, you can call the method through a single script, and save yourself trouble in this way. If you would so appreciate it, here is my implementation of your objective, hope this helps!
using UnityEngine;
using System.Collections;
public class randomObstacles : MonoBehaviour {
public Vector3 spawnLocation;
public GameObject myCube;
// Use this for initialization
void Start () {
if (myCube != true) {
Debug.Log("myCube not set");
myCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}
if (myCube.renderer.enabled == false) {
Debug.Log("myCube not rendered");
myCube = GameObject.CreatePrimitive(PrimitiveType.Cube);
}
CreateCube();
}
// Update is called once per frame
void Update () {
}
void CreateCube() {
spawnLocation = new Vector3(0, Random.value * 10, 0);
Instantiate(myCube, spawnLocation, Quaternion.identity);
}
}
It may be worth pointing out that you're creating your object in the Start method, which means your code will only run once: from the name of your class, I'd assume you want to create more than one object using this code.
If you move the code into Update you'll create one object per frame, which is most likely too many. My guess would be that you want something like a coroutine that will run on a random interval, and then spawn cubes repeatedly over time, something like this:
void Start () {
StartCoroutine("SpawnObjects");
}
IEnumerator SpawnObjects()
{
while (keepAddingObjects) // a boolean - could just be "true" or could be controlled elsewhere
{
GameObject SpawnLocation = (GameObject)Instantiate(myCube, spawnLocation, Quaternion.identity);
float delay = Random.Range(1f, 5f); // adjust this to set frequency of obstacles
yield return new WaitForSeconds(delay);
}
}
Taken from my own code in a game that auto generate mazes:
public class Cell
{
private GameObject instance;
public void CreateVisual()
{
// Load a GameObject that exist inside the "Resources" folder.
GameObject prefab = (GameObject)Resources.Load("Models/Walls/3W1");
// Create an instance of the prefab
instance = (GameObject)GameObject.Instantiate(prefab);
instance.transform.position = myPosition;
}
}
I think the part you are missing is the Resources.Load() method.

Categories

Resources