15.3.2019 - HP Noronen
It’s time to introduce the Friday Rewind posts.
Friday Rewind is a weekly post about some of the progress we have made lately. Our aim is to keep on publishing Friday Rewind on each Friday. In addition to Friday Rewinds we are also planning to publish some more specific posts on individual topics.
Last week (like the previous couple of weeks) we’ve focused strongly on performance optimization. The current aim has been to reach a level of performance that is very close to what we should have on the release version as well. On a higher level, there were a few major challenges and then just a pile of more general optimization that were done.
First of all, there was clearly a need for overall performance improvement that would help the game to run on lower-end equipment as well. Then again, even with high-end computers there was some performance spiking that made the gameplay experience far from optimal. We focused on both of these things and the performance is now reasonable on mid-level equipment, and most of the spiking has been now dealt with.
A few of the most important fixes included:
Performance hiccups with the vegetation system
One of the things that we noticed were strange slowdowns when turning the camera. This was tracked to Vegetation Studio, which is a plugin that provides us more control and improved performance on vegetation compared to Unity’s default implementation. However, it turned out there’s a silly feature that was creating new colliders on runtime and setting them up, even though in our case we had already built those before. It was a bit hard to find the issue, but disabling unnecessary generation caused a dramatic improvement on performance spikes.
Shader loading spikes
In Unity, the default behavior is to not prewarm shaders before you need them. This is mostly to ensure that we don’t push tons of shader variants into memory, as there’s most likely a pile of them that we don’t need. However, when a shader is loaded for use for the first time, it’ll have a performance hit. In many cases this was actually quite a big hit, so big that it was one of the main sources for performance spikes. Unity supports a thing called shader variant collections, which would allow you to warmup specific set of the shaders early. However, its collection functionality didn’t seem able to collect all the variants that we were using, and it seemed very hard to track which ones were still loaded on runtime. Due to that, we ended up having to warmup all of the included shaders early on. It is not the most effective way to use GPU memory, but it helped a lot with performance for now. This is something we might still revisit during development.
Instantiating new objects on runtime can be heavy for performance and cause spikes. For that, we have had a pooling system in place for quite a while. To populate the pool early on, we have used a manual configuration for some common items and also a possibility for scripts to dynamically register something into the pool at the start of a level. However, in case of manual configurations, there was considerable variation in the amount needed between levels, and in some cases we ran out of items in the pool. This was especially true in, for example, the first level, with tons of fire and explosions. We improved our pooling system to be able to record which pools we had to extend when playing through the level and create level specific configurations automatically based on that. That is now in use in some of the levels, and is clearly helping with some of the spikes.
Memory management improvements
When working with Unity, memory management is, in large part, about taking care of garbage. As C# has a garbage collector that takes care of objects from which memory is to be released, it means that reserving and releasing such memory can have a high cost, showing up as performance spikes caused by the garbage collector. This part has been a bit painful for us, as we need to constantly keep recording and freeing snapshots of the game state for rewinding mechanics. However, we were able to tackle a massive number of those issues. I’m not going to delve too in-depth into what we did here, but maybe I will tell more about it in a special blog post later on.
UI Improvement: Quick examination box
In the current phase of the development, we are very actively looking for improvements on usability and user interface. One of the things that we have been looking at, is to include more detailed information about enemies, characters and other damageable entities in a way that it would not clutter the game screen.
For this purpose, we went through how pile of the other great games have dealt with it and decided that we should aim for something similar to how Divinity Original Sin 2 does it.
On the top of the screen you can see our new quick examination, giving summarized information of the entity under your mouse cursor. The quick examination box includes the main information that you’d want to see during the combat, such as health and active status effects.
On the screenshot, you can also still see the old indicators for the health and enemy name. You can easily guess what kind of mess it would have been if we would have tried to squeeze status effect information there as well!
In near future we are going to work on reducing some of that information as well. At least the overhead health bar has heard it’s death sentence said aloud already.
We now are in a happy place where it is also possible to plug in some of the features we have had planned for ever but never actually finished.
Depth of field and foreground vignetting finally work. The focus stays locked to the active camera target and the black foreground vignette moves to an optimal position based on camera distance from the target. The screenshot is our first taste of the focusing in use. After seeing that we also added distance based aperture control!
Combat stances. Characters who are in combat now look the part. They crouch down a bit and move in a more balanced, ready to strike way. This helps differentiating between characters that are hostile and ones that are not without having to resort to UI indicators.