The following is an incomplete, unsorted bullet list of tips I picked up while porting my Unity3D game ‘Rope Racket’ to iOS (iPhone/iPad).
I will try to keep this list updated as needed, so check back soon.
Physics
Unity3D uses the highly optimized PhysX physics engine. Normally all operations involving physics should be taken care of automatically by the engine. Here are some points to consider:
§ Colliders – remove unneeded colliders and combine colliders into one. E.g. an enemy object can typically use one collider.
§ Overlapping colliders can produce as many as 3 solving iterations per each frame so pay special attention when forcing position on an object with a collider.
§ Use simpler colliders – sphere is simpler than cube, which is simpler than mesh collider.
§ Joints chain reaction – a movement on one end of the joint always yields movement on the other end. Using several interconnected joints can produce a chain reaction of consequent movements, and should be used with care.
General Optimization
Code optimization can significantly improve performance. In most cases, inefficient programming is the main blame for unoptimized performance.
§ Some operations do not need to be called with every frame. E.g. button states, check distances, AI decisions etc. Most actions can be called 10 times per sec without anyone noticing.
§ Get functions are a neat concept but can be expensive. Declaring a variable as public is crude, but is faster when external access is needed.
§ transform is also a get function. Cache it in a variable when used repetitively.
§ To avoid cpu spikes call garbage collector at scheduled intervals with System.GC.Collect();
§ Precalculate arithmetic tables where possible, or at least cache values when they don’t change (sin/cos/atan/sqrt).
Graphics
iOS GPU is very different from your PC’s. Some well known caveats:
§ Triangle count – I’ve read a lot about some hard limits like 7k but my experience shows that it doesn’t change that much in comparison to other factors. Try to keep below 10k per scene if possible.
§ Memory optimization – change the target maximum texture size per iOS to the smallest possible size without losing details. I found the 256x256 or less is quite sufficient for most objects. You can also change it per each target platform so it looks detailed on PC, and saves on memory on iOS.
§ Complex and multi-pass shaders are slow on iPhone’s GPU. Toon and cutout shading are examples of shaders that should be avoided if possible.
§ Lighting is very expensive. In general, every light is another drawing pass for all affected objects. Use one bright directional light. Use other lights, especially spot and point, with caution. Note that additional lights have two wrongs – they take an additional draw pass per each lit item, and initial light-on calculation takes a few milliseconds of stalled application.
§ Changing vertex position, normals or tangents is expensive and should not be done repetitively. Changing UV (pixel position within texture) is fast, however.
§ OnGUI is called every frame, plus every event. Furthermore, it does not share materials between subsequent calls to texture or text, even when the same resource is used (e.g. 2 calls for text and its shadow). Avoid if you can.
Draw Calls
A Draw Call is one or more objects, sharing a single material on a single camera, in a single shader pass, being placed on screen.
My experience proves that the most important element to optimize is to reduce the number of draw calls, even in high-end mobile device such as iPad. Apparently the pipeline between the CPU and GPU is not as efficient as in non-mobile devices.
§ Make sure Dynamic Batching is checked.
§ When checked, the engine groups all objects by their shared material, up to a maximum number of vertices per batch. Make sure that member sharedMaterial is used instead of material, whenever applicable.
§ Furthermore, all batched objects must share queue pass. Each camera has multiple drawing passes. If a few materials share the same pass, there is no guarantee that all objects using the same material will be batched. Therefore you can help Unity by placing a material in a particular pass by changing its pass order.
For example, shader directive Tags {“Queue” = “Transparent-1” }
will make sure that this shader is drawn right after Transparent pass. So setting Transparent-1 separates this new material from normal Transparent shaded materials, and batches all objects using this material into one pass.
Note: This trick also helps when trying to sort depths and fix Z issues.
§ Most importantly: Use one material (one texture) for as many objects as you can. For example, use an icon atlas and change UV position according to the desired image.
§ Alternative solution: load a font and export its texture. Place all your images on the exported font texture instead of the original letters. Then, use Text Mesh and change the letters to display the proper icon.
More Tips
Here are some handy non-optimization tips:
§ Instead of OnGUI or GUITexture, display all GUI in a new camera.
o Create a new camera for GUI: isometric, ignores all other layers, Depth 1 (to draw after main camera) and Don’t Clear.
o Use Text Mesh and Mesh Renderer to draw text. Make sure that the GameObject is set to the GUI camera layer. This is a very fast and versatile to use dynamic, localized text in Unity. Text can be changed, rotated, scaled and a user-defined material can be used instead of standard font.
o Similarly, logos and images can be displayed on boxes. The triangle cost is minimal and on this isometric camera, the side walls are culled.
When transparent image is used, either use a shader with Culling On so the back wall is removed, or place the box very close to the camera so the front wall is behind the camera and only the back wall is shown.
§ White border around textures is caused when a zero-alpha color partly bleeds into a visible pixel. In this case, the RGB part of that supposedly transparent pixel is taken into effect. To fix it in Photoshop:
o Select all zero-alpha pixels
o Paint around the edges of the images with the edge color (the select region will protect the image)
o Create Layar Mask -> Hide selected area.
The result is an image where the RGB part of zero-alpha pixels matches the edges of the texture and bled pixels look nice.