Sample of coding a pixel perfect 2D game in Unreal Engine 5 with Paper2D.
The sole purpose of this sample is to compile all the config required, and common pitfalls to avoid, to correctly render pixel perfect sprites with Paper2D.
For an Unreal Engine 4 version, check the branch ue4.25.
Go further:
- Importing textures
- Scaling up sprites
- True sprite colors
- Pixelated font
- Snap pixels to grid
- Orthographic camera
When importing new textures, check that the Texture Group option is correctly set to 2D Pixels (unfiltered), and that the Compression Settings is set to UserInterface2D (RGBA). This will prevent the textures from being antialiased:
Also, expand the Advanced section, and set the Filter option to Nearest:
Here is the charset used for Mario:
As you can see, the size of one frame is really small (16x32 pixels). If it were displayed as is in UE5, then all your physics would have to be configured with unrealistic values and placing or moving Actors in the editor would become complicated due to their small size.
In order to keep consistent units between the physics and the rendering, all sprites are configured with a pixels per unit value of 0.5, which effectively make them scaled by two:
This is how the level looks up in editor with this configuration. You can see it is neither too small nor too big:
One common problem is that rendered colors are altered by UE5's post processing effects. By default, many post processing effects are enabled and are causing sprites not to render correctly. In this sample, all post processing effects are disabled by setting EngineShowFlags.PostProcessing to 0 with a custom UGameViewportClient class:
void USampleGameViewportClient::Activated(FViewport* InViewport, const FWindowActivateEvent& InActivateEvent)
{
Super::Activated(InViewport, InActivateEvent);
// Disable post processing effects when the viewport is activated
EngineShowFlags.PostProcessing = 0;
}
The custom UGameViewportClient class can be configured in Project Settings > Engine > General Settings:
Another possibility is to execute the following console command, however it turns out that it doesn't work in shipping builds where console commands are disabled:
APlayerController* Controller = UGameplayStatics::GetPlayerController(GetWorld(), 0);
check(Controller);
Controller->ConsoleCommand(TEXT("showflag.postprocessing 0"));
Also, in Project Settings > Engine > Rendering, make sure to uncheck the Mobile HDR option and all post processing options such as Bloom, Auto Exposure, Anti-Aliasing, etc.
Importing a custom font to use in a TextRendererComponent seems complicated. This requires the Font Cache Type parameter of your font to be set to Offline. Next, make sure to uncheck the Use Distance Field Alpha option to prevent the font from becoming blurry:
Also make sure to uncheck Enable Antialiasing to prevent any antialiasing:
The last step is to create a new material to render the font. This can be done by copying the default material used by any TextRendererComponent and modifying it:
In retro games, all sprites were snapped to a pixel grid, ensuring each pixel fitted in the grid and was correctly rendered. In modern games, your Actors may be at positions containing float values, causing pixels to fall off the grid and render incorrectly. Here is an example of moving the same sprite without pixel snapping (left) and with pixel snapping (right):
To do that, you can copy the default material used for sprites and modify it to offset vertex positions to make sure they fall correctly on the grid:
You can do the same for the font material, but by replacing the PixelsPerUnit parameter by 1.
This sample uses a static orthographic camera positioned at the center of the screen. The expected resolution of our viewport is 512x448 pixels, the double of the SNES screen resolution, that's why the OrthoWidth parameter must be set to 512 pixels and the Aspect Ratio parameter to 8/7:
- Added a step to set the Filter option to Nearest on sprites. Without that, the game build was all blurry despite the game being crisp in PIE.
- Replaced ConsoleCommand by creating a custom UGameViewportClient class to disable the flag as console commands are disabled in shipping builds.
Sprites are coming from The Spriters Resource.
Font from FontSpace.
Pixel snap trick Ludicrous Games
Licensed under the MIT License.