Unity is a fantastic engine, but its history as a 3D engine sometimes shows when using it to develop 2D games. This is especially apparent when games use a pixel art style. This series will demonstrate some common problems, and provide solutions to make developing 2D games in Unity easier.

This first post in the series will focus on the pixel grid and how to lock items to it. Unity uses floating point positions for objects, unlike many 2D specific engines which use pixel locations. This means that sub-pixel movement is a common problem in Unity games and can lead to rendering artifacts or inconsistent visuals. Sub-pixel movement refers to objects moving between pixels causing pixels to not align properly. Though this may not always be an issue, depending on your game it can be an eyesore. The image below is an example of sub-pixel movement leading to pixels on the screen not lining up properly.

The first image is an example of sub-pixel character movement where the player sprite pixels aren’t lined up properly with other pixels in the scene. The second is a gif that shows how sub-pixel camera movement can lead to strange rendering artifacts where pixels have different widths. It’s subtle in the gif, but if you focus on the ground sprite pixels you can see them changing width as the camera moves.

I’ve created a simple script that you can add to child objects to lock their positions to a grid. For example, you’d have a parent GameObject which handles the player’s movement, and a child GameObject that contains a SpriteRenderer. This ensure that the player can move in a sub-pixel way ensuring smooth movement, while still locking the sprite to the pixel grid.

using UnityEngine ; public class SnapToPixelGrid : MonoBehaviour { [ SerializeField ] private int pixelsPerUnit = 16 ; private Transform parent ; private void Start ( ) { parent = transform . parent ; } private void LateUpdate ( ) { Vector3 newLocalPosition = Vector3 . zero ; newLocalPosition . x = ( Mathf . Round ( parent . position . x * pixelsPerUnit ) / pixelsPerUnit ) - parent . position . x ; newLocalPosition . y = ( Mathf . Round ( parent . position . y * pixelsPerUnit ) / pixelsPerUnit ) - parent . position . y ; transform . localPosition = newLocalPosition ; } }

LateUpdate() runs every frame after all Update() calls are completed, and offsets the GameObject so it lies on the pixel grid determined by pixelsPerGrid and it’s parent’s location.

Download the Windows demo or Mac demo to see this in action. You can also download the example project to experiment with the script.

Hopefully this approach is enough to get you started and alleviate any 2D pixel art rendering issues. If you have any further questions please feel free to message me on Twitter @RyanNielson or comment below.