Development Update - Level Streaming Vs. Level Loading
During the porting process of Infliction to consoles, there have been many challenges that have made me reconsider the way I had done things. It's definitely changed the way I will do things moving forward.
The earliest version of Infliction was actually the second project I ever created in UE4. The first being a game jam. Needless to say, I had no idea what I was doing. As such, quite a few decisions I made without even knowing I had made a major decision that I’d one day have to undo or change. In this instance it was to not use level streaming.
Pictured above: 48 hour Game Jam. Team: Myself, James Lucas, Rhiannon Vanderlinden & Kylie Roberts
Many “decisions” I made at this time I didn’t know I had a choice. I did know what level streaming was though and had used it in that aforementioned Game Jam. The difference there was that in that game jam, we were loading parts of the level in and unloading that other parts behind. This was implemented so you could look out the window of the space station and not unmodeled exterior of the area you just left. Infliction is a house and not a large one. I did really think about this decision at all because I grey boxed the house and used that to playtest in without a need to stream anything.
Why did I need to implement Level Streaming after all this I hear you asking yourself? Well, load times of course! The original structure of Infliction on a base model PS4 would take 90 seconds to load. This threw me. My PC loads the levels in 5 seconds. As part of a discussion with the awesome programmer at Blowfish Studios doing the port, we had decided to try level streaming for a couple of reasons. When you load a level in UE4, it blocks everything until it’s loaded. This is a problem when it comes to loading screens because you need to indicate the console hasn’t crashed with some kind of animation. Particularly if the load times are 90 seconds long. This is something that would fail certification so it needed to be rectified. The other reason was to see if we could reduce that load time which felt way too long.
So the task was laid out before us, make the game stream in its levels and stream out un-used levels/assets. The first solution was to make a single empty level called ‘Master’ and stream everything else in to this one level. This broke quite a few things in terms of logic flow because much of the level set up logic assumes the level has been loaded fresh, the player is being loaded, not pre-existing. After fixing up these logic bugs, I implemented the new structure and we got to testing. The results were astonishing. Load times were 82% faster per level. It was an amazing result. What was 90 seconds was now 16 seconds. Sadly, the victory was short lived, we found a new problem with our entire approach. Nav mesh in the later levels with AI were now broken. Ultimately, the navmesh cannot be streamed in with a level and now needed to update itself dynamically. With the AI unable to navigate the level we went back to the drawing board. Another problem here is that I had been using static navmesh. Dynamic mesh would take more performance and slow down the game.
We had a few ideas on how to tackle this problem. The solution we landed on was an empty ‘Master’ level for each level. This master would contain the navmesh among other things. When the game opened a new level, it’d bring up the loading screen, then load the empty master with only a few actors in it. Then it would stream in the level itself while allowing animations on the loading screen while this took place. This eliminated the bugs caused in logic expecting a new level to load because we were loading a new level every time, albeit an empty one. It also bumped up load times from 16 seconds to an acceptable 25-30 seconds. This turned out to be the final solution. And it’s really working well for Infliction.
My main take away from this is that I’ll always stream my levels in future and depending on the game, will likely stream parts of levels or rooms at a time to ensure memory usage is as optimized as it could be.