[Solved] Global accesor problem.

Announcements, support questions, and discussion for the Dialogue System.
Post Reply
Japtor
Posts: 120
Joined: Thu Jun 28, 2018 1:41 pm

[Solved] Global accesor problem.

Post by Japtor »

Hi,

I have attached a UI prefab inside the Dialogue Manager -> Instantiate prefabs -> prefabs.

Inside the UI parent, I have a script which I am interested to make it as a "global accessor". So every script inside the game, if it needs it, they can access to their functions. To test if it can access various scripts, I have written one function that changes the UI visibility:

Code: Select all

public class MManager : MonoBehaviour {

  private static MManager m_Instance;
  public static MManager GetInstance()
  {
    return m_Instance;
  }

  // All the child panels
  public GameObject m_Child;

  private void Awake()
  {
    m_Instance = this;
  }

  public void SetOpen(bool value)
  {
    m_Child.SetActive(value);
  }
}
Then, Inside the canvas (provisional place) I have a script that calls the SetOpen function from the previous script:

Code: Select all

public class TestMCall : MonoBehaviour
{
  private void Update()
  {
    if (MManager.GetInstance() != null)
    {
      if (Input.GetKeyDown(KeyCode.E))
      {
        if (!MManager.GetInstance().m_Child.activeInHierarchy)
          MManager.GetInstance().SetOpen(true);
        else
          MManager.GetInstance().SetOpen(false);
      }
    }
  }
}
Inside the UI, when it is visible, you can see a button. When you click it, it calls a load level function from a new script inside itself:

Code: Select all

public class MLoadScene : MonoBehaviour {

  public void LoadLevel(int scene)
  {
    StartCoroutine(LoadAsync(scene));
  }

  IEnumerator LoadAsync(int scene)
  {
    MManager.GetInstance().SetOpen(false);

    AsyncOperation async = SceneManager.LoadSceneAsync(scene);

    while (!async.isDone)
    {
      yield return null;
    }
  }
}
The problem is that, when It loads the level (itself), the MManager.GetInstance() seems to have lost its reference. Any idea of why of that? (Sorry, I am a newbie to coding).

Thanks!
Last edited by Japtor on Fri Oct 26, 2018 6:26 am, edited 1 time in total.
User avatar
Tony Li
Posts: 22057
Joined: Thu Jul 18, 2013 1:27 pm

Re: Global accesor problem.

Post by Tony Li »

Hi,

If you can move

Code: Select all

m_Instance = this;
to Start() instead of Awake(), then it should work.

The Dialogue Manager GameObject defaults to Don't Destroy On Load and Allow Only One Instance.

Since it's set to Don't Destroy On Load, the original Dialogue Manager survives the level load. Since the level that it loaded has a Dialogue Manager in it, this means the scene now has two Dialogue Managers: the one from the original scene, and the new one.

Allow Only One Instance tells the new Dialogue Manager to check if there's already a Dialogue Manager in the scene. Since there is, the new Dialogue Manager destroys itself. It does this in Awake().

At the same time, before the new Dialogue Manager gets destroyed, your script's Awake() method in the new Dialogue Manager reassigns m_Instance.

One solution is to move it to Start() like I mentioned above. If the Dialogue Manager destroys itself in Awake(), your script's Start() method will never be called (which is correct). I can suggest other solutions if that won't work for you.
Japtor
Posts: 120
Joined: Thu Jun 28, 2018 1:41 pm

Re: Global accesor problem.

Post by Japtor »

Hi,

Thanks for your answer! However, I have changed the Awake method to Start as you mentioned before and stills losing the reference after loading the scene.

Code: Select all

private void Start()
{
  m_Instance = this;
}
 
What are the other suggestions? :)

Thanks!
User avatar
Tony Li
Posts: 22057
Joined: Thu Jul 18, 2013 1:27 pm

Re: Global accesor problem.

Post by Tony Li »

That shouldn't happen unless you have another copy of MManager that's not under the Dialogue Manager, or if your Dialogue Manager's Dont Destroy On Load and Allow Only One Instance checkboxes are unticked.

However, here's another way you can do it.

Replace:

Code: Select all

private static MManager m_Instance;
public static MManager GetInstance()
{
    return m_Instance;
}

private void Awake()
{
    m_Instance = this;
}
with:

Code: Select all

private static System.Collections.Generic.List<MManager> m_Instances = new System.Collections.Generic.List<MManager>();
public static MManager GetInstance()
{
    if (m_Instances.Count == 0) return null;
    else return m_Instances[0];
}

private void Awake()
{
    m_Instances.Add(this);
}

private void OnDestroy()
{
    m_Instances.Remove(this);
}
This will keep a list of all instances in the scene. When an instance appears, it will add itself to the list. When the instance disappears, it will remove itself from the list. The list really should only have one item. But this should handle any strange cases where there are temporarily two of them (before one of them gets destroyed).
Japtor
Posts: 120
Joined: Thu Jun 28, 2018 1:41 pm

Re: Global accesor problem.

Post by Japtor »

Hi,

This seems to work fine. Thanks!

It is weird about the Start(). I have checked the Hierarchy and there is only one MManager script active inside the scene and Don't Destroy On Load and Allow Only One Instance checkboxes are ticked.

One last question: Should I use your method in every script that I want to have a "global accessor" or only the ones that are going to be attached to the Dialogue Manager?

Thanks for all, as always! :)
User avatar
Tony Li
Posts: 22057
Joined: Thu Jul 18, 2013 1:27 pm

Re: Global accesor problem.

Post by Tony Li »

That is weird. If you want to get to the bottom of it, you could add a Debug.Log line in Start(), something like:

Code: Select all

void Start()
{
    Debug.Log("Setting m_Instance to " + name, this);
    m_Instance = this;
}
It might tell you which GameObject(s) have the MManager script that's reassigning m_Instance.

It don't think it would hurt to use the List<MManager> approach everywhere. But you probably really only need it on GameObjects that behave like Dialogue Manager.
Japtor
Posts: 120
Joined: Thu Jun 28, 2018 1:41 pm

Re: Global accesor problem.

Post by Japtor »

Hi,

I used your example and it seems to only call one time. So, I guess its correct. When it loads the scene it calls the Debug.Log again. However, it loses the reference at that point.

Probably I am missing something! :?

Thanks.
User avatar
Tony Li
Posts: 22057
Joined: Thu Jul 18, 2013 1:27 pm

Re: Global accesor problem.

Post by Tony Li »

Sorry, I totally forgot that you were using Instantiate Prefabs!

It appears that this is a weird quirk of Unity. If you instantiate a GameObject in Awake(), and even if its parent is also destroyed in Awake(), the instantiated GameObject's Start() methods do in fact still run. So the List<MManager> thing is probably the best solution in this case.
Japtor
Posts: 120
Joined: Thu Jun 28, 2018 1:41 pm

Re: Global accesor problem.

Post by Japtor »

Hi!

I see! It is fine! Don't worry :P

Thanks for the List example thing! It helped me a lot :)
Post Reply