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 loadingunloading. 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 languagecode to look within itself and get method names ypesscope 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 criti