The Co-Founder of The Sixth Hammer Dimitar Popov has returned to tell us about designing a big seamless 2D world without loading screens in Unity.
I am Dimitar from The Sixth Hammer – a small independent Bulgarian game studio, currently focused on 2D game development using the Unity3D engine. Our largest game – Moo Lander, required us to find creative solutions to a lot of difficult problems, typically arising in more complex games. We wanted Moo Lander not to include loading screens and instead be one big open 2D world. So we needed to find a solution to the problem of performantly streaming content during gameplay.
In Unity we have the concept of Scenes, so we just needed to find a way to load those in the background while the gamers play the game. Simple enough, right? Wrong!
These are some of the questions I will try to answer in this article. Come on, let’s dive deep into the topic!
You can imagine the Scenes in Unity as “bundles” of Game objects. Unity offers two types of loading a scene – Additively and Non-Additively. Non-additive loading replaces the current scenes with the loaded one, while additive loading keeps the already loaded scenes – in our case we needed additive loading.
Unity also offers two ways of loading each of the types – Synchronously (that basically pauses everything else until the loading is done) and Asynchronously (in the background). For our game – Additive Asynchronous loading was the type we used.
We’ve had to build a whole system of game objects and helper classes for the scene loading in order to tackle the aforementioned issues that arose during development.
Scene Switchers
We thought long on what is the best solution for loading/unloading a scene at the right moment (when the action is minimal, when the scene itself is not visible and when the scene is not needed anymore anywhere else). In the end, we came up with the idea of a Scene Switcher.
Each Scene Switcher holds as children two “Gate” Game Objects. Each of those has a 2D physical trigger collider (that is usually rectangular, but may be shaped in any way if needed to fit a scene layout). The Scene Switchers also have a field for the Scene Name, that it needs to load/unload. It then keeps track of the interaction with its Gates. An interaction with a player in the way of 1 => 2 should load the scene, while interaction of 2 => 1 should unload the scene.
The white rectangles with tiny “1” and “2” texts on them are Scene Switchers:
Scene Switchers may know a player’s intention to load or unload a scene. But we’ve separated the actual process of loading a scene to another singleton system class, that keeps track of all of the player’s intentions as well as which scenes are already loaded.
It does that by doing a few things:
Of course, the actual implementation includes a dozen of helper functions and other variables, but this is the main idea. With that architecture, we are solving the first two issues (mentioned at the beginning of the article) gracefully.
On the other hand, the third issue about the performance deserves a separate page of its own. So let’s see what were the issues and challenges there and how we solved them.
The way Unity loads a scene in the background works pretty well while the scene is loading. But then on the last frame Unity creates and activates all of its game objects (which is normal behavior, because the engine cannot know the dependencies between them) which causes a pretty substantial lag.
The severity of the lag mostly depends on two things:
So based on that, we have a couple of different approaches to improve the performance:
Delayed Activation of Game Objects
In order to do that, we first need to prevent Unity from doing this automatically on scene load. Unfortunately, we did not have a built-in way to do that, so we just went ahead and:
We then extended the scene system that was responsible for the loading/unloading of our scenes with the following behavior for after a scene is loaded:
With that, we improve the quality of the player experience by removing the nasty performance spikes when additively and asynchronously loading scenes with Unity.
Bonus tip – unloading a scene does not actually unload all of the used textures and other assets that the scene uses. This is why you should also call the built-in UnloadUnusedAssets method from time to time during gameplay in moments where there is not much action. That causes a small spike in performance. But We still have not gotten to the point, where we would need to optimize that Unity behavior.
Also, check out that link for other insights on improving the async loading of scenes.
As you see, there is a lot happening in order to ensure the smooth loading of everything the players need throughout a game. And after we finish with Moo Lander, we will package all of that into a plugin that would save a lot of time for future fellow developers.
Of course, there is much more happening to our game scenes and objects (like persistent objects that can move between scenes, etc..), but I will share insights on that with you in future articles. For now, I hope this will give you enough of a direction to build your own great solutions to the additive scene loading problem.