Note: Most of this write-up from mid-2011 is now largely irrelevant. Unity is introducing a new 2D-specific engine (rumor is that it’s Box2D) in Unity 4.3, which is currently in closed beta. For more information, see our “Unite 2013″ conference post-mortem.
Our last game, Brainarang, was built using cocos2d, which is an awesome open-source 2D game engine for iOS. As happy as we were with it, we couldn’t resist the siren song of Unity. Unity is AMAZING. If you don’t know about it and are interested in game development, stop reading this and go bend your knee in fealty right now.
But for all its godlike virtue, Unity is still a 3D game engine (yes, I just compared Unity to a giant, a beautiful woman, a king and a deity in, like, 15 seconds. Love is a powerful thing.), so you have to do a little monkeying to do 2D. Below is a compilation of what I’ve learned for handling this, not only from Unity and my own experience, but from the use of third-party tools and from the awesome community of developers sharing what they’ve learned (see Props below).
Orthographic vs Perspective Camera
Orthographic is the easy choice for 2D games. With the orthographic camera, objects further away from the camera don’t reduce in size as they do in perspective. It essentially flattens the world, which makes sense for a 2D game. This makes positioning super simple. You can use z position for simple z-ordering, and any object with same x and y position will always be the same place on the screen. Also, because objects will always be the same size, pixel-perfect textures are easier to deal with (see Textures, below).
One reason you may want to use perspective instead is that you can get parallax scrolling without writing code, since objects in the distance will scroll more slowly than those in the foreground. If you choose to do this, Sprite Manager 2 has a pixel-perfect option that will automatically resize textures, although this runtime resizing can make manipulating objects in your scene confusing.
Also, there is a depth sorting issue when using perspective that can lead to objects appearing closer than objects in front of them. If you have this issue, checkout this video (halfway into Part 1) from Owlchemy Labs, who explain and have a solution for the problem.
I prefer using orthographic and write my own parallax code, but there are arguments for each method. Here is a forum thread that goes a little deeper on the issue.
Unless your game scales sprites or does zooming, you will probably want pixel-perfect textures, meaning textures that are rendered on screen at their native size. In 3D games, textures are resized constantly as they move closer or diminish in the distance. Unity, by default, imports textures with some optimizations for this. You should disable these for crisper textures. In the inspector, choose Filtering: Point, select Texture Type: Advanced and disable Generate Mip Maps.
In order to size your meshes so they display textures pixel-perfect, you need to figure out the ratio of pixels to world units. This is based on orthographic size of your camera and the resolution of the screen. The size of the camera will be half the height, in world units, of the screen. So, if you have a camera size of 10, the world unit height of your screen will be 20. If your game pixel resolution is 480×320, each world unit will be equal to 320/20 = 16 pixels. You might be tempted to change the ortho size so that there is a one-to-one ratio. This might be ok if you aren’t using physics, but not if you are. The physics engine is tuned so that objects will behave most realistically when each world unit is equal to a meter in game.
Once you have determined this ratio, it can be tedious to resize all your meshes. Don’t soil your pretty little pants about it, Owlchemy Labs has a script to resize automatically and Sprite Manager 2 has a pixel-perfect option that will do this as well.
Remember to size the mesh itself, don’t change the scale in the transform component, otherwise you won’t be able to take most advantage of draw call batching. See Meshes and Batching Draw Calls below for details.
Another thing to consider is compression. You can select from three compression levels for each texture. For iOS, the tightest compression is PVRTC. These are very small memory-wise, but can be pretty crappy-looking for 2D games, especially with gradients and alpha edges.You have to weigh quality against performance here.
You generally only need one type of mesh primitive in 2D games: a quad. Unity ships with a plane primitive but it has way too many triangles. You only need two. You can either create your own quad in a 3D modeling program or in code. Or Sprite Manager 2 creates them automatically.
Draw Call Batching
One of the most important things you can do for performance is to batch your draw calls as much as possible. There are two main ways to do this in Unity. The best and easiest way is static batching, but it has some limitations: it’s only available in Unity Pro, it can only be used on game objects that will not move, scale or rotate, and only objects that share the same material will be batched.Check the static flag at the top of the inspector of any game object to enable this.
The second is dynamic batching. This is done automatically for any objects that share the same material. It allows objects to move and rotate, but NOT scale. All objects to be batched must have identical scales. This is not well documented and took me a while to realize. It is why it is so important to resize meshes themselves instead of scaling with the transform. If you are using Sprite Manager 2, setting the sprite size is safe for batching.
Because of the need to share materials to achieve batching, and because there is only one texture per material, using sprite sheets becomes very important. Also, be aware that many manipulations of an object’s material in code will actually make a local copy of the material and destroy batching for that object. You can manipulate the shared material, but this will change the material for all objects.
Use them! Packing multiple images onto one texture atlas helps performance in several ways. It cuts down on memory use and app size, and more importantly, it allows different sprites to share a material and thus a draw call. You can definitely roll your own solution for this, or (brace yourself) you can just use Sprite Manager 2.
Lighting and Shaders
Lighting is not needed for 2D games and it is expensive, especially for mobile games. Don’t put light sources in your scene. All of the shaders that ship with Unity use lighting and will cause unneeded processing. Owlchemy Labs has a pack of simple, unlit shaders available on their site for free. They’re the only ones you’ll need.
It’s super easy to set up physics for 2D. Just set the constraints on each of your rigidbodies to disallow z movement and x and y rotation. Then just leave your colliders with some depth, and you’re done! You just made the next Angry Birds.
Sprite Manager 2
We use Sprite Manager 2, and it has been ridiculously helpful. It helps or solves many of the issues I’ve talked about here, and that is really just an awesome by-product of its main purpose, which is to be a very nice sprite frame animation tool. Currently its $150 on the Unity Asset Store. Its more basic ancestor, Sprite Manager, is free. My only criticism of it is that it sometimes doesn’t lay out atlases optimally, but I’m guessing that’s a difficult problem.
iTween is free animation tool for Unity with lots of coded-up examples for sale for a buck a pop.
Rage Spline is a crazy looking unity tool that allows in-editor vector-style drawing of objects, including creation of meshes and colliders.
If it seems like I’ve mentioned Sprite Manager 2 and Owlchemy Labs a lot, it’s because you’re paying attention. I’ve learned a lot and gotten a lot of utility from both. I definitely recommend checking them out, including Owlchemy Labs amazing game Snuggle Truck.
I hope this helps someone out there. Make it good and make it flat.