Home / Features / 2D, text and UI
2D is not a separate framework bolted on the side. Sprites, shapes and text are controls in the same panel tree as 3D models, drawn in the Overlay phase of the same frame, and positioned in design resolution so a layout written once holds on a phone and on a 4K monitor.
Five types cover everything the engine draws in two dimensions. They all derive from
Control, which means they share the placement fields, the
Enable flag, the async Load
contract and the OnClick handler with every 3D control.
| Control | What it is for |
|---|---|
Sprite2D | Screen-space images, including live effect outputs addressed by texture name. |
Sprite3D | World-anchored billboards — markers, emotes, floating icons. |
Shape | Dots, rectangles, circles, rounded rectangles and frames for panel chrome. |
Texts | MSDF text: resolution independent, scaled by Scale, optionally auto-translated. |
Texture | The engine-side texture handle, for custom uploads and shared references. |
Sprites. SpriteBase carries what both sprite
types need: a Color tint, Ext for
the file extension, OriginWidth and
OriginHeight for the source pixel size, and
SourceX/Y/Width/Height for drawing one cell out of a sheet.
Clock advances a sprite-sheet frame,
FlipX and FlipY mirror it, and
TextureOverride takes a one-shot upload — a file path or
already-decoded RGBA8 pixels — which the next update consumes and clears.
Showing what compute wrote. Storage textures produced by compute effects are
registered under a compute:// name, and a
Sprite2D can be pointed straight at one. That is the whole
debug-view mechanism: no readback, no copy, no special control type.
// A normal image.
var badge = new Sprite2D { Name = "Assets/badge", Ext = ".png", PosX = 24, PosY = 24 };
// The ambient-occlusion buffer, on screen, as a sprite.
var aoView = new Sprite2D
{
Name = Season.Rendering.Effects.GtaoEffect.TextureName,
PosX = 20, PosY = 580, Width = 240
};
Volumes are the exception. Three-dimensional outputs use the
compute3d:// prefix and live in a separate backend
dictionary, so a sprite cannot display one directly — a slice kernel has to bridge it
down to 2D first, which is exactly what Sdf3DViewEffect is
for.
Billboards. Sprite3D lives in world space and
sorts with the transparent pass. Mode chooses how it faces the
camera: Spherical turns on both axes and always faces you,
Cylindrical turns only around world Y so a tree or a nameplate
stays upright, and None hands orientation entirely to the
quaternion Rotation.
Shapes. ShapeType is
Dot, Square,
Circle, RoundRect,
RectFrame, Gradual and
GradualCircle. Circles and rounded rectangles are analytically
antialiased rather than being scaled bitmaps, and
Border sets the frame thickness. It is a deliberately small set:
enough to build panel chrome without shipping a vector graphics engine.
There is one glyph pipeline, on every backend. Glyphs are rasterised into multi-channel
signed distance fields at a fixed PixelRange of 4 and packed
into the engine's own atlas, so text stays sharp at any scale and no per-size atlas is ever
rebuilt. Scale is a free parameter, not a quality setting.
title = new Texts
{
Content = "Settings",
Color = Colors.DarkRed,
Scale = Vector2.One * 1.2f
};
AddControl(title);
| Member | Notes |
|---|---|
Content | The string. Assigning it triggers layout; ContentOrigin keeps the untranslated original. |
Translate | On by default. Runs the string through Localization before layout. |
Scale | Vector2, so text can be stretched on one axis. Costs nothing in sharpness. |
LineHeight | Line advance in design units. 40 by default. |
WidthRequest, HeightRequest | Wrapping box. Leave them null and the text is a single line. |
WordsSpace, EmptySpace | Extra tracking, and the width of a space in Latin runs. |
TextsType | Immediately or FadeIn, for text that appears rather than pops. |
Color, Alpha | Per-control colour and opacity. |
Append(string) | Appends a fragment and lays out only the tail. See below. |
Inline colour. A small span markup is understood inside
Content, which is enough to highlight one word without splitting
a paragraph into three controls.
log.Content = "Build <span style='color:Red;font-weight:bold;'>failed</span> in 2.4s";
Streaming text. Append exists because token
streams exist. It lays out only the new tail against a checkpoint of the layout state at the
start of the current line, so an incremental append matches a full relayout pixel for pixel,
including whole-word lookahead. That is what makes a language model's output land in a
Texts control at token rate without rebuilding the paragraph
sixty times a second.
A colour scope can straddle a chunk boundary, and span indices are absolute within the
whole string, so the tail cannot be parsed on its own. Once span markup appears in
Content, appends fall back to full relayout until the
next whole-content assignment. Streaming text and inline colour are each supported;
combined, you pay for the colour.
Line breaking is script-aware: CJK breaks between characters, including the punctuation that
lives outside the main unified block and must not start a line, while Latin breaks on whole
words. Fonts are loaded with Font.CreateAsync(fileName, size)
and several can be resident at once, with glyph metrics cached per font size and code point.
Panel is the composition unit for both 2D and 3D. It holds a
list of child Panels and a list of
Controls, and it is where a screen, a HUD or a whole scene is
assembled. AddControl and
AddPanel define load order;
Layer and Order define drawing
order. OnClose fires when the panel goes away, and
SetMode lets a panel reconfigure itself without being torn
down.
| Ready-made panel | What you get |
|---|---|
BoardPanel | A framed board with a settable FrameColor. The background for most overlays. |
FrameButton | Text button with separate normal and hover colours for ground and text. |
ImageView | Thumbnail with an optional clear affordance; FullView in the same file is the expanded form. |
Input | Text field with description, alignment, abbreviation and an optional clear button. |
MovePanel | Scrolling and sliding container with configurable padding, size and motion type. |
Picker, SimplePicker | Single or multi-select lists over a List<EData>, with hover colours and OnSelect. |
ObjectPicker | Not a widget — the 3D pick and edit panel. See Picking and editing. |
Every control accepts an OnClick and an
OnTouch handler, and
MouseOver is maintained for you. There is no separate UI
event system, no bubbling model and no command pattern to learn: a button is a panel with a
delegate on it.
Draw order is explicit. Controls and panels that implement
IRenderOrder sort by
Layer, then Order, then
insertion index — a stable, total order with no z-fighting between overlay elements
and no surprises when a list is rebuilt.
RenderDomain decides when in the frame a control is
drawn rather than where it sits in the tree.
RenderDomain | Meaning |
|---|---|
Inherit | Follow the parent. The default, and almost always what you want. |
Scene | Draw with the 3D scene, before post-processing. Affected by tonemapping, TAA and the rest of the chain. |
Overlay | Draw in the Overlay pass, after everything. Untouched by post-processing, which is why UI stays crisp. |
Setting a 2D control to Scene is occasionally exactly right
— a diegetic screen inside the world should be bloomed and graded with everything
else. Setting a HUD to Scene is how you accidentally make
your text ghost under TAA.
You lay out against DesignResolution, 1280×720 by
default. What happens next depends on the platform, and the difference is worth knowing
because it is the one place the 2D model is not identical everywhere.
| Property | Desktop and web | Phones and tablets |
|---|---|---|
BasicResolution | Equals DeviceResolution. You are laying out in real pixels. | Equals DesignResolution. The design box is preserved. |
Scale | 1. The window is the canvas. | The smaller of the two axis ratios, so the whole design box stays visible. |
ExtendResolution | Equals DeviceResolution. | The design box grown along the longer axis, covering the extra space a tall phone has. |
DeviceResolution | Always the true swapchain size in pixels. | |
The practical rule: anchor anything that must stay reachable to
BasicResolution, and stretch backgrounds and edge decoration
to ExtendResolution. That gives you letterbox-free layouts on
a 20:9 phone without a second design.
Resizing recomputes Scale and
ExtendResolution only;
BasicResolution is deliberately left alone so a window drag
does not reflow a mobile layout. Orientation changes come through a separate event.
Input raises the native keyboard through
DeviceServices.Dialog.ShowKeyboard. There is no in-engine
caret, selection or IME composition, so text editing looks like the operating system
rather than like your game.