Unity Masterclass: How to set up your project for pixel perfect retro 8-bit games
Retro games with simple mechanics and pixelated graphics can evoke fond memories for veteran gamers, while also being approachable to younger audiences. Nowadays, many games are labeled as “retro”, but it takes effort and planning to create a title that truly has that nostalgic look and feel.
That’s why we’ve invited the folks from Mega Cat Studios to help us talk about the topic. In this blog post, we’ll be covering everything you need to create authentic art for NES-style games, including important Unity settings, graphics structures, and color palettes.
Mega Cat Studios, out of Pittsburgh, Pennsylvania, has turned the creation of highly accurate retro games into an art form. So much so, in fact, that several of their titles can also be acquired in cartridge form and played on retro consoles like the Sega Genesis.
Unity workflows for maximum retro-ocity
Recent additions to the Unity workflows have made it a well-suited environment for creating your retro games. The 2D Tilemap system has been made even better and now supports grid, hex, and isometric tilemaps!
Additionally, you can use the new Pixel Perfect Camera component to achieve consistent pixel-based motion and visuals. You can even go so far as to use the Post Processing Stack to add all sorts of cool retro screen effects. Before any of this work can be done, however, your assets will need to be imported and set up correctly.
Preparing sprite assets
Our assets first need a correct configuration to be crisp and clear. For each asset you’re using, select the asset in the Project view, and then change the following settings in the inspector:
- Filter mode changed to ‘Point’
- Compression changed to ‘None
Other filter modes result in a slightly blurred image, which ruins the crisp pixel-art style we’re looking for. If compression is used, the data of the image will be compressed which results in some loss of accuracy to the original.
This is important to note, as it can cause some pixels to change color, possibly resulting in a change to the overall color palette itself. The fewer colors and the smaller your sprite, the greater the visual difference compression causes. Here’s a comparison between normal compression (default) and no compression.
Another thing to be aware of is the Max Size setting for the image in the Inspector. If your sprite image has a size on any axis greater than the ‘Max Size’ property (2048 by default) it will be automatically resized to the max size.
This will usually result in some loss of quality and cause the image to become blurry. Since some hardware will not properly support textures over 2048 on either axis, it is a good idea to try to stay within that limit.
Above, is a sprite from a spritesheet that was 2208 on one axis with max size set at 2048. As you can see, increasing the Max Size property to 4096 allows the image to be sized appropriately and avoid a loss of quality.
Installing the 2D Pixel Perfect package
With assets prepared, we can set our camera up to be “pixel-perfect”. A pixel-perfect result will look clean and crisp. Telltale signs of pixel art which aren’t displayed as pixel-perfect includes blurriness (aliasing), and some pixels appearing rectangular when they should be square.
The 2D Pixel Perfect package can be imported through the Package Manager in Unity. Click the ‘Window’ menu in the toolbar followed by ‘Package Manager’. In the new window, click ‘Advanced’ and make sure you have enabled ‘Show preview packages’. Select 2D Pixel Perfect from the list on the left, and select install on the top right of the window.
That’s it. Now you are ready to begin using the pixel-perfect camera component.
A high level of pixel-perfect-itude
The Pixel Perfect Camera component is added to and augments Unity’s Camera component. To add it, go to your main camera and add the Pixel Perfect Camera component to it. If the Pixel Perfect Camera component option is not there, follow the previously stated instructions to first import it into the project.
Now let’s look at the settings we have available.
First, I recommend checking ‘Run In Edit Mode’ and setting the display aspect ratio in the Game view to ‘Free Aspect’ so you can resize the game view freely. The component will display helpful messages in the game view explaining if the display is not pixel-perfect at any given resolution.
Now, you can go through each setting to see what they do and how they affect the look of your game!
Assets Pixels Per Unit
This field is in reference to the setting you can select in the inspector for each asset. As a general rule of thumb, each asset that will be used in the game’s world space should use the same pixels per unit (PPU), and you’d put that value here as well. If your game world exists as a grid of tiles and sprites, with each being 16 pixels by 16 pixels, a PPU of 16 would make sense – each tile of the grid would be 1 unit in worldspace coordinates. Make sure you put your chosen PPU here.
Set this to the resolution that you intend all of your assets to be viewed at. If you want a retro look, this usually means a very small resolution. For example, the native resolution for the Sega Genesis is 320×224. When porting a game from Sega Genesis, we would use a reference resolution of 320×224. For general 16:9 usage, 320×180, as well as, 398×224 (if you want to keep the vertical resolution instead) should work well.
Upscale Render Texture
This causes the scene to be rendered at as close to the reference resolution as possible and then be upscaled to the fit the actual display size. Because this setting results in a filled screen, we recommend it if you want a full-screen pixel-perfect experience with no margins. ‘Upscale Render Texture’ will also significantly affect how sprites look when rotated.
- Original (not rotated)
- Without Upscale Render Texture (rotated 45 degrees, pixel-perfectness is lost as pixel size varies on diagonal edges)
- With Upscale Render Texture (rotated 45 degrees, pixel-perfectness is maintained as all pixels are the same size, but the sprite is less accurate looking compared to the original.)
Only available with Upscale Render Texture disabled – With this enabled, sprite renderers will be snapped to a world-space grid automatically, where the grid’s size is based off of your chosen PPU. Note that this does not actually affect any object’s transform positions. As a result, you can still smoothly interpolate objects between positions, but the visual movement will remain pixel-perfect and snappy.
Pixel Snapping disabled. With our background at position (0, 0) and our character sprite at (1.075, 0), we get some pixels not lining up correctly. Notice how there are some pixels that are only half-covered by the shadow. Pixel Snapping enabled. Same positions – background (0, 0) and character sprite (1.075, 0). The pixels snap together perfectly.
Crop Frame (X and Y)
This crops the viewed region of worldspace to exactly match the reference resolution, and adds black margins to the display to fill the gaps at the edges of the screen.
Becomes available if you enable both x and y for Crop Frame. This causes the camera to scale to the game view to fit the screen in a way that preserves aspect ratio. Because this scaling won’t happen only in whole number multiples of the reference resolution, it will cause pixel-perfectness to be lost at any resolution which is not a whole number multiple of the reference resolution.
The advantage here is that even though you lose pixel-perfectness for many resolutions, you won’t have the black bar margins and will instead have a fully filled screen. Note that although blurring often occurs from stretch fill, the usual alert display message does not show up.
Recommendations on using the Pixel Perfect Camera
If you want a pixel-perfect and snappy display that will work for a variety of use-case, I recommend:
- Use a reference resolution that will never be bigger than a player’s window resolution (such as 320×180).
- Enable or Disable Upscale Render Texture
- Enable it if you will use rotations outside of 90, 180, and 270 and if you prefer the visual effect it has on rotated sprites.
- Upscaled render texture can result in a non-pixel-perfect image at some resolutions, depending on your reference resolution. Experiment with this and different screen resolutions using ‘Run in Edit Mode’ enabled on the Pixel. Perfect Camera component to determine whether this is an issue for your resolution. If you can get this to produce a pixel-perfect image at all target resolutions, this will result in the best full-screen pixel-perfect experience.
- Enable or Disable Pixel Snapping as you prefer
- This is more personal preference than anything. Without snapping, you have much smoother movement, but pixels can be out of alignment.
- Enable Crop Frame X and/or Y if not using Upscale Render Texture
- If you can’t consistently get a pixel-perfect result with upscale render texture, cropping X and/or Y will ensure a pixel-perfect image for any resolution greater than the reference resolution, but creates big margins at the edges of the screen for some resolutions.
- Disable Stretch Fill
We recommend setting the camera to be optimized for 16:9 aspect ratio viewing, including reference resolution if possible.
At the time of writing, most gamers play on 16:9 monitors, and in 1920×1080 resolution. For example, 320×180 reference resolution is 16:9, and so it will have no black bar margins when played at 1920×1080 or any resolution which is an even multiple of 320×180, such as 1280×720.
In Unity’s toolbar, you can go under Edit > Project Settings > Player and limit the aspect ratios that the game will support. If you find a particular configuration works just as you want in the ratio you’re targeting but looks bad in some particular aspect ratios, you can prevent the window from being at those ratios here. However, keep in mind that not all users will have a display setup that will work well with your limitations, so this is not recommended. Instead, enable cropping so these users will have margins, rather than having to play in a resolution which doesn’t fit their screen.
Creating Authentic NES Styled Artwork
Now that we’ve covered how to set Unity up for pixel-perfect art, let’s look at the basics of creating artwork for games that follow the restrictions of the classic Nintendo Entertainment System. This console generation places a large number of restrictions on the artists trying to create an authentic image. These restrictions include things like palettes used and the size and amount of objects on a screen. Additionally, it is import to keep in mind is the reference resolution of 256×240 when “targeting” this console.
When creating artwork that is genuine to the NES, there are a host of restrictions that the artist will have to follow. Some of these will be consistent no matter what retro console an artist is attempting to emulate, while many others are specific to the NES itself. The first, and possibly the most important of these restrictions involve the way color palettes are used in an image.
The NES is fairly unique when it comes to its color palette because the full-color palette of the console is hardcoded into the console.
The NES chooses which colors to use in an image by sending a series of values to the graphics processor on the NES, and then the graphics processor returns the colors associated with those values. Below is an image of the NES’ color palette:
These colors cannot be changed due to the fact that they are part of the console themselves. Every game you have ever seen for this console uses combinations of these colors in order to make their images.
To create the combinations that are used in the game, sub-palettes are created and assigned to either the in-game sprites or background elements. The NES breaks its palette up into sub-palettes that can be assigned to sprites and backgrounds. Each sub-palette includes one common color that is used across all of the sub-palettes and three unique colors. It is capable of loading four sub-palettes for the backgrounds and four sub-palettes for the sprites. In the case of the sprites, the common color at the beginning of each sub-palette is treated as transparency.
This is an example of a series of sub-palettes that are being used in a game. The top row represents the background sub-palettes and the bottom row represents the sprite sub-palettes. In this example, black is being used as the common color that is shared across all of the sub-palettes. Because the common color is treated as transparency on sprites, a second black palette entry is needed to be made for the sprite sub-palettes, in order to use it as a visible color.
The restrictions on palette use get even tighter as the artist moves on to how the palettes are used in the game. To explain this, there needs to be further discussion on how retro consoles store, use, and display art. The artwork in any retro console is stored in the game as 8×8 px tiles. Using this tile-based approach allows artists to save space by reusing tiles for different things. (For example, pieces of a sidewalk can be repurposed and used to make the ledge on a building).
The other important thing to note about tile based storage is that color information is generally not saved with the graphics.
All of the tiles are saved with a monochromatic palette. This way, whenever a tile is displayed in the game it can have a sub-palette assigned to it, allowing the same tile to be simultaneously displayed on screen with different sub-palettes This is significant when creating artwork that is true to a retro console on a modern platform because it affects how you assign palettes to the artwork.
The NES assigns palettes to sprites and backgrounds differently. It assigns sub-palettes for sprites on a tile-by-tile basis. That means that every 8×8 tile in a sprite can have one of the four sprite sub-palettes assigned to it.
This Ninja Character utilizes two sub-palettes to give it a greater color depth. On the right, you can see it split up into each individual 8×8 sprite tile. With this split view, it becomes more obvious that the light teal and darkest red that is used in the sword and headband are unique to those tiles, while the dark purple and black outline pieces are used in the remaining three tiles.
Backgrounds, on the other hand, are much more restrictive. Backgrounds assign their palettes in 16×16 chunks. The sub-palette assignments for an entire screen’s worth of background are referred to as Attribute Tables. These Attribute Tables are the reason why most retro artwork involves heavy use of repeating tiled segments. Those segments tend to be composed of 16×16 tiles so that they neatly fit into an Attribute Table. Despite being in response to a hardware restriction, this 16×16 tile-based approach to backgrounds ended up being a defining characteristic of retro artwork and is absolutely necessary when trying to recreate it.
This is an example of a nice RPG style town background made within the limitations. The image on the right shows how it’s neatly broken up into 16×16 px blocks, and palettes are selected per block. Things like roof tiles, grass, and the bricks on the bridge are composed of repeating segments of these blocks to save space. The roof tiles on the smaller buildings all use the same tiles, but assign different sub-palettes to them to give them all a unique look.
Even though artists are free to use different sub-palettes for each 8×8 tile of a sprite, they might find themselves in a situation where they want to have a greater color depth in a sprite than what is already available. This is where sprite layering can come in. Sprite layering is simply splitting a sprite up into two separate sprites and then placing them on top of each other.
This allows artists to circumvent the one sub-palette per 8×8 tile restriction. Doing this will essentially allow artists to double the number of colors that can be used in a single 8×8 area. The only major drawback of doing this is sprite rendering limits. The NES is only capable of displaying 64 8×8 sprite tiles on screen at once, and only 8 sprite tiles in the same horizontal line with one another. Once those numbers are reached, any further sprite tiles will not be rendered on screen.
This is why many NES games would flicker sprites when there was a lot of them on the screen at once. That way, it’s only displaying certain sprites on alternating frames. These limits are something artists need to be mindful of when they are layering sprites on top of each other because while it doubles the number of colors, it also doubles the number of sprite tiles on the same horizontal line.
This is an example of Sprite Layering in action. Starting from the left, is the original three-color version of the Ghost Pirate Sprite. From there, the artist split it up into two pieces, the body/hat and the face/hands, and assigned different palettes to them. Finally, you can see the result of layering the two pieces on top of each other.
Sprite layering can also be done with the background to get around the Attribute Table limits. This trick is generally used for static images, like story screens and character portraits, to give them a much greater color depth. In order to do this, the artist would draw part of the image as the background and then layer sprites on top of it to fill in the rest.
The Ghost Pirate’s portrait also uses sprite layering in order to give it a greater depth. His green skull is being rendered on screen as a sprite, while his collar and hat are being rendered as part of the background. This allows the artist to use more colors within a 16×16 area to totally circumvent the Attribute Table limitation.
To explain the next major restriction of the NES, first, we need to circle back to the fact that graphics are stored in tiles. Graphics tiles are stored in 256 tile pages and tiles from these pages cannot be loaded into VRAM in different locations, so it becomes difficult to mix and match tiles from different pages on the fly. The NES’ VRAM is only capable of displaying 512 of these tiles at once. Beyond just that restriction, it splits the tiles in half for sprites and background. That means it is only capable of displaying 256 sprite tiles and 256 background tiles at any given moment. This can become very restrictive if the artist wants to display a large variety of sprites and background elements.
This is a visual representation of the background and sprite tiles of a game that are loaded into VRAM. The console cleanly keeps backgrounds and sprites loaded on separate pages.
In order to combat this limitation, the NES has a feature that allows the artist to break each page up into partial pages called banks. So while the NES isn’t capable of loading individual tiles from various points in the graphics data, it is capable of loading different sections of a page at different times. For most games, these banks are either going to be 1K or 2K banks.
A 1K bank equals one-fourth of a page or 64 tiles, while a 2K bank is half of a page or 128 tiles. The artist must decide if they want to reserve the use of each type of bank for either Sprites or Background elements because both types of banks need to be utilized.
That means that you cannot have 1K banks for both the sprites and backgrounds. One page needs to use 1K banks and the other needs to use 2K. Generally speaking, most games tend to use 1K banks for the sprites and 2K banks for the backgrounds because background tilesets tend to be more static and need less in terms of on the fly variety.
This shows how the same image above has been broken into banks. The background pane on the left is using 2K banks, which means it is split in the middle, while the sprite pane on the right uses 1K banks. Each bank can be swapped freely on the fly.
The usefulness of 1K banks for sprites is pretty significant. If the player sprite has a large range of animations that will not fit in a single page along with all of the other sprites that need to be loaded, individual actions can be saved in 1K banks and then swapped between depending on what action is happening on screen.
It also allows for a larger variety of sprites that can be used in a single area of a game. For instance, if the player is to encounter six different kinds of enemies in an area of a game, but the sprite page only allows for the player and three other types of sprites, then when one enemy type is cleared off of the screen, the game can swap one of the enemy banks in for a new enemy type.
One of the only major drawbacks of using 1K banks for sprites and 2K banks for backgrounds is how the NES handles background animation. In order to animate a background element for a NES game, the artist has to create duplicate banks of the animated background elements.
Each new duplicate bank will contain the next frame of animation for each of the animated elements. These banks are then swapped in and out one at a time like a flip-book, in order to create the animation. If the artist is using half-page banks for the backgrounds, then storing all of those duplicate banks can take up a lot of space. One way to circumvent this though is to put all of the animated background elements for the entire game into a single bank. But, that also leaves the artist with the restriction of only having 128 tiles left over for the static elements for each background. It is up to the artist to decide the best course of action when deciding what kinds of banks they are going to use for the art.
Many games from that era will employ tricks to create effects like parallax scrolling in the background, but these too present the artists and designers with a challenge. While the later 16-bit consoles allowed for multiple background layers, this is not an option on the NES. All backgrounds are a single flattened image. In order to create a sense of depth and layering, different programming tricks were used.
In order to create a parallax background, for instance, the developer is able to set a register that can tell when a certain horizontal line (known as a raster line) is being rendered on the screen. They can then use that register to control the speed and direction that the screen is scrolling in. By using that, they can create a horizontal row of the background that scrolls at a different speed as the rest of the background. The trick for the artists and designers at this point is to be mindful that the background is still one flat image.
If a platform or any other element that is supposed to be “in front” of that slower moving background is placed in that region, then it too will scroll slower than the rest of the image. That means that designers need to be mindful of where they are placing background elements in the scene, and artists need to create the background in a way that the effect will be seamless.
In this example screen, the area that is highlighted in red could be set to scroll slower than the rest of the background in order to simulate depth. The Heads-Up Display above it will be set so that it never scrolls, even though it is also part of the flattened background image.
There’s also another trick for artists that want to have one of their background elements appear in the foreground. On the NES, developers are able to set a sprite’s priority to be less than zero. When this is done, it will cause the sprite to be displayed behind any non-transparent background pixels. Sprite priorities can be modified and triggered on the fly as well, allowing for certain elements to change a sprite’s priority as needed.
When someone is trying to create a project that is authentic to a retro console, there are many technical considerations that they need to keep in mind that might not be things that modern development has to worry about. Due to the way older machines would render images and handle having small amounts of room to manoeuvre with the CPU and GPU, the designers would have to think creatively to work around the hardware’s limitations.
In the modern age, it becomes important to learn about those limitations and the techniques, in order to truly recreate the look and design of games from that era. In the next post, we will look at the design limitations imposed by the 16-bit era as well as the Unity work needed to get that truly “old TV” feel.