Pixel Perfect 2D
11/2018 Update: We now have a Pixel Perfect package that solves this issue! Learn more & get it here.
How do I make a pixel art game that looks great? We get that question a lot. If you start your pixel game with all the default settings in Unity, it will look terrible! But the current version of Unity can be made to render pixelated games that don’t suck. Read on for a rundown of do’s and dont’s.
Image on the left is rendered on a poorly calibrated camera while the one on the right is properly calibrated. (click on image to see full-res)
The Secret Sauce
The secret to making your pixelated game look nice is to ensure that your sprite is rendered on a nice pixel boundary. In other words, ensure that each pixel of your sprite is rendered on one screen pixel (or any other round number). The trick to achieving this result is tweaking the camera’s orthographic size (and live with the consequences).
The Basics: What Is Orthographic Size?
On an orthographic camera, the orthographic size dictates how many units in world space the screen’s height is divided into. So on a screen height of 1080 with orthographic size of 5, each world space unit will take up 108 pixels (1080 / (5*2)). It’s 5 * 2 because orthographic size specifies the size going from the center of the screen to the top.
Therefore if your Sprite’s Pixels Per Unit (PPU) settings is 108, it will be rendered nicely.
Practical Approach To Achieving Pixel Perfect.
To apply this on an actual project requires a bit more considerations and planning ahead. Since you can’t tweak the physical resolution of the target device and you only have a very limited range of PPU (by way of asset bundle variant), the only real number that you can play with is the orthographic size.
Changing the orthographic size will have the effect of increasing or decreasing the visible world space. This impacts your game code directly and need to be accounted for. Here’s a table with various scaling scenarios
|Vertical Resolution||PPU||PPU Scale||Orthographics Size||Size Change|
* Orthographic size = ((Vert Resolution)/(PPUScale * PPU)) * 0.5
There are three well known techniques to handle the scaling and it is quite plausible that you will employ all three in your project.
Technique #1: Thick Borders
For a small difference, a simple ‘thick border’ will suffice. Depending on your game’s design, a few percentage bigger or smaller than your reference screen size can be easily filled with the thick border.The overall screen size grows but the art size stays the same. This can be easily dealt with by increasing the thickness of the floor/roof.
However, when the screen increases beyond a certain size, a thick border will no longer be practical.
Technique #2: Increase Asset Resolution
If you start out with a reference screen height of 768 and PPU of 32, going to 1080 is going to be troublesome as your assets will not scale nicely.
The table above illustrates scaling a game starting from 768 all to way to 1536 with three alternatives on how to deal with 1080. The most interesting number to look at is the orthographic size. The ideal situation is that as your screen size grows, the orthographic size remains the same. This will ensure that your assets will take up the same amount of screen space as they do with your reference resolution.
From the table above, using an alternate sprite at PPU 48 would create a visible world space that’s only 6.25% smaller than your reference size. This could be easily dealt with using the thick border approach.
PPU 32 vs PPU 48 at 1080 with ortho size 11.25. Notice the terrible looking PPU 32 sprites on the left. Color shifted on the right to clearly show swapped asset variants.
Technique #3: Halving Orthographic Size
If your screen grows big enough, we could display your sprites at 2x by applying a scaling factor to the calculation of the orthographic size. For the case of 1440, we could keep using the PPU 32 sprites but calculate the orthographic size as such (1440/(2*32)) * 0.5 which gives you 11.25.
This means, each world space units will contain 64 screen pixels. This effectively tells the engine to render 32 pixels from the sprite onto 64 pixels on screen. This gives a nice whole number factor of 2 which will look good at the same time giving you a world space that’s just 6.25% smaller than your reference setup.
This technique does not require new assets to be created.
* The PPU 32 sprites are now rendered twice as big but still look fantastic. This only works for whole number enlargement.
These other settings are essential to make things as crisp as possible.
On the sprite:
- Ensure your sprites are using lossless compression e.g. True Color
- Turn off mipmapping
- Use Point sampling
In Render Quality Settings:
- Turn off anisotropic filtering
- Turn off anti aliasing
(Optional) Turn on pixel snapping in the sprite shader by creating a custom material that uses the Sprite/Default shader and attaching it to the SpriteRenderer.
To implement the above system requires 2 parts.
- Create a simple component that calculates the orthographic size for the camera based on the screen height.
- Allow for overrides for certain resolutions so that the user could dictate the PPU settings for that resolution
- This calculation takes place at the start of the scene
- Load the right asset bundle variant based on the screen height. Either load the default bundle or one that’s based on the override settings.
Check out a demo project that demonstrates this approach
Even with all these number tweaking, there are still a few issues:
- Physics and animation systems might move/rotate your object to a position or rotation that’s not ideal.
- Single-pixel width art assets will render poorly if they are allowed to be rotated arbitrarily. This is true no matter what you do. The only real solution to this is to either:
- Avoid single pixel art
- Always ensure they are axis aligned
Making pixel perfect 2D games is possible with the current version of Unity. However, as detailed above, there are consequences and known issues, but they are not insurmountable nor are they blockers. Long live retro style games!