Unity 2D Tips and Tricks

Category : Coding, Design, Games, Productivity · by May 28th, 2015

While the 2D features in Unity3D gets a lot less love than the rest of the engine, Unity3D is still a very powerful tool for rapid prototyping in 2D given some customization. Since the 2D features leverage design decision catered toward 3D, there are occasional gotchas in 2D as well to be aware of. Here I detail some of the tricks and gotchas I noticed while working with 2D in Unity.

In general, it is a VERY GOOD IDEA to extend the given MonoBehavior class to create shortcuts for verbose but necessary instructions. The shortcuts can be implemented by way of C# properties. This reduces clutter in code and leads to less bugs. In fact, all but one of the “tricks” uses this to simplify 2D programming.

Tricks:
1. Barring very special cases, most of your objects are going to have the SpriteRenderer component. When extending MonoBehavior, you can implement a shortcut to access spriteRenderer:

private SpriteRenderer _spriteRenderer;
public SpriteRenderer spriteRenderer{
	get{
		if(_spriteRenderer == null){
			_spriteRenderer = GetComponent();
		}
		return _spriteRenderer;
	}
}

The _spriteRenderer in this code caches the spriteRenderer to skip extraneous costly GetComponent calls.
Now, monoExtendedObj.GetComponent() can essentially be replaced with monoExtendedObj.spriteRenderer.

2. Angle manipulation in 2D is usually limited to rotation on the Z axis. However, to specify Z rotation you must go through Quaternions, which are necessary for 3D rotations. We can shove the messy quaternion operations under the rug by defining a custom “angle” property:

public float angle {
	set {
		Quaternion rotation = Quaternion.identity;
		rotation.eulerAngles = new Vector3(0, 0, value);
		transform.rotation = rotation;
	}
	get {
		return transform.rotation.eulerAngles.z;
	}
}

Note that when getting a value from this property, you will always obtain a value between 0f and 360f. If this is undesirable, consider defining a private variable that stores the non-moded angle value.

3. Alpha (opaqueness) values require dealing with colors property of the spriteRenderer – an extremely verbose operation even with the existing spriteRenderer shortcut. This property lets you set transparency of the spriteRenderer easily

public float alpha {
	set {
		if(spriteRenderer != null){
			Color _color = spriteRenderer.color;
			spriteRenderer.color = new Color(_color.r, _color.g, _color.b, value);
		}
	}
	get {
		if(spriteRenderer != null){
			return spriteRenderer.color.a;
		}
		else return 0;
	}
}

An alpha value of 1 or more is fully opaque; 0 or less is fully transparent. The values are not clamped between getting and setting so that the alpha may double as a transparency counter/timer. However you might want to use a Tweener to achieve timing effects more gracefully.

Tips:
1. Most graphic bugs from your 2D game will come from drawing order or faulty z-values. Unity3D’s ordering scheme for 2D sprites is versatile but also confusing. The determining factors for sprite ordering are (from highest to lowest priority):
-Sorting Layers (Bottom ones in the Unity UI are drawn on top)
-Order in Layer (Higher orders are drawn on top, negative values are possible)
-Z value (Closer in camera line-of-sight (LoS) drawn on top)

By default, the camera is at a height of 10 with LoS pointing downward. This implies that without changing the camera, higher Z values are drawn on top. HOWEVER, the camera also has a near-clipping plane of 0.3, meaning that it will not see anything closer than 0.3 units away from the plane perpendicular to the camera LoS (using default values, any Z-value > 9.7).

Finally, note that Order in Layer is a signed short ranging from -32,768 to 32,767. Attempts to utilize numbers outside of this range will likely mess up your drawing order.

There are several takeaways from this. The first is that you should be careful with modifying default camera values unless you are certain about how camera works in 2D. The next is that while working with dynamic Order in Layer and/or Z values are convenient, you will be working with their respective constraints. In general, Z values are better for handling cases where many more layer values than 64,000 are needed (this does occasionally happen), but can cause sprite to disappear if you are not careful. Z value is also a float, so keep rounding errors in mind.

2. Unity3D’s particle and trail renderers do not have their “sorting layer” field exposed, and that’s terrible because suddenly you cannot control the drawing order of your particles and trails.
To fix this, write a custom component that has the below code and attach to your particle or trail renderer:

void Start () {
        //Change Foreground to the layer you want it to display a particle
        //system on, otherwise particles will not show up.
        GetComponent().GetComponent().sortingLayerName = "YOUR_LAYER_NAME";
}

Note that this will set your object’s drawing layer to a static layer called “YOUR_LAYER_NAME”. If this is not your wish, or you prefer to select the drawing layer in the Unity3D UI, check here to obtain a list of drawing layers and work from there.

3. As these reddit users have found, Unity3D’s 2D collision detection system will make your life very tough if you do not use physics. This is problematic for a lot of simple 2d games where physics would either be a overkill or make debugging difficult.

I have found that the 3D collision detection system has much less stringent requirements. You must still have at least one rigidbody per collision detection, but if you tightly control the z values of your objects (usually by clamping it to 0), you can get around a lot of weird issues that arises in 2D collision detection without physics.

Note that doing both this and ordering with Z value can get hairy fast. Avoid one or the other.

4. Often it is useful to know the dimensions of the sprite either at import time or after stretching, the variables below have very similar names, but only some will be correct:

SpriteRenderer.sprite.rect will specify the size of the sprite in pixels.
SpriteRenderer.sprite.bounds.size will provide the rectangle bounding box of your sprite before scaling in game units. The size of this bound is usually SpriteRenderer.sprite.rect divided by it’s “pixel per unit” under the sprite import settings.
SpriteRenderer.bounds.size will provide a rectangle bounding box of your (sprite)render as it appear in the game, which means after stretching SpriteRenderer.sprite.bounds.size through transforms.

All .size can be replaced by .extents to obtain a box with each dimension halved, this is a useful shortcut when only the size of half the bound box is required.

These are all I have regarding 2D, for now! I’m planning on starting a general Unity3D tips and tricks post soon, and most of that post will apply to 2D as well, so stay tuned!

SHARE :

(1) Comment

Joule
3 years ago · Reply

Great post. Thanks!

Leave a Reply

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