How to Manage Objects Common for Multiple Levels?

Hi,

I guess my task is not something specific, but I couldn’t find a good solution for it.

I’m working on a tower defense game with multiple levels. This is my first game made in Unity, so I may be missing many essential concepts.

All levels have Common Objects:

  • UI
  • Camera
  • Light
  • Level controllers

And each level has Level-Specific Objects:

  • Environment
  • Walkable paths

The question is how to manage Common Objects on multiple levels from a developer perspective?

“From developer perspective” means absence of duplication (no necessity to go to each scene to add a new object or to modify one parameter for, let’s say, camera), so it would reduce chance of errors.

I tried two approaches so far, but none I like.

Approach #1: “Main Objects” prefab

Create a prefab, which contains all Common Objects.

Cons:

  • impossible to work with child objects as prefabs. Only parent object can be a prefab. So it’s impossible to use small prefabs for duplicating objects inside “Main Objects” prefab (e.g., menu items)
  • camera (maybe some other objects) is not really stored as a prefab, so its settings should be updated on each level. It means that if I’d like to change the camera’s angle for all levels, I should go to each scene and update the settings.

Approach #2: Multi-Scene Level

Create a scene that includes only Common Objects. A level is loaded from multiple scenes: the scene with Common Objects and a scene with Level-Specific Objects.

Cons:

  • difficult (or impossible?) to implement a preloader for such level. Can’t track progress for additive scene loading mode. At least, I could not implement it. AsyncOperation did not update progress until the second scene load is initiated (or something like this).
  • difficult to start a scene as is. Requires additional scripts to load additional scene with Common Objects (the smallest challange)
  • difficult to work with the level and Common Objects scenes. Requires specific implementation to pass data from objects in one scene to objects in another, difficult to build levels, when part of the level is not visible (located in another scene)

#2 looks not so bad, if 1st con would be solved.

Approach #3: Involvement of a Level Manager/Builder

This is the next approach I’m going to consider.

I’ve already implemented a small builder for non-walkable areas on levels, so having one real scene a level designer can load areas information for a specific level, edit and save it. But it has very simple data to save (coordinates).

It looks more complicated to manage arbitrary Level-Specific Objects:

  • how to understand which objects on the scene should be saved (considered level-specific)?
    • should the level-designer choose them explicitly?
    • should thay be recognized by a tag?
  • how to save/load objects (references to prefabs)?
  • it also requires some script to make start of a level possible from the scene in Editor (requires some way to specify the level)

Approach #3 looks complicated to me, so I’d like to understand whether this is the only possible way or maybe there are simpler ways that I’m not aware of?

Thanks in advance!

@buskamuza
wow! very interesting question.
I’m in my first Unity project too, so don’t expect a big and extremely optimized response, but I’ll try to help you…
If I got the idea, you are doing a tower defense game that you have some collectibles and you want to maintain them in your inventory, and also you want to use a lot of objects that are the same in lots of scenes.
have you already heard about Object.DontDestroyOnLoad? this is used to maintain the object when you load another scene. Maybe if you do this, you don’t need to use Async operations like SceneManager.LoadSceneAsync.
I hope this will help you with something, but if not, I’m sorry.
see ya :slight_smile:

Hi @buskamuza, I actually use the method #2 and it works great.

This is the result scene composition on runtime for my first stage, I understand that you want something like this?:
82737-multiplescenes.png

In my project every “stage” scene is composed of 3 scenes:

  • Common: all common objects like the camera, UI and such
  • StageXX_Logic: all needed logic scripts, triggers, events, enemies, etc.
  • StageXX_Art: the stage environment with effects and as many things as you want

All enemies can go in the art scene but since I manage them from the logic I put them in the Logic scene. The art scene has a script that looks for the main logic script and starts it. The lights are baked only at the StageXX_Art.

I have a SceneLoadService class in my loading scene. Whenever I want to load a scene I call an static LoadSceneBatch and I pass it a scene name array. The method first loads the loading scene with SceneManager.LoadScene so everything is destroyed, then it loads the scenes using SceneManager.LoadSceneAsync, then it unloads itself.

This way you can reuse (or not) your scenes and the loader manages all by itself.

Suposing you have:

  • scenes: a scene name array
  • index: starting at 0, the current loading scene index
  • ProgressSlider: maybe you have a progressbar?

The code would be:

            while (index < scenes.Length)
            {
                var async = SceneManager.LoadSceneAsync(scenes[index], LoadSceneMode.Additive);
                while (!async.isDone)
                {
                    if (ProgressSlider != null)
                    {
                        ProgressSlider.value = index*stepLength + async.progress*stepLength;
                    }
                    Debug.Log(string.Format("{0} - {1:P0}", scenes[index], index * stepLength + async.progress * stepLength));
                    yield return new WaitForNewFrame();
                }

                index++;
            }

            var lastScene = SceneManager.GetSceneByName(scenes[index - 1]);
            var currentScene = SceneManager.GetActiveScene();
            SceneManager.SetActiveScene(lastScene);
            var uAsync = SceneManager.UnloadSceneAsync(currentScene);
            while (!uAsync.isDone)
            {
                yield return new WaitForNewFrame();
            }

Hope it works for you! :slight_smile: