Hello, everyone! In this video,
we are going to take a look on how to improve
our workflow when working with multiple Scenes.
First, I will show you how to create a Loading Screen
to go, for instance, from the Main Menu to the Level.
Then, we are going to see how to avoid
loading the whole level at once,
and rather split it into different Scenes,
so we can load our level faster.
As a reminder, I'm using the Unity version 2019.3,
but this should be working the same way on previous
or more recent versions of Unity.
Today, I will be using the FPS Creator Kit
that you can find on Unity Learn website
as a base for this demo.
Now, let's have a look into our Scene Main Menu.
Here, using the Assets, I just created a simple Scene.
If you go to the Hierarchy, you will see that I have added
this Main Menu Prefab that I have created.
Here we have a Play button along with an Exit button,
because at some point, you would love to Exit the Game.
As you can see, the Play button is calling a function
from the Main Menu Script that we will see in a moment.
And we have the Exit function called here from the same script.
Now, let's start by creating a simple loading bar.
Right-click on the empty Loading Interface Prefab,
and select UI > Image.
Let's call this "ProgressBarBackground".
And now, we are going to duplicate this
and rename it to "ProgressBar" for the action loading bar.
I will change its color, let's say, for instance, green.
And now, we want to see the bar getting filled.
I will go to my Assets folder,
right-click and choose Create > Sprite > Square.
Now that I have it, I can go, drag and drop it.
You can also click here and select it as a Source Image.
By doing this, I now have new options that appear,
including the possibility to choose the Image Type.
Let's select Filled.
Now, let's make it horizontal and from left to right.
As you can see, now I can choose the amount value
and see the progress effect.
We can adjust the filled amount later in the code.
Now, let's go to the Main Menu Script that I have mentioned.
First, since we will be using
the ScreenManagement Scripting API from UnityEngine,
we need to make sure to add that at the top of our Script.
Also, we will be using the Image Type,
so UnityEngine.UI should also be included.
Here I have a reference to the menu GameObject that I have created:
the loadingInterface and the loadingProgressBar in the Editor.
The StartGame() function that we mentioned being called
from the StartGame() button, is calling three Methods.
When we want to start the game,
first thing we need to do is to hide the Menu
and then show the LoadingScreen bar that we have created.
Here, I'm adding the two Scenes that I want to load
from the Main Menu to the list of AsyncOperations I have created,
which is called scenesToLoad.
As you can see, I'm using LoadSceneAsync() from SceneManager,
which allows me to load the Scene asynchronously in the background.
The other parameter which I'm using is the LoadSceneMode,
which is set to Additive, as we want the Scene to be loaded
along with the Game Play one.
Then, I start the coroutine LoadingScreen().
In here, a have a float of the totalProgress
of all the Scenes loading.
I start the For loop to iterate through all those Scenes,
using the AsyncOperation variable,
I will be able to access the progress value
of the level loading process.
And here, we are saying, "As long as the Scene is not loaded yet,
I add its loading progress to the totalProgress value.
After that, I need to divide the value by the number of the Scenes
to load in order to get the value between 0 and 1.
Let's go back to the Editor.
If we click on Start Game on the Menu,
we can see the progress on the bar
while waiting for the Level to load.
Now, especially when working with big Scenes,
having everything loaded at once, even it's not needed,
would take so much time and make the game less performant.
That's why an efficient way of loading the Scene
would be to not load everything at once,
but load only the parts that are needed,
depending on the Player position in the Level or the map.
So what I did here, I put all the GamePlay elements
in one Scene that I called GamePlay.
And I split the whole Level into different parts,
and put them in separate Scenes.
That allows me to only load the ones I need.
Now, to know which one should be loaded, there are a lot of ways to do it.
Today I'm going to show you two of them,
and each has its pros and cons.
First, we are going to check using Distance,
and then we are going to do it using Trigger.
Before diving into scripting that, I will show you
a visual presentation of what we will be doing.
Here I have the different Scenes of the Level.
Some are loaded, and others are unloaded.
The Anchors are the positions we will use to calculate
the distance between the Player and different parts of the Map
that are separated in different Scenes.
Now we can see the Player and the range
which we can adjust its value if needed.
As the Player moves, we load and unload the Scenes
depending on the Distance Check.
Let's go back to the Editor. In the Gameplay Scene,
as you can see in the Hierarchy, I have the ScenePartsLoader section.
This part includes Prefabs for each different part of the Level.
For instance, Level 1, Part 1 is the Prefab that will take care
of loading Scene Level 1, Part 1,
which is the first part of the Level.
In this example, there are five parts,
but, of course, it depends on your level and your game.
If you click on the Prefab, its position represents
the Anchor Point for that part of the Level,
as I have shown in the visual presentation previously.
You can see that it also contains a Box Collider set to its Trigger.
I only explain the method using distance.
The Trigger is another way of loading the Level.
Instead of calculating the distance to the player,
we check if the Player is in the Trigger
to load that part of the Level.
Added to this, we have a Script attached to the GameObject
called ScenePartLoader.
Here we can choose the Check Method, either Trigger or Distance,
and I can choose the Range that will be used,
if I choose the Distance Check Method.
To better see those positions and Triggers in the context
of the whole level, let me drag and drop the different parts
of the Scene that I have here in the Level folder.
As you can see, for instance, the Collider we see
on the Level 1, Part 1, represents the area
where the Player will need to be in order for Part 1 of the Level to load.
Same for the other different parts.
Now, let me remove the Scenes.
Let's dive in into the code and see how it is handled.
Before that, make sure that the name of the different Scenes
are exactly the same as the names of the Prefab's Loaders.
This is important, as we will use their method to load our Scenes.
In this Script, I created an enum, CheckMethod,
to be able to easily choose which method to use.
So I declare the CheckMethod variable here as public,
to be able to see it from the Inspector,
and I choose the method from there.
I also have a reference to the Player and the float loadRange.
We will use them both for the Distance Check Method.
Then I have these two Scene states Bool variables:
isLoaded will let us avoid loading the Scene twice,
and shouldLoad is used for the Trigger() method.
Now, let's see the Update() function.
In here, we are calling the different Check functions,
depending on the chosen CheckMethod.
Let's start to look into how the Distance one works.
If we go to the DistanceCheck() function,
we are checking if the distance between the Player
and the position of the GameObject, to which we are attaching this Script.
If it's less than the loadRange, which can be adjusted
from the Inspector as it is a public variable,
then we call a LoadScene() function or UnLoadScene() function,
if we are not within the range.
The first thing that the LoadScene() function does
is to check whether the Scene is already loaded or not.
In case it's loaded, we, obviously, don't want to load it twice.
And if it's not loaded yet, then we call the LoadSceneAsync() method,
which will allow us to load the Scene asynchronously in the background,
exactly like we did in the Main Menu previously.
I'm passing the gameObject.name as parameter,
that's why I insisted that it's important to make sure
the Prefab name is exactly the same on the Scene name.
We are also adding another parameter
which is the LoadSceneMode, like in the Main Menu
the Additive mode adds the Scene to the currently loaded Scenes.
And this is exactly what we need in this situation,
as we don't want to unload already loaded Scenes like the Game Play 1.
Then, I set isLoaded = true,
so we don't load the Scene twice.
In case we are not within the range, the UnLoadScene() is called instead,
and here we check if the Scene is loaded,
then we unload it and set its isLoaded variable to false.
Now, that's all for the DistanceCheck() method; let's go test it.
I select all the ScenePartLoader's Prefabs,
and make sure I have the Distance check method chosen.
I will put a top view on the Scene window,
so we can better see the loading and unloading of the Scenes.
[GURGLING]
[SHOTS]
[BUBBLING]
[SHOTS]
[GURGLING]
Now, let me go back to the first part of the Map.
As you can notice here, we can see this part on the right of the Map,
though we don't really need it because the Player won't see it.
And, in general, if your level is, for instance,
an interior of a house or a maze,
the Distance method won't be the best.
That's why I created the Trigger method which solved this problem.
So let's see how this method works.
Our TriggerCheck() method is seeing if the Scene should be loaded or not.
This bool variable is set using
the OnTriggerEnter(), and OnTriggerExit() method.
If the player entered the Trigger, we set shouldLoad = true
or false when they leave.
And then, depending on that variable,
we load and unload the Scene, using the same functions
we have used with the Distance method.
And now, let's test it.
Now, let's try again, using the Trigger method.
[GURGLING]
[SHOTS]
As you can see, when we start,
we don't see the part of the Map that we don't need.
So for this game, choosing the Trigger option
is a better way of loading Scenes rather than the Distance one.
But this depends on your level and on your game.
And that's why I prefer to show you both methods.
[SHOTS]
If I drag and drop the Level 1, Part 1 Scene, for instance,
let's say, I want to make a few tests,
I press Play, and we can see that the Scene didn't load twice,
and that's because I already took care of that
on the Start() function of the ScenePart Loader Script.
The SceneManager.sceneCount returns the number
of currently loaded Scenes. So if it's greater than 0,
I go through those different Scenes using the loop,
and check if the scene.name is the same of the gameObject.name,
which is, again, the same as the scene.name for that part.
So we set isLoaded = true in this case.
And this will later prevent the Scene from loading twice,
as it's already loaded.
This is the may to manage your workflow when working
with multiple Scenes. That's it.
From now on, you'll be able to reset all your games.
Thank you for watching, and see you soon for another video.