Search Unity

  1. Welcome to the Unity Forums! Please take the time to read our Code of Conduct to familiarize yourself with the forum rules and how to post constructively.
  2. Dismiss Notice

Question Get a reference to an object in a manager scene.

Discussion in 'Scripting' started by TiggyFairy, May 22, 2023.

  1. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    I've been keeping my UI and core game code in a manager scene to keep it organised. The idea is that the contents of the manager is always loaded, no matter what else is. This has worked great, and I hear other people do this. But I've just discovered you can't reference from one scene to another, even if it's static. I've been told the only really effective fix is to put everything in DontDistroyOnLoad. Is that the case?
     
  2. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    You can. You can't store a cross-scene reference, but there are lots of ways to grab the reference at run time. A static locator should work fine.
     
    TiggyFairy likes this.
  3. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    Here's a blast of related and unrelated data.

    Whatever you do , keep it simple, don't try to out-clever yourself.

    Remember the first rule of GameObject.Find():

    Do not use GameObject.Find();

    More information: https://starmanta.gitbooks.io/unitytipsredux/content/first-question.html

    More information: https://forum.unity.com/threads/why-cant-i-find-the-other-objects.1360192/#post-8581066

    ULTRA-simple static solution to a GameManager:

    https://forum.unity.com/threads/i-need-to-save-the-score-when-the-scene-resets.1168766/#post-7488068

    https://gist.github.com/kurtdekker/50faa0d78cd978375b2fe465d55b282b

    OR for a more-complex "lives as a MonoBehaviour or ScriptableObject" solution...

    Simple Singleton (UnitySingleton):

    Some super-simple Singleton examples to take and modify:

    Simple Unity3D Singleton (no predefined data):

    https://gist.github.com/kurtdekker/775bb97614047072f7004d6fb9ccce30

    Unity3D Singleton with a Prefab (or a ScriptableObject) used for predefined data:

    https://gist.github.com/kurtdekker/2f07be6f6a844cf82110fc42a774a625

    These are pure-code solutions, DO NOT put anything into any scene, just access it via .Instance!

    The above solutions can be modified to additively load a scene instead, BUT scenes do not load until end of frame, which means your static factory cannot return the instance that will be in the to-be-loaded scene. This is a minor limitation that is simple to work around.

    If it is a GameManager, when the game is over, make a function in that singleton that Destroys itself so the next time you access it you get a fresh one, something like:

    Code (csharp):
    1. public void DestroyThyself()
    2. {
    3.    Destroy(gameObject);
    4.    Instance = null;    // because destroy doesn't happen until end of frame
    5. }
    There are also lots of Youtube tutorials on the concepts involved in making a suitable GameManager, which obviously depends a lot on what your game might need.

    OR just make a custom ScriptableObject that has the shared fields you want for the duration of many scenes, and drag references to that one ScriptableObject instance into everything that needs it. It scales up to a certain point.

    And finally there's always just a simple "static locator" pattern you can use on MonoBehaviour-derived classes, just to give global access to them during their lifecycle.

    WARNING: this does NOT control their uniqueness.

    WARNING: this does NOT control their lifecycle.

    Code (csharp):
    1. public static MyClass Instance { get; private set; }
    2.  
    3. void OnEnable()
    4. {
    5.   Instance = this;
    6. }
    7. void OnDisable()
    8. {
    9.   Instance = null;     // keep everybody honest when we're not around
    10. }
    Anyone can get at it via
    MyClass.Instance.
    , but only while it exists.
     
    TiggyFairy likes this.
  4. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    Thank you, but I'm not totally sure statics are going to work because the code that made me realise there was a disconnect is this:
    Code (CSharp):
    1. void Awake() {instance = this;}
    2.  
    3. public static InventoryItem GetHeldIten() {  Debug.Log("inv.heldItem " + inv.heldItem.name); return instance.inv.heldItem; }
    Which, if I'm reading yours right, should work. However - even though this file isn't even instanced, not even the debug log fires. There's no error. Nothing. The code just does nothing. I don't understand it tbh.

    You should tell that to all the youtube tutorials. :p I swear, literally every one of them does this for just the most random things. Usually the very first thing they need from their scene.
     
  5. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    In fact, I just tested the same thing with this in two different statics files:
    Code (CSharp):
    1. public static int BOOP(int test) { Debug.Log("BOOP " + (test + 5)); return test + 5; }
    And this in the other method:
    Code (CSharp):
    1. Debug.Log("Returning BOOP " + Interfaces.BOOP(5));
    And I got nothing at all - even though they're not attached to anything, plus other debugs in the trigger method work fine so that's running, and I can fire code in those statics from inside the manager. I'm really not sure what to make of this because everything I know says it should work. And, it has. Just only in the manager. Doesn't even seem to matter if the class itself is static or not.
     
    Last edited: May 23, 2023
  6. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    None of the code samples that you've posted have enough context for me to figure out what you are even trying to do.

    For example you say the following code does nothing:
    Code (csharp):
    1.  
    2. void Awake() {instance = this;}
    3. public static InventoryItem GetHeldIten() {  Debug.Log("inv.heldItem " + inv.heldItem.name); return instance.inv.heldItem; }
    4.  
    Of course it does nothing on it's own. You have to call the GetHeldIten method from somewhere. I assume that you are, and that maybe you're doing it cross-scene, but you didn't mention that anywhere. I can't really attempt to troubleshoot if I don't know the circumstances.

    One problem you might encounter is a matter of Timing. For example, if I have some sort of GameManager singleton that says:
    public static GameManager gameManagerInstance=this;

    I can reference gameManagerInstance from another scene, but if the code in my other scene attempts to reference gameManagerInstance before the above line of code executes then it will certainly fail because gameManagerInstance is yet unassigned. This whole arrangement will only work if the scene that contains my GameManager loads first.

    If I have both scenes open in the editor at the same time, and I press play, I have no real guarantee which scene's scripts will run first.
     
  7. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    I'm sorry I may have explained poorly. If you look at my second post you should be able to see what I did. Basically I've got a class called Interfaces which is not attached to any game objects. In that class I have this code:

    Code (CSharp):
    1. public static int BOOP(int test) { Debug.Log("BOOP " + (test + 5)); return test + 5; }
    I then have a monobehavior in my test scene which runs this code:

    Code (CSharp):
    1. Debug.Log("Returning BOOP " + Interfaces.BOOP(5));
    The Debug before that line fires, and so does the Debug right after it. But rhe actual line does nothing.
     
  8. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    Are you sure that Debug.Log works in a static method? I've never tried it before, but it may need a reference to a specific instance for the console. Not sure.
     
  9. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    That's a fair point, I can check that. But the debug is newer than the return - and that doesn't work.
     
  10. kdgalla

    kdgalla

    Joined:
    Mar 15, 2013
    Posts:
    4,326
    If the code even compiles I don't believe it's even possible for a return to "not work". Do you mean that it's returning the wrong number?
     
  11. TiggyFairy

    TiggyFairy

    Joined:
    Dec 22, 2019
    Posts:
    413
    I think I might have been running the wrong script, with similar debugs lol. It's working now, thank you.
     
  12. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    36,563
    This is why I wrote this:

    Trying to be clever or different on Game Managers and other such long-lived scripts probably accounts for one out of every ten posts on this forum.

    Even the simplest possible setup will have enough complexity to get you in trouble. Don't add more.
     
  13. Ryiah

    Ryiah

    Joined:
    Oct 11, 2012
    Posts:
    20,082
    That said if you do want a more complex approach there are assets for that. I've been playing around with one of the simplest dependency injection frameworks I've ever seen. In this example the manager is in a separate scene and located by the framework, but I could have it instantiate it if not found with
    LazyInit = true
    .

    Code (CSharp):
    1. using Sisus.Init;
    2. using UnityEngine;
    3.  
    4. public interface ISomeManager
    5. {
    6.     public void Boop(int x);
    7. }
    8.  
    9. [Service(typeof(ISomeManager), FindFromScene = true)]
    10. public class SomeManager : MonoBehaviour, ISomeManager
    11. {
    12.     public void Boop(int x)
    13.     {
    14.         Debug.Log("Boop: " + x);
    15.     }
    16. }
    Code (CSharp):
    1. using Sisus.Init;
    2. using UnityEngine;
    3.  
    4. public class SomeObject : MonoBehaviour<ISomeManager>
    5. {
    6.     private ISomeManager SomeManager;
    7.  
    8.     protected override void Init(ISomeManager someManager)
    9.     {
    10.         SomeManager = someManager;
    11.     }
    12.  
    13.     private void Start()
    14.     {
    15.         SomeManager.Boop(5);
    16.     }
    17. }

    https://forum.unity.com/threads/init-args-the-practical-di-framework.1215084/
    https://docs.sisus.co/init-args/features/services/
     
    Last edited: May 31, 2023