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!
Hi, we are using option 1. Nested prefabs are implemented with storing a "parent" prefab object in each scene that reference other common prefabs. Then, during Awake of this object, instantiate other prefabs (e.g. menu is instancing prefabs of menu items during awake, using some dummy empty objects as anchor/reference transform). Each scene then contains a unique "parent" prefab that will manage the life of other common prefabs.
The scene itself will contain objects that are unique to each scene. A special object/prefab could be used to customize all "settings" of this level, like specific camera settings. If this object is a prefab, it can contain some kind of default values for all levels. It's still sometimes needed to open all scenes and change some settings, but with the new multi-scene editing and editor-side helpers, it becomes quite easy to write an editor only script to automate this.
Hi @dns , thanks for your comment. Interesting idea about "settings object". And about "parent" prefabs, just to clarify. I understand the idea of instantiating child objects, it looks interesting. But about the "parent" prefabs -- do you have multiple such objects on each scene, each with its function? So, if you need a new such object, you should add it to each scene, but as soon as you have all necessary "parent" objects, you can configure them in one place (as prefabs). Is it correct?
We have a Game$$anonymous$$anager prefab object in each scene (the "parent" prefab). It has the DontdestroyOnLoadFlag. This object has a script that will instantiate (as child) other prefabs like UI$$anonymous$$anager, or Sound$$anonymous$$anager, or AI$$anonymous$$anager. All those other "manager" scripts will have their own responsibilities. The advantage is that you can start any scene, the Game$$anonymous$$anager there will instantiate what it needs for the game to run. If you are loading another scene from a scene, well, those stuffs are already instantiated so the Game$$anonymous$$anager just need to not re-instantiate other managers (using some singleton-like mechanisms).
For stuffs specific for each scene, this Game$$anonymous$$anager could check the "settings" object and instantiate or destroy objects depending on those settings. I.e. a "against the clock gameplay" level could instantiate a Clock$$anonymous$$anager script that other level don't need.
The main idea is that you can easily start any scene and play it: convenient for iterating/debugging. You have only one common prefab in each scene (the Game$$anonymous$$anager) + one for the "settings" + any other specific stuff like 3D art that are uniquely placed or based also on prefabs (like a tree you reuse on multiple scenes). The other advantage is that if you modify any $$anonymous$$anager prefab, the modification is virtually made in all levels (that's the concept behind prefabs). Hope it helps.
To understand those concepts I would suggest to create a few scenes with some simple scripts using Debug.Log messages. You could see that "Object X is instantiated with value Y", "Object Z is destroyed" etc... This helps setting good bases that can then support more complicated scripts once everything works :)
Answer by Zehru · Mar 05, 2016 at 01:01 AM
@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 :)
DDOnLoad is the standard way for the OP's method 3. Load common stuff in the Intro scene. Several Q's here describing it.
(But I haven't seen the new multi-scene edit. It may or may not be a new way to do this.)
Thanks, @Zehru Yes, I already use this method, just for other purposes. $$anonymous$$y question is more about management scenes in Editor, not just how to preserve objects between scenes. How to make development more convenient and require less guessing (e.g., "I guess this scene will have this object when built"). As I understand, to use Object.DontDestroyOnLoad
I need a very first scene ("Intro" scene mentioned by Owen Reynolds in the comment) with Common Objects in it. But I don't have several sequential scenes that need the Common Objects, so I'm not sure where would I load this Intro scene. Example of a user flow is: $$anonymous$$ain $$anonymous$$enu (no Common Objects) -> Level 1 (has Common Objects) -> $$anonymous$$ain $$anonymous$$enu -> Level 2 (has Common Objects). So I need these common objects to be absent on $$anonymous$$ain $$anonymous$$enu scene, but I need them be present on Level 1 and Level 2. Or is the idea to load Intro scene, when the user opens a level, mark all objects non-destroyable and immediately load real level scene? But I guess here I'll have the same issue with preloader.
Answer by -JohnMore- · Nov 24, 2016 at 12:46 AM
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?:
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! :)
Your answer
Follow this Question
Related Questions
Managing Levels in a 3D Game 0 Answers
Should stages be separated by multiple scenes or code? 3 Answers
Communicate between scenes with prefabs? 3 Answers
How to passing levels automatically in games? 0 Answers
Cannot access LevelChanger to change level from the pan/delay coroutine in CameraPanForIntro 0 Answers