
In this article we will take a look at the solution of the pixel perfect font problem in Unity as I implemented it for my currently developed game SODHARA. Unity, given that it is a modern tool geared towards high res output, is not particularly friendly to the retro-inspired style of the yesteryear – the script presented in this article is one of the ways in which this hurdle can be overcome.
1 Overview
In SODHARA I use TextMeshPro
text components for everything text related. I use pixel perfect bitmap fonts in which all the glyphs’ sizes should be integer values, and the resolution is low enough so that each “pixel” (or, rather, a constituent square) of each glyph should correspond to a single pixel on screen.
The truth is however that these fonts are not perfect, and the problem is mostly with padding. This means that for the left (or right) aligned text the first glyph will probably be set properly but then the next one will note be an integer value pixels away, which will accumulate after a while and present rather unpleasant artifacts. Much bigger problem lies in centered text, since usually not even the first glyph is properly set in such a case.
This can be somewhat circumvented by carefully placing the text objects on screen so that, even though their position lies between pixels, the text itself is rendered properly. This only works if the resolution of the game is 1:1, i.e. if one pixel of the graphics corresponds to one pixel on screen. However, this approach yields diminishing results as one introduces features such as localization, template strings, and any process which tampers with the strings in runtime, thus making it impossible to place the objects properly in advance.
To get my point across, please consider images 1-4, whereas the odd ones are taken from the editor and the even ones are in-game screenshots.




Whenever a glyph pixel founds itself in a sub-pixel position it will be “rounded” to the closest integer value, which, in case of being around the center of the pixel, means it will appear in two places at once, yielding “chunky” and irregular glyphs (both in case of letters and spaces) all over the text. What’s worse, in case of dynamic text, the glyphs will change width and shape based on their (changing) position, which I find truly horrendous.
The solutions I found online usually boil down to either moving the entire text object to such a sub-pixel position that will “trick” the rounding process and make the text snap into place, or using custom scripts that snap the text to grid. As I stated previously, the first solution is problematic when one has to deal with varying text, i.e. when localization is involved, and it is near impossible to achieve with centered text. The second solution seemed more plausible, but none of the users that mentioned it shared the scripts they use. Thus, I decided to write my own.
2 Pixel Perfect Text Enforcer
Retro-styled games usually use a small number fonts – SODHARA in particular uses three, so the first step was to describe all the fonts used in order to have relevant info on each of them in one place. The class I used is shown in listing 1.
Listing 1. The CharSpacingInfo
class keeps tracks of the various font measurements.
The fields of the CharSpacingInfo
class represent the following font qualities:
pixelSize
: size of the “font pixel”, or the “constituent square” as I dubbed it previously,fontSize
: size of the font as set in theTextMeshPro
script within the inspector,fixedCharWidth
: width of the glyphs in monospaced fonts (set to-1
in case the font is not monospaced),interCharSpaceWidth
: width of the space between individual glyphs, and finallywhitespaceWidth
: width of the whitespace, or spacebar character.
The data given for each of the three fonts are used to force the glyphs to snap to pixel grid by means of wrapping the chars and spaces with <mspace={size}px>...</mspace>
tags, and also followed up by <space={size}px>
tag when a non-whitespace character is followed by another non-whitespace char. The size
are taken from the CharSpacingInfo
instances, namely the _fixedCharWidth
and _interCharSpaceWidth
fields.
The actual wrapping is done by the methods shown in listing 2.
Listing 2. The WrapStringInTags
and WrapCharInTags
methods of the PixelPerfectTextEnforcerStatic
class.
The WrapStringInTags
method handles the string processing obviously. Each character (alongside the following one) is fed into the WrapCharInTags
method which then wraps the input character into the required mspace
tags and adds the space
tag if necessary. Since the character glyphs themselves are pixel perfect, in this way we are only forcing the proper spacing between the characters and the proper whitespace character width as well.
There is however another case one should account for, and that is the situation in which the string of pixel perfect text is supposed to be centered, but is of odd length in pixels – such a string can never snap perfectly to the pixel grid since all of its pixels will smack in the middle of the grid fields the entire time. For that purpose the methods shown in listing 3 are employed to adjust the TextMeshPro
object position in such a way that pixel perfect text is assured even in such a case.
Listing 3. The GetStringPixelWidth
and GetPosXAdjustment
methods of the PixelPerfectTextEnforcerStatic
class.
The result is responsive pixel perfect text that is suitable for all alignment settings, and for both static and dynamic text alike. How it looks like in the end can be viewed in image 5.
Image 5. Pixel perfect text enforcer.
There is a short interval which the text needs to snap into place. This float value is exposed by the script and can be adjusted in the inspector. One more important thing to note is that the TextMeshPro
object’s RectTransform
component has to be set to integer coordinates for the PixelPerfectTextEnforcer
script to work properly.
The entire script may be viewed on GitHub and freely used. It is tested and works well with all kinds of text – static, dynamic, localized or not.
3 Conclusion
Nasty artifacts can really spoil the illusion of playing a game released a few decades ago, which breaks the immersion and ruins the style which the developer worked so hard to achieve. This simple script handles the TextMeshPro
font problems neatly and effectively, and lets developers concentrate on things more important then nudging the text objects by sub-pixel values around.