46 Tips & Tricks for 2D mobile Performance in Unity.

  Unity_LogoUnity’s a beast. I’m always impressed at how versatile and performant it can be. But like any beast it requires a little bit of taming (and in some cases some very counter intuitive trickery, but that’s where this metaphor falls apart). I’ve had a bit of a struggle getting Truck Toss to play smoothly on my 3GS, but here’s a nice big list of tips to help you on your way. 

If this is your first jump into the world of Unity, my first tip (this one’s a freebie) is to stop trying to use it like other languages and environments. You will be using GameObjects, you will be adding multiple script components, and you will have to think differently. When I first started, my approach was to largely ignore prefabs (or use them like Flash’s display list) and get a copy of Box2D running. Painful as it is to deviate, get ready to put in some work!

Without further ado, let’s get started:

Physics:

-Use the built-in physics.
It might seem like a waste of cycles to have a fully 3D physics engine running the show for a 2D game, but bear in mind that the Nvidia PhysX engine will be running in Unity’s native core. We’re talking about a hyper-optimised engine maintained by large professional teams, and not a hobbyist 2D engine. Freeze Z position and X/Y rotation for that 2D feel. 

 -Try to use a 1/1 scale. 
By this I mean, 1 unit = 1 meter.  You can use larger or smaller scales, but you’re likely to encounter some weirdness when it comes to collisions, and the speed at which objects fall. Remember, a grand piano without air resistance will fall as fast as a dead baby but if everything is large, it will seem slow. You can fiddle with gravity, but again, you’re running the risk of messing with collisions. I like to use a 1/32 scale compared to my sprites with a cam size of 160.

-Get your object Mass right.
Just like with scale, if you have a grand piano weighing 2gm or a dead baby weighing 500kg things are going to get unpredictable. Try to keep it realistic-ish.

 -Mesh colliders can be slow compared to primitive box/sphere colliders. 
While a sphere may have many more verts than say a cube, the uniform distance from the centre would make it massively easier to calculate than lots of individual triangles. 

-You can simulate more complex shapes by combining primitive colliders.
If you have a parent object with say a Box Collider and Rigidbody component, you can add child objects with just a Box Collider. The entire object will collide like one solid multipart object. 

-Continuing from the above…
Rather than having several of these linked together, you can add more child objects with RigidBodies and Colliders, and use Joints to connect them to the parent object. You could build a compound car for example with a parent body to move the entire thing.

 -Multiple basic joints are not supported on one game object…
…but multiple configurable joints are. Rather than having a network of jointed objects you could for example have a spring and a slider from wheel to axle cutting down on a suspension object in between. 

-Objects with a collider but no RigidBody are considered static.
Moving these is expensive, so if you’re creating them with code, add the collider and physics material (to the collider) after positioning.

-While it’s considered good practice to keep your solver iterations constant…
…you might find it beneficial to use the full amount only every 2nd update. I.e.  8,4,8,4,8,4. If this alleviates some of the processor load the solver for example may not have to skip iterations, and will actually provide a more consistent simulation. I said it was going to be counter-intuitive.

-While the use of Interpolation and Extrapolation on RigidBodies is discouraged en-masse..
…in some cases you might find that turning these on, and reducing the overall solver iterations provides a better simulation. Fiddle. 

-Lower your timestep!
If you’re aiming for an unrealistic 60FPS and the phone is constantly struggling, you’re best just to settle for a lower framerate and give it some breathing room. I like to use a 0.03 fixed timestep with a maximum of about 0.05. Again, it can be slightly counter intuitive decreasing timesteps to get higher framerates, but give it a shot.

-Timescale scaling.
This could help, depending on the feel your’e going for. It simply simulates more time passing between each iteration. Setting this too high will obviously mess with collisions, especially if an object has traveled say, a mile in one frame, it’s not going to hit a damn thing.

 

Sprites & Textures

 My engine uses a hybrid system of Sprite Manager2/EZGUI and RageSpline for sprites, but I’ve used 2D Toolkit and much of the following still applies.

 -Easy on the fill rate!
It might seem obvious but if you have a 64×64 image, with only the top left 32×32 filled, that’s still a 64×64 sprite. Trim your transparent images where possible. Lots of libraries will do this automatically.

-Hide sprites you’re not using.
Make a reference to them and set them active = false; They won’t be drawn when offscreen anyway, but something has to determine whether or not they’re visible and chances are you know best, especially when one sprite may be fully hidden behind another and is still drawn.

-Batching is your friend. But not always.
If you have 40 collectable coins in your level, all using the same sprite then batching will use the one texture source multiple times on a  giant mesh, saving on draw calls. Draw calls=time. In some very rare cases the batch calculations can be a hinderance, depending on how your game’s set up, but if that’s the case, chances are you’re doin’ it wrong.

 -Resize your sprite’s Quad (the sprite itself) rather than its transform.
If you have say a sprite component on a GameObject, then resize the GameObject’s transform, you’re going to break batching on that sprite. Instead consider the next point. With SM2 for example, you’d just set the Sprite/PackedSprite’s “Width” and “Height” properties in the inspector.

-If you have a 64×64 sprite, on a 6px wide cube…
…then it’s going to look like a small version of your image, but upon zooming in, you’ll see that the full 64×64 sprite has been UV mapped to the cube in perfect detail. Remember what I said about fill rate. Unless your’e zooming in and out, you might not want to use such a large texture.

 -Use a Sprite Sheet/Atlas where possible.
This one ought to go higher, but what the hell. A sprite sheet will allow you to use commonly grouped items like your character, coins, platforms, etc in a single image/texture. Why? Less draw calls! The same part of a texture can be UV mapped to different parts of a 3D shape multiple times. I.e. if you were modelling a red and white stripy candy cane, you’d draw one white and one red line, then apply them multiple times. This is a similar concept.

 -Use the right shaders!
There’s no point using lit shaders if you’ve no lighting, and there’s no point using a transparent shader on a solid square sprite. You can find dedicated mobile shaders in the Unity Store, by googling and doing a little copy-pasta via MonoDevelop or using those that come with SM2/Unity. As of 3.5 I believe the default shaders do a pretty decent job. Coming from various other backgrounds it might be easy to underestimate the importance of these even in a 2D environment.

-Do you really need antialiasing/filtering on your sprites?
Be sure to check on your target device. Some things will look pretty horrific scaled up on your monitor, but absolutely fine on those tiny high-density screens. Give it a shot, and remember to apply changes to your Sprite Atlas where possible.

-Easy on the compression!
DXT (DirectX) compression will do a fantastic job on your PC, with hardware decoding, but mobile devices lack this hardware decoder and will have to do it in software. I.e. Slowly. Generally IOS devices will support hardware PVRTC compression and Androids ETC, and keep in mind what I said in the last point. DXT might be fine given that it offers better clarity during say level loads, but you certainly don’t want to be decompressing them during gameplay.

-Do you need Mip Maps?
Mip maps are scaled down versions of a texture stored within the compressed texture itself. So depending on how far away you are, a lower res copy can be used. Obviously this takes more memory and more decompression time. You probably don’t need ‘em for a 2D game. 

-Conversely…
…rather than using giant sprites on a non retina display and tiny sprites on a  retina display, it might be worth your while making a small and large version of textures and using each accordingly.

 -Read/Write enabled textures generate a second copy.
Second copy needs more memory. In most cases, you can just leave this turned off.

-Tinting a sprite will break batching…
…and create a new copy of the source texture in memory. Avoid where possible, or try to pre-make any colors you’ll need! E.g. if all your Numbers in a text sprite sheet are to be red.. do it in photoshop.

 

Loading, Saving and Object Access:

 

-Do you really need to recreate your GUI for each level?
You can hide it and have it persist when loading different scenes, reducing loading time.

 -GameObject.Instantiate() is slow!
One common technique (which proved absolutely vital in Truck Toss) is to create a pool of objects during loading. E.g. 4 of each enemy type. When an object’s no longer needed, disable it and shove it back in the pool instead of recreating it. So you’d have a function along the lines of MakePrefab(“path/to/prefab”); which will only only call Resources.Load() provided there are none in the pool.

-Resources.Load() is even slower!
This function does not cache, and involves reading from the device’s HD or equivalent. Ideally you want a hybrid pool system if you’re doing a lot of loading\unloading. I.e. your standard pool system which preloads objects,  but when the instantiate function is called, it keeps a different copy in a different list. Whenever instantiate’s called and there aren’t enough in the pool but there’s a copy in the spare list, Instantiate from that rather than doing a Resources.Load fresh again.  This is a balancing act of memory use and processor use, so target it for your device.

 -GameObject.Find() and GetCompoenent()..
…are slow (You saw that one coming, right?). If you’re going to be using an object or component repeatedly, then it makes sense to create a reference to it in a local variable rather than looking it up repeatedly.

-Reflective functions can be noticeably slower.
Reflection is the ability for a language\code to look within itself and get method names\types\scope etc and potentially alter them. I.e. calling a function by string name, or using delegates. Try to avoid this kinda of behaviour for performance critical code.

 -The garbage collector is slower yet.
It has to scan trees of objects looking for orphaned classes and objects, and ‘islands’ of objects with references only to each other, determine how long they’ve been that way, and then free up the memory. Sure you can call it manually, but that’s generally more of a hint to it than a command, and shouldn’t generally be used during gameplay.

 -Using too much memory…
…will cause IOS devices to crash\quit and your app won’t be accepted to the app store. That’s actually secondary to the point that when memory’s low, your game will slow down dramatically, especially during garbage collections and when you’re trying to instantiate new objects.

 

Sounds: 

-Set your BG music to decompress on load.
Anything you’ll be using a lot should probably be decompressed on load rather than streaming from the disk (which is slow and can be especially troublesome on Android.) This is another tradeoff situation however, given that decompression can take time and memory. Balance it!

-Infrequently used clips…
..may be left compressed in memory, especially if there are lots of them.

 -Force to mono?
Yes yes! Unless you really want stereo and you’ve got some bitchin’ music or sound effects, you’ll save yourself some space on this one. Remember the phone has a mono speaker

-Hardware decoding is faster.
Well, that’s a generalisation, but faster is better. 

UI: 

-Unity’s UI system is slow.
Where possible try to use a plugin or sprite package that renders to meshes in 3D space.

-OnGUI is slow!
Even in a blank scene you can see it spike. Try to have no more than one of these in your code, and centralise branches out from it. It’s fairly easily done.

-Try to keep your UI animations in FixedTimestep() functions.
This way they’ll stay consistent across multiple devices and framerates. You might benefit from having all of your game logic for every class branch out form a single base call to FixedTimeStep();

-Does your UI have to update *every* frame?
You might gain a massive performance boost by deferring it to every 2nd or third frame. With truck toss, the game runs much smoother with the entirety of the game logic running on every second FixedUpdate. I know, right?

-Moar Cameras!
If your game does a lot of scaling\zooming, then why have to scale the UI and risk breaking batching? Add another camera for the UI, and set UI objects’ layers to that of the camera. Again, it sounds like a lot of extra blitting, but could potentially speed your game up with very little in the way of changes.

Build Options:

 -Disable the accelerometer!
In older Unities, that was done via #define kaccelerometer_frequency 0 in the AppControler class via XCode. Nowadays you can disable it from within Unity itself, and can free up 2-3 FPS.

-In some cases OpenGL ES 1.0 or 2.0…
…will offer better performance on your device. This seems to vary between devices and Android\iOS. Try on as many devices as you can.

-Strip things down!
When your game’s running nice and stable, switch compatibility to the .NET 2.0 subset, Stripping Level to micro mscorlib and Script Call Optimisation to Fast but no Exceptions. This will generate a smaller binary with less redundant code and debug symbols. I successfully navigated that one without a stripper reference. Jugs.

-Target iOS 5 where possible.
There really is no discernible speed difference. However, make sure your XCode project’s settings match, or uploading to iTunes might fail. Perhaps it’s worth exporting another project.

 

Bonus Content – Building Faster!:

-Symlink Unity Libraries…
…from the build settings where possible, this will save copying more files over to xCode, by effectively creating a shortcut. Much love for symlinks.

Set your “Debug Information Format”
From within Xcode (Build Settings) to STABS, otherwise have fun watching DWARF taking ages. Stabs..dwarf… Lols…

-Sometimes using “Build for “…
…within XCode will build and run faster. Delete the previous version on your device and give it a shot. I have no idea why this is so, but you’re welcome.

 

Bonus Content 2 – Faster physics modelling:

 This is a fun little technique I use to convert edge chains to solid blocks!

Create a fake edge chain, where each link between 2 points is actually a stretched out, rotated cube parented to something, forming the outline of shape of your choice (cloud\star\car\whatever). Hit play, and while the game is running, drag your chain back into the editor to make a prefab. You now have a group of parented objects you can fill in and make solid. This lets you use your own code to generate physics shapes.

 

And finally,
Break every rule. In the spirit of hacking away at things, you must fiddle and see what’s going on.If something seems like a silly idea at first, you might just not have thought about it from all angles. 

Let me know if you’ve anything interesting to add. Happy hacking!

Leave a Reply

Your email address will not be published. Required fields are marked *

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>