Posted February 27, 2021 by Woody's Games
#Games #Game Development #Game Design #Programming #Optimization #Unity
After having the worldview, game concept and design blueprints, I was able to start developing this game. This game should take priority over mobile devices, so mobile optimization was considered beforehand. In this Unity project, I used all sorts of optimization techniques to reduce the performance overhead at anywhere as much as possible.
Generally, performance optimization depends on CPU, memory and storage. There are trade-off and balance among them.
Calculating distance between two objects is necessary most of the time, but personally it is a bit expensive for mobile devices due to the calculation of square root. In some situations it is not required to calculate the exact distance. Instead, a rough distance is enough, so I remain the square. What is more, even if it needs to calculate the accurate distance, but no need to calculate it at each frame, then it will be called once per 5 frames or longer interval.
The environment of this game is very special compared to other games. There is no vegetation, mountains or other objects like rocks, just a large mirror-like water volume, and several mirrors act as portals. The water with reflection, refraction effects dominates the performance overhead. The calculation of reflection is unavoidable, because this is the feature that makes this place so unique. However, refraction can be reduced as much as possible without affecting graphics quality. Unlike a lake, the water here is so shallow that almost no objects underneath it need to be distorted, except for the ground. The water is textured with normal maps already, so an extremely cheap material with a monochrome texture (32 * 32 pixels) is sufficient enough to be applied to ground.
Other significant performance overhead is produced by the particle system. There are lots of abstract objects in this game, so particles are heavily used. My strategy was to limit the maximum amount of particles(less than 30) emitted from each particle system. The size of particle images is well controlled, most of the images are 128* 128 pixels. Plus, if the images are not transparent, I remove the alpha channel. Many of the particle materials use the default Unity additive shader, one of the features of this shader is that areas in an image which are completely black will not be rendered at runtime, which leads to avoidance of using transparent images and satisfying reduce of image size. One of the advantages of using abstract objects primarily in this game is that compared to 2D painting and 3D modelling, it takes less time on game art, but fancy graphics, peaceful and mystery atmosphere are already created. Etherealness is one of the features in this game.
The calculation of the main scene is more intensive than the game level scenes. There are seven mirrors with particle systems attached, and theoretically there should be 7 cameras capturing images and rendering them on the corresponding mirror panel at real-time. A wise optimization technique was used for camera capturing. Instead of using 7 cameras, I used only 2 cameras, one for the mirror that reflecting the character, the other one for all the remaining mirrors, which means only 1 mirror is allowed to reflect the character at at anytime. The average draw calls would be 210 if 7 separate cameras are used, but only 160 draw calls are produced if applying my technique. With this technique, almost 25% of the total performance is saved. I also limited the distance of clipping plane of cameras to reduce the rendering load. All mirrors are low-poly 3D models using materials with efficient mobile shaders.
Compared to other parts, lighting is easy to be optimized in this game. Recall that the scene doesn’t have trees, rocks or many other polygons, so shadow reasonably becomes less important. Except for the character, there are not so many objects that cast shadow. And the seven portal mirrors even don’t need to cast shadow at all. Although they look like physical, players may feel like they are abstract teleport termination after applying particle effect. Those mirrors are movable objects, but baked lighting was still used. The reason is simple: Players will not pay attention to the detail of the surface of mirrors, so calculating specular is unnecessary. Similar to the ground, material with a very low resolution map is applied on each mirror. Real-time shadow is turned off. The shadow of the character was implemented by using shadow projector to simulate the shadow effect by rendering a simple round shape on the ground.
A few post-processing effects were used in the game, which makes the sky bluer and the environment more refreshing. In Mirror of Freedom, motion blur effect is used during gliding.
To reduce the app launch time, I followed a basic and useful principle: Load the minimum resources in the main scene.
It still looks fancy after applying all the rendering optimization.
Moving to programming, one of the optimization disciplines is simple and straightforward: Only update the states or data when it is necessary. This discipline is very easy to follow, I used a game state attribute as a gateway to control the operations. For example, when the game state is at game level 1, only operations related to level 1 are executed, other game logic from level 2 to level 30 will be paused.
Object pooling technique was used to avoid creating and destroying game objects frequently at real-time. Without this technique, a lot of overhead will be produced.
Another powerful optimization technique is the extremely high reusability of game objects. With this technique, it brings an incredible benefit: decreasing CPU usage, saving memory and reducing application size at the same time. I mentioned that there are trade-off and balance among CPU, memory and storage, which means that if you want to have better graphics, you may have to increase CPU usage and memory usage, or if you want to decrease the CPU calculation intensity, you may need to use more memory(cache some attributes in memory) and storage(baked lighting data etc.). Recall that there is no terrain or vegetation in this game, so I don’t need to save the entire game level as a scene, and load it when it is going to be played. The objects I used for making game levels are just energy, fences, glowing platforms, cubes and other basic objects. Unlike many other games, I just need to store the raw data(position, rotation and scale) of each needed object, then it ends up with storing at most 9 floating numbers per object, which can save massive storage. Naturally, CPU usage and memory are saved significantly as well. No need to load a scene into memory, and each necessary object will be transformed into target size and moved to the right place, therefore, the calculation is very simple, fast and cheap.
The further paths will not show until the character is getting close enough, and at this moment, the previous path will disappear. Objects used to build the previous path will be used to build the further path. Also with this approach, it limits the accessible area and gives a hint that which way the players should go.
For the localization, there are two left-to-right-written languages: Arabic and Persian. I used Arabic Letters Support For Unity plugin(https://github.com/Konash/arabic-support-unity) to handle it. For these two languages, not just the orientation needs to be changed, the alignment should be right instead of left.
With all these optimization techniques, this game has satisfying achievements: 1. Not very CPU intensive but has a relaxing and immersive 3D environment 2. A very wide range of compatible versions 3. Incredibly small app size but has a lot of game content
Sky Mirror · Strolling, a 3D mobile game with 70 game levels, at least 2 hours and 30 minutes of game length, app size of 64 MB on Android(Android 4.4 or higher) and 77 MB on iOS(iOS 9 or higher), runs smoothly on a 6 years old phone.
I believe the next game I make will be closer and closer to the masterpiece level.