The AIR COMMAND front end is the hangar, the pilot profile, the controls page and the sign-in screen. All of it is drawn with Unity's immediate-mode GUI (IMGUI). It's fast to iterate on, needs no prefab wiring, and every page is plain code.
IMGUI has a well-known problem: it works in pixels. A layout designed at 1080p is tiny on a 4K monitor. The standard fix is one line:
GUI.matrix = Matrix4x4.Scale(Vector3.one * Screen.height / 1080f);
It works, and everything turns soft.
Why the matrix blurs text
GUI.matrix transforms geometry after it has been rasterised. Glyphs are rendered at the font size in your style, 17 px for example, and the resulting quads are then stretched by 2×. You're looking at 17 px text upscaled to 34 px. On a Retina or 4K panel, it looks like a screenshot of a menu.
Lay out in 1080p units, draw in pixels
Our fix is a small kit, Brand, that every page draws with. The rules:
Every size is in 1080p units. A page lays itself out on a canvas that is 1080 tall and as wide as the aspect ratio allows.
Brand converts to pixels when it draws. One scale factor, clamped for sanity:
public const float ReferenceHeight = 1080f; public static float S => Mathf.Clamp(Screen.height / ReferenceHeight, 0.5f, 3f);Fonts are asked for their real pixel size. A 17-unit label on a 2160 px screen requests a 34 px font, so glyphs are rasterised at the size they're displayed.
No page calls raw
GUIwith canvas rects. Pages useBrand.Label,Brand.Button,Brand.Filland friends, which do the conversion. GUILayout pages multiply spacing byBrand.S.
The result is the same layout at every resolution, with text that is sharp at every resolution.
Measuring text in the right space
One subtle consequence: style.CalcSize measures in pixels, because the style carries a pixel font size. Any measurement fed back into layout has to be divided by S:
return style.CalcSize(content).x / S;
Letter-spaced capitals, which the brand uses for eyebrows and buttons, add another wrinkle. CalcSize trims a lone space to nothing, so the width of one space is measured as the difference between two strings.
Two more IMGUI rules that saved us
- Draw overlapping controls front to back. IMGUI gives a click to the first control drawn under the cursor. A button on a clickable card must be drawn, and take its click, before the card's own hit test.
- Dialogs disable the page underneath.
GUI.enabled = falsewhile the page draws, thentruefor the dialog. Otherwise a click on the dialog's OK button also lands on whatever sits behind it.
IMGUI has a reputation as a debug-only tool. With a thin kit that owns units and fonts, it has carried a full branded front end, and it has never needed a single prefab.