There is no quick fix but constant experimentation and measurement will guarantee results!
As per Unity : ” It is vital to understand that there is no one size fits all approach to improving rendering performance. Rendering performance is affected by many factors within our game and is also highly dependent on the hardware and operating system that our game runs on. The most important thing to remember is that we solve performance problems by investigating, experimenting and rigorously profiling the results of our experiments.”
I decided to document a few of the techniques that seems to help my Unity project’s performance.
First, I use ubuntu linux + Unity Game Engine (Personal Edition) V5.6.0f1 for my development machine. (My project version for this article was V1.11.27b)
Note: Beta 10 of Unity has a few quirks of course, one major pain is that the Editor crashes when you use Maximize on Play, so I had to ‘Play in a Window’ or make a build as a workaround. Always save the Scene and Project before testing and make a full folder backup after each build. I typically do this weekly.
Note: I don’t have a special GPU on my HP Probook 4540s, so I have to work ‘extra hard’ to get a decent frame rate. As of this writing I am at least able to achieve a 45 FPS average and with burst of 60 FPS possible on this machine. The machine does have 16 Gb of ram. I am also discovering that frame rate is not everything…meaning if the flow of the game is smooth, even a slightly lower frame rate can be acceptable. I am constantly learning/discovering new tweaks for optimization so any suggestions comments are welcome from your own experiences on real projects. Theory is great and all, but I am more interested in what actually works.
The game I am working on is called ‘Escape from Pirate Island’. The latest version (in pre-Alpha) can be found here. https://mafuta.itch.io/escape-from-pirate-island
When playing in ‘fastest mode’ on this machine with 1280 x 720 resolution for example (from Game Setup Config), one can easily achieve up to 60+ FPS. Close all other applications when playing the game as well and experiment with best modes for your rig.
So, here is a summary of my Game Tweaks in case it might help someone else:
- Level Design
- Assets – For the most part I used Unity Store Assets and adjusted LOD levels when appropriate/available.
- Shadows are nice…but they come at a cost if you are not careful, so make sure you go through the scene level and do a per object evaluation (trial & error) for which objects should cast/receive shadows. In general, small unimportant objects may not have to cast a shadow nor receive one. The best is to test and check in editor how it looks/feels.
- Shaders can make/break a game and I am no expert here but I did notice that in general Pixel Shaders ‘cost’ more in performance but provide better looking results usually. Conversely, Vertex Shaders look a little less good but can be faster and perform better. Diffuse might be ok in certain cases but not others. Experiment, profile (in builds) and make a judgment.
- Texture Resolution begins in the 3d design software, but if you are like me (not an artist) and have to rely on the work of others for your models, at least you can do a per object evaluation by experimenting with texture compression For PC games I found 2048 are ok for important objects the player will see up close but you can and should be able to drop down to 1024 for smaller (less important) objects like a small stone, mug, bug etc. Saving texture memory is a vital part of optimization but again profile first and if you are not memory constrained and performance is good, don’t sweat it too much. Alternatively there is a kind of ‘bulk’ option under quality settings where one can set the texture quality to say ‘HalfRes’ and so on. This must be used with care because it will literally halve the texture quality of whatever was set on that particular object ie from 1024 –> 512 and 512 –>256 for example. This does save you the trouble of doing it per object but may make ‘important’ objects look ugly if they were already using the optimum setting.
- Quality Settings for each mode can also be tweaked for a balance between performance and look. For example if Hard Shadows looks almost as good as Hard & Soft Shadows or Soft Shadows, but gains decent performance, then why not use it?
- Test and experiment carefully with other settings under quality like anti aliasing (which is supposed to soften ‘jagged’ edges) and set to 4x or even 2x to see if looks vs performance equation is still works ok for your game. The hardest thing to accept is that there are no instant magic answers and no two games are the same. Don’t blindly ‘optimize’ without having a decent profile ‘before’ and ‘after’. I learnt the hard way that not every trick works for every situation…in fact it could make it worse, so be patient. I am falling in the hole every so often but I am determined to learn what works for each situation.
- Occlusion Culling can make a huge difference in performance but be careful as it can also make things worse if not setup correctly for your game world. I found that a completely ’empty’ world does not necessarily benefit from this and conversely a world with objects hiding view of others can in fact work great.
- Static and Dynamic Batching should be experimented with as well but don’t instantiate static objects at runtime as it can kill performance.
- Sound – Most Sound files are in .WAV format
- I have only one main theme song on an endless loop.
- The best method is to experiment, profile and repeat until sounds is a non-performance issue.
- Colliders -Most Objects use simple colliders instead of mesh colliders
- This means box colliders & or sphere colliders for most of them
- Make sure you check the right object ie myPlayer is the thing that collides when it should and consider removing colliders completely for objects not requiring them.
- Scripts – All scripts are in C# and I try to minimize complexity
- This means a lot of simple script logic with specific purpose and only the required logic to accomplish the task at hand ie pickup a food item or something to drink. The downside is code repetition to some extent but the upside is that it seems to run better and is easier to debug a specific task in a short script.
- I try to avoid doing ‘Get Component’ logic during the Update() loop and instead setup the reference in Awake() or Start() methods
- I try to avoid doing ‘for loops’ as they can be inefficient.
- I minimize Find Logic
- I rely on ‘invisible’ box type triggers a lot.
- Animations are kept as simple as possible during Alpha-phase
- This means many animals share the same idle, walk, run animations too
(Often I use legacy generic animations without an animation controller for better performance, but this needs to be tested and considered carefully for each case)
- NPC’s also often have similar AI script logic, again as simple as possible.
- I avoid instantiation as much as possible and when used, it happens at scene start, or periods of low activity, for the most part.
- This means many animals share the same idle, walk, run animations too
- I minimize lights. In fact the whole scene has fewer than 10 lights and only one directional light (Sun). See screen shots at end.
- When you do use lights look at the unity notes on what ‘cost’ more and tweak settings carefully and never under/over light the scene at the expense of looking to bad vs running super fast.
- I found that often, lights work ok as point lights with no shadows ie just a general area volume light. Try to add a halo for a smoky/foggy effect for example.
- Water – I removed Unity Water and replaced with AQUAS and carefully tweaked the settings for best performance. This means I disabled ‘Caustics’ which does not work very well anyway in my game.
- Terrain – I used Gaia 1.5.3 for the terrain which is 2048 x 2048 but with windzone and unity water removed. I did keep wind for grass however. I set the parameters very conservative as per Adam Goodrich’s (developer of Gaia) excellent optimization tips here: http://www.procedural-worlds.com/gaia/tutorials/mobile-unity-scene-optimisation-with-gaia/
I removed all empty Start() and Update() methods from all scripts. When importing a new asset, test it in ‘Lab’ project first, make a custom package with prefabs and components you actually will use in the main project. This helps to keep backups down in file size. Builds don’t matter as Unity will only include assets actually used in project.
- I build only for PC/MAC/LINUX platforms.
- I usually build with the LINEAR color space (as opposed to Gamma) as the blending of colors is better (scientifically more accurate) for my case but I suggest you test both linear and gamma. I also tweaked the quality settings for each mode to be a little less ‘good’ than standard.
- During the build, player settings, I chose ‘Pre-Bake’ Collision meshes and combined that with a Full (Manual) Light Re-baking followed by a Full (new) Occlusion Culling run (I clear the old data before every major Build).
- All static objects are marked ‘static’ via the Inspector as appropriate
- I implemented a custom Zone Manager Script that essentially cuts the world in 5 chunks, with a large Box Trigger (invisible walls) setup between them. As the player leaves zone1, hits the ‘trigger wall’ I disable all objects (except terrain) behind him, while enabling zone2 objects for example. This requires the use of a 5th zone where certain objects (required to be always active) are organized. I avoid instantiate and destroy as much as possible and use a hide/show strategy controlled by the Zone Manager logic instead.
- I hope others will add to this list and provide comments on these tips.
Below are some screen shots from the Unity Editor for my current setup
This setup lets me achieve 60-70 FPS at the moment.
I set the ‘Good’ Mode as my default as per above settings but found that FAST or FASTEST mode with 1280 x 720 resolution gets me to 60-70 FPS!
Player Settings for Windows Builds. Note that I removed Direct3D9 (DX9) support for older pc’s (built before 2007 or so). Also note I use Linear Color space. In this example, the Prebake Collison Meshes is disabled but experiment with it as the main benefit is that one can often get faster load times, higher fps but at the cost of (sometimes but not always) a slightly larger executable file.
GFX.Waitforpresent issue: Essentially ( on my machine without a GPU) I ran into this issue but after enabling ‘Prebake Collision Meshes’, the CPU was no longer too busy. My understanding is that the CPU was constrained trying to draw/render the world (without the relief of handing off to the GPU -remember I don’t have one!-) but with prebake meshes it seems to be much better. I would like to hear what others think about this?
I also setup my Tiers as follows: Note the changes to Realtime Global Illumination CPU usage set to LOW for Tier1 and Tier2 and using Forward Rendering Path.
Terrain Settings BEFORE tweaking!
Drop down the Detail Distance, Detail Density and Tree Density and start Billboard as soon as is acceptable for your situation. Try to find the balance between looks and performance.
When using the profiler, do NOT use it in Editor (not reliable/accurate). Always run profile against a build for better results and disable all other programs.
REFERENCES & related Links by others:
By Adam Goodrich from Gaia
Unity Game Engine documentation
By the Sniper Fan (Great Tips)