General Performance Tips
From Unify Community Wiki
Revision as of 20:21, 31 August 2007 by Danielbrauer
The following tips are not meant to be absolutes but rather guidelines for Unity users that want to learn how to make a well performing game.
Official Tips from OverTheEdge
General Tips on Optimization
- Profile first. Don't spend time trying to optimize some obscure code or reducing the size of a texture unless you know it is a bottleneck. Always profile your game first to find out where the bottlenecks are. Apple's Shark is a good tool for profiling OpenGL based applications.
- Profile again. Don't forget to profile the game again after applying your optimizations to see if they had the intended effect. Also this might uncover more bottlenecks.
- Work-flow first -- performance second. Spend time making the creation of your game as smooth as possible. Being able to change and update the game faster will also ease performance tuning later on.
- Test the scene in the Scene View. This will tell you if the performance is being slowed down by the objects in the scene or scripts attached to obejcts in the scene. If the Scene View is slugish, you might want to optimize the geometry or textures, if not, the bottleneck is probably in the scripting or physics.
- Disable individual game objects. While in play mode, try disabling and enabling individual game objects. This can be used to narrow down where slowdowns are coming from.
- When possible, combine the nearby objects into a single object that has a single material. For example, if your level contains a table with dozens of objects on it, it makes sense to combine it in your 3D application (this might require combining textures into one big texture atlas as well). Reducing the number of objects that Unity needs to render can boost the performance dramatically.
- Do not have needless meshes. If you have a character in your game, it should probably be only 1 mesh. If you have a ship, it should probably only be one mesh. There is a significant overhead for each mesh Unity renders due to realities of hardware and drivers.
- One material per mesh. Each material that is rendered is treated like a separate mesh getting rendered.
- The performance gained from using extremely low-poly meshes (like under 500 polygons) is minimal if at all there. The majority of graphics cards have hardware transform and lighting, which means they can process ridiculous amounts of polygons per second. Additionally there is overhead for submitting a mesh to the graphics card to render, so being really thrifty on polygons is probably only making your game look blocky.
- Starting out, make characters with about 1500-2000 triangles. This number can vary wildly, but for starting artists this should provide a good compromise between quality and performance for one level of detail. Note that if your model has quadrilaterals, (quads) Unity will convert each quad into two triangles on import.
- Each pixel light rendered is effectively another rendering pass. Pixel lights can make your game look great but don't go too nuts with them. However, using the Quality Manager to adjust the number of pixel lights rendered for each quality level is a great way to provide ample performance/quality tradeoffs in your built game.
- Spot lights are more expensive than point lights which are more expensive than directional lights. A good way to light a scene is to first get the effect you want correct. Then look at all the lights you have and see which ones are important and see if you can reduce the lights while keeping the effect similar enough.
- Point and spot lights only affect meshes within their range. If a mesh is out of range of a point or spotlight and the light is set to attenuate, the mesh will not be affected by the light thus saving performance. This way one could theoretically have many small point lights and still have good performance because they only affect a few objects. Remember, though, a mesh will only respond to the eight brightest lights affecting that mesh.
- Keep the size of textures as small as possible while still looking nice. If your graphics card does not have enough memory to keep all the textures in memory at once, they will get placed into regular system memory and uploaded to the card during rendering. This can be okay as there is a lot of bandwidth available on newer computers; however, if you go too nuts your game will completely choke on computers with low amounts of graphics memory. There is no need to edit the size of textures in your image editor. You can edit the size that Unity imports the image on in each image's Settings.
- Don't use low quality image files. Trying to use jpeg files with low quality or low-color png or gif files will not result in a smaller player size. Unity compresses all textures when publishing the game automatically, so keep the source texture files as original high-resolution files. This will minimize quality degradation due to multiple compression and decompression.
- Use .ogg for compressed audio. Any other audio format will be stored as uncompressed PCM audio in the published player.
- Use uncompressed audio for small sound effects. Unity (as of 1.6) decompresses all ogg files on the fly. It pays to have short sound effects played often stored as uncompressed wav or aiff files in order not to spend CPU cycles on uncompressing the audio. Examples of where this matters are rapid gunshots, footsteps and similar.
- Each rigidbody takes computation, so less are of course ideal. Rigidbodies also have the ability to sleep when their angular and translational velocity drop below a certain threshold. When this happens, the amount of computation they require drops significantly and remains low until they have a force manually applied to them or a collider touches their collider if it exists.
- Complicated collisions take more computation than simpler ones. A large pile of rigidbodies with sphere colliders should be more complicated to process than if those spheres were laying far from each other on a terrain.
- More complicated looking shaders probably cost performance compared to simpler ones. The VertexLit Diffuse shader should be the fastest shader that still takes a texture and responds to lighting. However, if there are no pixel lights in the scene or if all pixel lights are off in the Quality Manager, most shaders will fall back to a more simple vertex version.
- Are you using the right algorithm? Selecting the right algorithm for a task yields much better optimization than any other code tweaking you might do. Note that the best algorithm is not always the one with the lowest average complexity. For small datasets, it is often better to use a slow algorithm with a small setup cost than a smart one with high initialization cost. (Eg. one would use hashtables or binary search trees for large list of data accessed by name, but a simple list and linear search if you are only accessing a few elements. -- although .Net's HashTable class already chooses the most optimal method depending on your data in this case.)
- Keep your FixedUpdate functions as lean as possible. These functions can get called around 50-100 times a second per applicable script per object, so they are a good target to optimize. If there are things that need to be only done when the display is updated, put that code inside the Update function.
- If possible, disable scripts on objects when they are not needed. If you have a large level in your game and there is an enemy kilometers away, you can probably switch off its AI script until the camera gets closer. A good way to switch on and off objects is by using gameObject.SetActiveRecursively(false) and sphere or box colliders set as triggers.
- Beware the empty Update functions. When creating new scripts with the Assets menu, they include an empty Update() function. Remove it if you don't need it as it comes at a (rather light) performance cost. This performance cost applies to all overridable functions in MonoBehaviour scripts, with Update and FixedUpdate being the main targets.
- Refer to a GameObject by the most logical component. One could theoretically write: someGameObject.transform.gameObject.rigidbody.transform.gameObject.rigidbody.transform, but there is a whole lot of needless work done there. If you want to deal with an object's Transform, refer to it in your script as such in the first place.
- Coroutines are your friends. Coroutines have only a tiny overhead and can be preferrable to an Update method that is called all the time needlessly. For example, if you had a script to fade in and out a light on command, you could do the fading in a coroutine instead of in Update. That way, most of the time when the light is not fading, the script takes a minimum amount of performance. If the fading was done in the Update function, you would be inefficiently polling to see if there is fading to be done.
- Don't use methods which search for objects any more than is necessary. This includes methods such as GameObject.FindByTag() and GameObject.GetComponent(), as well as all the component convenience properties (transform, light, etc.). These methods are optimised to operate as quickly as possible, but they still have to search through all the relevant objects to find the one you want. The most important thing is to avoid calling search methods repeatedly in Update() or FixedUpdate(). Instead, call the method once, store its result in a member variable of your class, and then use the member variable to access it the next time you need it.
- Don't use SendMessage() (or similar functions) if you don't have to. SendMessage() is at least 100 times slower than calling a function directly, and this number increases as more scripts and functions are available on the object. If you can find the script you need ahead of time, do so and then call the function directly.