How do you handle a case when a game (and all of its systems) cannot be initialized synchronously on a start. For example you need to pull some data from a server before you can do something.
I do not want to break a game-designer’s experience I want to allow them play the game from any scene they are working on. And I do not bloat my code with if-checks to see here and there if something is loaded.
you either wait for data to be fetched with a black screen, blocking all player actions OR you use placeholder objects for whatever it is you’re loading (common thing with mmo avatars while they’re being loaded)…
For designers debugging scenes you implement cheat menu. For any external servers you might be needing you either start a test server for designers, or implement a placeholder. It is similar to unit test proactices of using fake data providers for the purposes of testing.
Personally, I’d look into making sure the data can be initialized on startup.
1 Like
Adhering to all your requirements will be a little hard.
My thoughts on how to achieve this:
Method 1
Have a scene that holds your game initialization and use LoadScene in additive mode. On enter play mode check if the scene hasn’t been loaded yet and load if necessary. Either as an OnEnable or a enterPlayMode callback.
As you are asynchronously getting stuff from the server and you don’t want to clutter the gameplay code with ifs that won’t be hit during normal play you need to deal with initialization order.
-
If you already have a system in place that initializes everything in the scene on a specific callback you are golden, just call it afterwards.
-
If you rely on OnEnable/Awake/Start for things like FindObjectOfType that is only present after initialization from a server. I am gonna assume that is what you have and due to ease of use for non core programming folks won’t change. Your only option is to delay the OnEnable/Awake/Start calls for scenes.
-
Hacky Solution 1: Start the scene with all GO inactive. I could envision using an OnEnable / RuntimeInitializeOnLoad callback or maybe SceneManager.onSceneLoaded to temporarily disable all gameobjects (of course remember their current state beforehand). Then after the initialization has finished just restore the original scene state.
-
Hacky Solution 2: Use RuntimeInitializeOnLoadAttribute together with BeforeSceneLoad to stall the mono engine until your server response is finished. (Doing webrequest synchronously).
Method 2
A thing I implemented for one of the games I am working on:
Using GetSceneManagerSetup you can get the current scene setup and save it somewhere (I put it in EditorPrefs). After that have a custom button that starts the scene as “gameplay” by loading the intial scene first. Then after initialization finishes you can check the saved scenes and load accordingly.
-
For our game I made this easer by putting a big fat “Play game from any scene” button right next to the normal play button of unity. As this depends on another hacky solution of mine which allows me to add IMGUI/UITK directly to the top toolbar this would be quite an investment relearning of how to start the game from the editor.
-
Downsides
-
The designers obviously need to press the correct button / menuitem / quicksearch. But as the current workflow likely involves navigating to the correct scene anyway this might not be such a problem
-
Your designers absolutely need to save their scenes before starting. Unity will automatically ask for this and you even save the scene programmatically before starting the game, but you will definitely lose the ability to start a modified scene without saving.
If by any chance a unity employee sees this (I am just starting to look into all capabilities of versions 2020 and beyond so there might be better solutions)
- Game initialization is a kind of unsolved problem . Having a way like RuntimeInitializeOnLoad that can be implemented over multiple frames would really help for such scenarios and might also minimize the “GameSetupScenes” which only have one MonoBehaviour using it’s start to load everything else.
- Adding things to the main toolbar where the play button resides gave us a tremendous boost (perceived) productivity boost (e.g. Play game button so you don’t need to switch scenes manually, adding buttons to quickly enable / disable features and see on first glance which global settings are set). A formalized solution to this would really be welcome. Still need to look into the toolbar / workflow prototype.
2 Likes
Thank you! It helped, so what I did today (I am using Zenject):
-
I created a script which has the highest priority execution order -10000. This script takes all root objects of the scene and puts them to a new deactivated root game object. So all original objects (including Scene Context) become deactivated before their Awakes are invoked. NOTE: This will allow me to activate all original objects at once later.
-
I initialize the ProjectContext by calling ProjectContext.Instance.EnsureIsInitialized(); (this is a most global object which contains all global services)
-
Then I wait until all global services get loaded.
-
Then I activate my deactivated root and this makes all my original scene objects get activated at once so SceneContext awakes and injects everything else.
-
Then I move all original objects to the scene root so that scene looks unchanged.
1 Like
Glad I could help.
One thing you might consider is how moving all root objects in your scene to another gameobject affects performance. As always try to profile it first but I would guess depending on the amount of gameobjects you have this can have surprising performance impacts depending on how unity handles disabled transforms e.g. all transforms that share a root gameobject can only be updated in the same thread so moving everything in and out of the same root object might cause some initial hickup when everything needs to be updated and reparented.
You might be able to by with storing the state and restoring it afterwards without the need for a root object to preserve the script execution order.
Performance here does not matter because this is a hack for playing in the Editor.