Search Unity

Discussion "Best" way to pass parameters to another scene to set up that scene

Discussion in 'Scripting' started by Olipool, Apr 11, 2023.

  1. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Hi all,

    to illustrate the question with an example, let us assume the following:
    I have a scene with a city map with a few buttons for locations to go to. The locations are just other panels in the scene. I have a map panel, a car dealer panel, a hotel room panel, etc. and when I click on the hotel button on the map, all panels are deactivated and the hotel room panel is activated. And when I press the "leave" button in the hotel room panel is pressed, then the map panel is shown again. (see screenshot, the map is a placeholder from the web)

    These panels are for simple locations with not much gameplay.
    But I have some mechanics with more complex behaviour so I put them in extra scenes. For example, from the hotel room location, I can go to a scene where I can plan a heist by moving characters around etc. And when I am done with my planning I want to return to the hotel room.

    So I am loading my city map scene but of course, I will arrive directly on the city map because that is the active location by default in that scene. So I need a way to tell the scene after loading to show the hotel room location panel. That brings me to the original question of how to do that. I can think of the following options:

    1. Use PlayerPrefs to set the desired location and then load the scene. In the scene, a script e.g. LocationManager has to evaluate the PlayerPref and if it is set then show the desired scene.
    2. Instead of PlayerPrefs use a static enum in the LocationManager and set that before loading the scene (closer to the code and leaves no "trash" behind after ending the game.
    3. Make the LocationManager a singleton and DontDestroyOnLoad and then from any scene just call: LocationManager.Instance.GoToHotelRoom(). Then the LocationManager is responsible for loading the scene. BUT: showing the right location can only be done after the scene has loaded so I need to subscribe to SceneManager.sceneLoaded, then show the location panel there and unsubscribe which is a bit "indirect" and maybe confusing. (would be great to have some kind of Start() for non destroyable objects).
    4. A mixture of 2 and 3, call GoToHotelRoom from everywhere, store the desired location in the singleton and load the map scene and have an initializer there that will have Start() called and can show the location panel there.
    5. Yeah... I can put every location in its own scene and directly load the desired scene. But somehow I feel this could bite me later when I want to make a small change to all locations (for example add a button showing info about the location and it should be at the same spot for every location...I could use prefabs or load the common functionality additively from a "common" scene to each location scene... so many possibilities :)
    Any other options? Somehow none of those have sparked my "this is the best way" sense. So I request for comments ;)
    panelProblem.PNG
     
  2. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,751
    I just like to make a GameManager. It might contain other sub-managers such as location managers, but basically it would be the ONE long-lived object that persists throughout the Game, managing the game. Hence... GameManager!

    Think of scenes as just guests or visitors... they fire up and have no idea what to do if it is game-variable.

    They would look to the GameManager and get everything there.

    It scales well because in the future let's say there is a condition such as "Hotel was destroyed!" That condition would be recorded and then there would be no hotel, according to the GM.

    And here's my usual blurbs:

    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.
     
    Ryiah and Olipool like this.
  3. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Thanks for the detailed answer and examples Kurt.

    A big GameManager was (and somehow is) my go-to solution far. In the last months, I read some books about software design etc. and I am a bit torn between worlds. Having a singleton with many variables/responsibilities accessible from everywhere is usually a huge smell. Maybe I will end up with 100 variables in there. In my case, why should the GM and everyone know what location I wanted to show a while ago. Sure, that is why we can use multiple GMs to divide the logic.

    So I am trying more and more to keep everything a bit separated and I find it hard sometimes with Unity because obviously those GameManager singletons are a big thing/concept in the engine and they work well in many situations. Sometimes I feel I am going against the engine when making classes that ignore there is a Unity somewhere :D

    But it is good to hear a GM is not a total anti-pattern in Unity. Thanks again!
     
  4. spiney199

    spiney199

    Joined:
    Feb 11, 2021
    Posts:
    7,935
    I think rather than thinking about in terms of 'passing' values to a scene, think of it the other way around. When a scene loads in, it could instead look for information that may or may not be present.

    It should be able to gracefully handle this information not being present (but still report it perhaps), which is handy for testing. And this could be done with a simple manager, or a service locator, for example.

    You can have more than one manager. Most of mine are just static classes with singular responsibilities, which act more like API's that various other parts of my project can use.
     
  5. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,751
    I never reuse these things, I just restart one for each game, do the absolute minimum going forward, and often the first implementation ends up being good enough for the entire game.

    Rarely does the shape of data-lifecycle containers like this negatively affect other design portions of the game. It's generally easy enough to refactor as long as you kept a nice tidy API of some kind, even if it was static and now you want it to be instance-y.

    To me there is always far more value in just moving forward and building up whatever GM I happen to need as I go, and ONLY what I need as I go.

    Just say no to pre-writing code only for it to remain dark and unused.
     
    Olipool and Ryiah like this.
  6. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    That was what brought me joy in the past, just hacking away and seeing things come to life :)
    Now I tend to brood over the design for way too long. I guess that will pass once I have coded a few designs so they are just second nature. And also make it work fast and refactoring after that is something I need to practice more.
    Also, small-ish games don't need that level of engineering, once it is done it is done. For my day job, I am working on software that has to be extended and maintained for a lot of years so that is a different matter and I need to leave that at work ;)
     
    Kurt-Dekker likes this.
  7. Kurt-Dekker

    Kurt-Dekker

    Joined:
    Mar 16, 2013
    Posts:
    38,751
    Don't do it! Be like your happy rabbit avatar and go nuts!!

    I give you permission to do this and make as many games as you like as fast as you like with as little or as much engineering as you want.

    GO!!
     
    Olipool likes this.
  8. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    :D Haha, thanks Kurt, will do!

    Btw. the rabbit is no rabbit but a... creature called Totoro. Before leaving this thread alone, I highly recommend watching the movie of Totoro as it is very joyful :)
     
    Kurt-Dekker likes this.
  9. EOMedvis

    EOMedvis

    Joined:
    Feb 19, 2019
    Posts:
    94
    I'm assuming there is only 1 copy of the panels in your game available at a time. This is just something I've used for persistent game Objects.

    I would have a public static script with publicly accessible variables. I would use this script to store all variables that I would like the transfer between scenes. I would make the Panels all prefabs with public accessible scripts variables.
    Before scene changes, I would store all relevant variables in static script, then create the panel on the new scene load and populate all the variables I stored.
     
    Olipool and Kurt-Dekker like this.
  10. Olipool

    Olipool

    Joined:
    Feb 8, 2015
    Posts:
    322
    Thanks! One question, why would the panels need publicly accessible scripts? Can't each panel just pull for the variables of interest from the static script and set them each on its own?
     
  11. juanmeanwhile

    juanmeanwhile

    Joined:
    Jul 17, 2013
    Posts:
    1
    Hi @Olipool! I feel the need to say that you are going in the right direction. A huge GameManager, or let's call it by another name, a singleton, it's an anti-pattern in every software development environment, but unfortunately there is no way around it in Unity.

    A Singleton does make following the flow of execution way harder (which class write what there and when?) and is a known source of side effects bugs. When using singletons, it recommended to minimise it's access as much as posible to as little classes as possible. I.e: instead of every object in an scene accessing the singleton to get what their need, you can have an SceneManager object that is the only one in charge to get data from the singleton as pass it to the rest of the objects. Also it can be the only one responsible from writing into it. About what to store in the singleton, keep it to the minimum, only what cannot be passed in any other way.
     
  12. SisusCo

    SisusCo

    Joined:
    Jan 29, 2019
    Posts:
    1,331
    Well... using singletons is always a choice, even in Unity. Using dependency injection instead is almost always also an option.

    Even without doing any fancy stuff with automated dependency injection, almost any situation can be solved with good old Inspector drag-and-drop and strategic use of scriptable objects. For example:
    Code (CSharp):
    1. sealed class OnClick : MonoBehaviour
    2. {
    3.     [SerializeField] Button button;
    4.     [SerializeField] Command command;
    5.  
    6.     void OnEnable() => button.onClick.AddListener(command.Execute);
    7.     void OnDisable() => button.onClick.RemoveListener(command.Execute);
    8. }
    9.  
    10. [CreateAssetMenu]
    11. sealed class GoToLocationCommand : Command
    12. {
    13.     [SerializeField] Location location;
    14.     [SerializeField] LocationManager locationManager;
    15.  
    16.     public override void Execute() => locationManager.GoTo(location);
    17. }
    18.  
    19. [CreateAssetMenu]
    20. sealed class LocationManager : ScriptableObject
    21. {
    22.     [SerializeField] CoroutineRunner coroutineRunner;
    23.     [SerializeField] PanelManager panelManager;
    24.  
    25.     public void GoTo(Location location) => coroutineRunner.Start(GoToCoroutine(location));
    26.  
    27.     IEnumerator GoToCoroutine(Location location)
    28.     {
    29.         int sceneBuildIndex = location.buildIndex;
    30.         var scene = SceneManager.GetSceneByBuildIndex(sceneBuildIndex);
    31.         if(!scene.isLoaded)
    32.         {
    33.             var loadOperation = SceneManager.LoadSceneAsync(sceneBuildIndex);
    34.             while(!loadOperation.isDone)
    35.             {
    36.                 yield return null;
    37.             }
    38.         }
    39.        
    40.         panelManager.OpenPanel(location.panel);
    41.     }
    42. }
    But please don't brood over finding the "perfect" designs too much; try to find a good balance of enjoying good anime, getting things done and gradual self-improvement! ;)

    Over time with experience things will start to "click" more and more, and you'll be able to more easily make design decisions based on avoiding as many of the pain-points your past self has had to live through :D
     
    CodeRonnie likes this.