UI
The @shell/ui package shows a HUD, a menu, or any other UI over a 2D game as entities and
components: a Canvas is the root of a tree of Elements, laid out with flexbox and scaled
with the screen. Engine2D installs it, and @shell/engine-2d exports its names.
import { Box, Canvas, Element, Sprite, Text, Tint } from '@shell/engine-2d';
const canvas = commands.spawn().root;canvas.addComponent(Canvas);canvas.addComponent(Box, { direction: 'column', padding: 24, gap: 12, align: 'start' });
const panel = commands.spawn().root;panel.addComponent(Element, { width: '360' });panel.addComponent(Box, { direction: 'column', padding: 16, gap: 8 });panel.addComponent(Sprite);panel.addComponent(Tint, { color: 0x334455 });panel.setParent(canvas);
const score = commands.spawn().root;score.addComponent(Element);score.addComponent(Text, { text: 'Score: 0', fontSize: 28 });score.setParent(panel);The layout places every Element, so an Element has no Position, Size, Rotation, or Scale.
What it shows is @shell/pixi’s own: Sprite with Tint for a coloured rectangle, with Texture
for a picture, with NineSlice to keep a picture’s corners, and Text for words. Words wrap at
the width of their Element.
Components
Section titled “Components”Canvas
Section titled “Canvas”The root of one tree of Elements. It fills the screen, whatever its own Element says. Requires
Element.
| Field | Type | Default | Means |
|---|---|---|---|
order | number | 0 | A Canvas of a higher order is drawn in front |
Element
Section titled “Element”Anything in a Canvas that the layout places. A length is a string, written as CSS writes it:
"120" (units of the reference resolution), "50%" (of the parent), "auto", or "" for not
set. A string that is none of these is told once in the console and counts as not set.
| Field | Type | Default | Means |
|---|---|---|---|
width, height | length | 'auto' | auto is as large as what it holds |
minWidth, minHeight, maxWidth, maxHeight | length | '' | Bounds |
position | string | 'flow' | flow in its parent’s row or column, absolute by its insets |
left, top, right, bottom | length | '' | Insets from the parent’s edges, when absolute |
grow | number | 0 | Its share of the room left over |
shrink | number | 1 | How readily it gives way |
margin | number | 0 | All four sides |
alignSelf | string | 'auto' | auto, start, center, end, stretch |
hidden | boolean | false | Not laid out and not drawn, with its children |
blocksPointer | boolean | false | A press on it does not reach the game’s world |
rotation | number | 0 | Radians, about its centre; children follow |
scaleX, scaleY | number | 1 | About its centre; children follow |
An Element that arranges its children in a row or a column. Requires Element. An Element without
Box arranges its children as a Box with every field at its default.
| Field | Type | Default | Values |
|---|---|---|---|
direction | string | 'row' | row, column |
justify | string | 'start' | start, center, end, space-between, space-around, space-evenly |
align | string | 'stretch' | start, center, end, stretch |
wrap | boolean | false | |
gap | number | 0 | |
padding | number | 0 | All four sides |
Controls
Section titled “Controls”A Control is one entity with one component, and is what the player operates. It draws itself as
rounded shapes in its colours, and is as large as its kind is until its Element says a size. Each
requires Element. An entity is one Control.
| Component | Fields | Natural size |
|---|---|---|
Button | action, label, labelSize, labelColor, color, disabled | 160 × 48 |
Toggle | checked, label, labelSize, labelColor, color, checkedColor, disabled | 160 × 28 |
Slider | value, min, max, step, color, fillColor, handleColor, disabled | 200 × 24 |
TextField | value, placeholder, maxLength, textSize, textColor, color, disabled | 240 × 40 |
ProgressBar | value (0 to 1), color, fillColor. Shown, and not operated by the player | 200 × 16 |
A press on a Button triggers the event Pressed, with the Button’s action and its entity, at
the start of the next tick:
import { Define, Pressed } from '@shell/engine-2d';
export const StartGame = Define.system() .observes(Pressed) .update(({ events }) => { if (events.get(Pressed)!.data.action === 'start') { // … } });What the player set on a Toggle, a Slider, or a TextField is written to its checked or
value at the start of the next tick; read it with a Changed query. A value a system writes is
shown, and is not told back to it.
A press that starts on a Control, or on an Element whose blocksPointer is on, is not a click in
the game’s world: @shell/ui claims it from the pointer resource of @shell/dom, so a system
that reads pointer.buttons needs no check. In an edit world a Control does not react.
ElementRect
Section titled “ElementRect”Where the layout put an Element: x, y, width, height in its Canvas, before rotation and
scale. Written by the layout, never saved, and added with Element.
Scaling
Section titled “Scaling”Every Canvas of a Project scales alike. The UI is laid out for the reference resolution, and on a screen of another size it is drawn larger or smaller by one scale:
scale = 2 ^ lerp(log2(screenWidth / referenceWidth), log2(screenHeight / referenceHeight), match)The values are a settings asset of the type UiScaling (ui:scaling):
| Field | Type | Default | Means |
|---|---|---|---|
referenceWidth | number | 1280 | The width the UI is laid out for |
referenceHeight | number | 720 | The height the UI is laid out for |
match | number | 0.5 | 0 follows the screen’s width, 1 its height |
Prefabs
Section titled “Prefabs”Registered in the prefabs resource, for the editor’s instantiate_prefab code:<key>:
| Key | Prefab | Components |
|---|---|---|
ui:canvas | CanvasPrefab | Canvas, Element, Box |
ui:box | BoxPrefab | Element, Box |
ui:image | ImagePrefab | Element, Sprite, Texture |
ui:text | TextPrefab | Element, Text |
ui:button | ButtonPrefab | Element, Button |
ui:progressBar | ProgressBarPrefab | Element, ProgressBar |
ui:slider | SliderPrefab | Element, Slider |
ui:toggle | TogglePrefab | Element, Toggle |
ui:textField | TextFieldPrefab | Element, TextField |
Resource and system
Section titled “Resource and system”resources.get('ui') is the Ui resource: scaleOf(width, height) gives the scale of a screen,
length(written) reads a length, and elementAt(clientX, clientY) gives the front-most Control or
blocking Element at a point of the page. UiControls and then UiLayout run in Schedule.Render,
after @shell/pixi has made the nodes and before it draws them, also in an edit world.
UiSignals runs in Schedule.First and hands over what the players of Controls did.
In an edit world a Canvas is of the reference resolution and its nodes are in the world, centred on
the origin or on the Canvas’s Position, so the editor’s scene view shows and picks them. In a
game the Position of a Canvas is ignored.
Limits
Section titled “Limits”- Nothing clips, so there is no scroll view, list, or dropdown.
- A Canvas is on the screen and not in the world.
- A Control is drawn in its colours and has no picture of its own yet.
- No keyboard or gamepad focus moves between Controls.