1

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!

How I attatched the animator component

Enrico Cortinovis
  • 811
  • 3
  • 8
  • 31
  • `public Animator animator;` is never assigned a value, so it has the default `null` – UnholySheep Dec 16 '19 at 21:39
  • @UnholySheep sorry I haven't specified this in my question: I attatched it in the inspector – Enrico Cortinovis Dec 16 '19 at 21:40
  • 1
    How did you attach it in the inspector when the component is only created at runtime? – UnholySheep Dec 16 '19 at 21:41
  • Ahh, that may be it. You definitely have a null reference for your animator. – Premier Bromanov Dec 16 '19 at 21:42
  • @UnholySheep added a screenshot! – Enrico Cortinovis Dec 16 '19 at 21:45
  • Am i correct in assuming you've created a prefab called LevelChanger and you are trying to spawn a new instance of that prefab? – Premier Bromanov Dec 16 '19 at 21:45
  • 1
    `AddComponent` creates a *new* Component - whatever you have assigned in the inspector refers to a different component than the one your code creates – UnholySheep Dec 16 '19 at 21:46
  • @PremierBromanov No, It is just a prefab that I've put in some scenes where I need it, to use it to handle some fade animations! – Enrico Cortinovis Dec 16 '19 at 21:46
  • 1
    Why are you doing this then? `GameObject gameObject = new GameObject("LevelChanger");` – Premier Bromanov Dec 16 '19 at 21:47
  • @PremierBromanov was the last try i did, as I saw on some answer in the unity forum. I don't really know how that could work, it looked wrong to me, but I tried it :/ I tried removing it, and I'm still getting a NullReferenceException. – Enrico Cortinovis Dec 16 '19 at 21:48
  • 1
    @EnricoCortinovis Okay, so what `new GameObject()` does is creates a new empty gameobject. It exists in the scene, usually at the origin. It has a transform and nothing else. `AddComponent` adds a component to that gameobject. So what you've done is created a new game object with an instance of your component `LevelChanger`. Because these are both brand new objects, their inspector references are not set, so animator is null. If you want to get an instance of the prefab that is in your scene at edit-time, consider using an inspector reference. – Premier Bromanov Dec 16 '19 at 21:51
  • 2
    The `LevelChanger` instance you are editing in the inspector is a completely different one from the one you create with `gameObject.AddComponent();`. – Ruzihm Dec 16 '19 at 21:51
  • @PremierBromanov and Ruzihm Thank you. But how can i fix it? – Enrico Cortinovis Dec 16 '19 at 21:53
  • @EnricoCortinovis What does your scene's hierarchy look like in the editor before/during play? Does the `LevelChanger` gameobject exist in the scene or is it a prefab of some kind? – Ruzihm Dec 16 '19 at 21:55
  • @Ruzihm It is a prefab, but it already exists before runtime. – Enrico Cortinovis Dec 16 '19 at 21:56
  • What about Grounded? – Premier Bromanov Dec 16 '19 at 21:57
  • @PremierBromanov Grounded is a script attatched to another gameObject (prefab) that exists in the scene before runtime. – Enrico Cortinovis Dec 16 '19 at 21:59

3 Answers3

2

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
Ruzihm
  • 19,749
  • 5
  • 36
  • 48
2

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;
Premier Bromanov
  • 429
  • 6
  • 15
  • Thanks a lot for the answer! In the second way to do it (which i'm testing right now), don't I need the class to be static too? It seems to not work – Enrico Cortinovis Dec 16 '19 at 22:18
  • 1
    @EnricoCortinovis No, do not make the class static. – Draco18s no longer trusts SE Dec 16 '19 at 22:29
  • 1
    @EnricoCortinovis No, the class does not need to be static, because you will need to get a reference to an instance of it. Static classes don't exist in memory per se. So only your `Instance` field needs to be static. You can call it whatever you like, like `myInstance` or `bob` – Premier Bromanov Dec 17 '19 at 17:26
0

I went through a problem similar to that when I was trying to show Ads using Unity Ads. I solved it by first importing Unity.VisualScripting and then calling GetOrAddComponent<>() function.

using Unity.VisualScripting; \*first import VisualScripting*\
using UnityEngine;
using UnityEngine.SceneManagement;

public class GameTitleBehaviour : MonoBehaviour
{
    public void LoadLevel(string levelName)
    {
        UnityAdController unityAdController = gameObject.GetOrAddComponent<UnityAdController>(); \*Then calling GetOrAddComponent<>()*\

        \*can now use UnityAdController functions*\
        if (unityAdController.GetIsShowAds())
        {
            unityAdController.ShowAd();
        }

        SceneManager.LoadScene(levelName);
    }
}

In your case it would be:

First import VisualScripting

using Unity.VisualScripting;

Then call GetOrAddComponent<>()

LevelChanger levelChanger = gameObject.GetOrAddComponent<LevelChanger>();

Now you can use LevelChanger's functions

levelChanger.FadeOut(true);