One of the big things that is no secret is that the Wave 4 demo hasn’t been doing so well regarding the overall performance. A big reason with that was that there was simply too much going on with physics calculations and the already relatively large map itself. So what were our options to try and improve the performance? There were a few ideas, of which some are still not fully implemented, but one of the biggest ones was a dedicated system that should handle the loading of our game scene(s).
One Reason Why Performance Was Bad
Let's start off with a little bit of background information on how Unity loads scenes. We are loading the game scene asynchronously so it doesn’t halt the game while being loaded. On first glance there doesn’t seem to be a problem with that. On second look though you will run into a big problem as soon as your scenes are getting larger. While the loading does indeed happen asynchronously and doesn’t freeze the game, the creation and placement of all objects is not asynchronous. You may know this as the long freeze that happened after creating your character in the Wave 4 demo.
One thing was obvious, we couldn’t keep everything in one scene. Loading times were already huge and will only get worse the more stuff we add. The solution to this is that we had to somehow load the whole scene in bit by bit, giving Unity some space to render frames and prevent it from locking up.
Tile Based Mapping
The approach we used could be compared to the way a map is built in older 2D RPGs. You have a ground layer on which additional layers are added to create the tilemap that makes up the world. We used a similar approach, the only big difference being that we are working in three dimensions instead of two.
For our game we split the terrain into large and small cells, the large ones being 300 meters in width and length, the small ones 100 meters. While the big tiles contain the large structures and things like buildings, that the player will be able to see from a distance, the small tiles contain all the little things that make up the environment, like clutter, rubble and items that you could find lying around.
The image below makes the resulting layout pretty easy to understand. We now got a grid of multiple large and many more small rectangular partial scenes that make up the map if combined.
Now if you look at the image again you can see that there are multiple type of large (coarse) and small (fine) tiles, named Level 0 to 3. This is something we did to reduce overall memory load, so just another thing to squeeze more performance out of the map. While the player will always see the buildings as the true structures they are if standing in front of one there is no need to do the same for structures that are further away or even all the way across the entire map. Therefore the tiles with the lowest detail level (Coarse level 0, colored gray) will only contain basic geometry, since the player is never able to actually get to view any of those tiles up close. Think of the SPP tower. You will most likely be able to see it from far away, considering how large it is. But when you are standing about a kilometer away from it you won’t be able to distinguish between the actual tower with hundreds, maybe even thousands of polygons and a simple hexagonal model matching the shape of the original with less than a hundred polygons. The result: Less load for the engine without reducing the visible quality.
The Problem: Close-Up Quality
When the player moves closer to a tile this will no longer work though, since you can only imitate a structure for so long before one would be able to tell that it is just a low-resolution model. Because of that we are switching the models out as the player comes closer to them. Look at the blue tiles (Coarse level 1) in the image. The player is close enough that the geometry of the buildings would look strange, so we replace the whole tile with a higher resolution of the previous one. Now you can probably guess what’s going to happen if the player moves even closer. The mid-resolution tile is swapped out for the real map tile, which contains all the real buildings and geometry. No turned down graphics in this one anymore.
The same thing is done for the smaller tiles, though generally on a smaller radius. The major difference here is that the fine level 2 tiles are not replaced by the level 3 ones, but the level 3 tiles are loaded additively, meaning they only add more detail instead of replacing the tile outright.
Even if this whole thing may sounds as if there were no big problems we had quite some trouble with the actual loading of the tiles mentioned above. In the end we were running into the same problem as before: Unity would stutter or even sometimes freeze for about a second when swapping out tiles, since it had to load a new scene. The problem was that there were too many objects in the tiles, which caused Unity’s load process to take some time. Thankfully this was fixed rather easily by splitting the scenes up even further, creating a maximum object limit for the tiles,resulting in not one tile scene with 1500 objects but three scenes with 500 objects each. The load of these much smaller scenes can then be distributed over multiple frames, hopefully keeping Unity from locking up.
In short, optimization and support for large areas without compromising performance is always a win.
That’s it for this time. This post was one of the more technical ones, but there has been silence for quite some time and since this system is pretty new it is something that could be considered notable. Currently we are tweaking the last bits of it and hope to have it ready soon. See you in the next post, and have a nice day!