@origingame/engine 0.0.0-stage → 0.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/og.mjs +25 -0
- package/dist/web/origin-engine.d.ts +1233 -0
- package/dist/web/origin-engine.js +8628 -0
- package/dist/web/origin-engine_bg.wasm +0 -0
- package/dist/web/origin-engine_bg.wasm.d.ts +366 -0
- package/dist/web-publish/origin-engine-lanes.js +8509 -0
- package/dist/web-publish/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish/origin-engine.d.ts +1176 -0
- package/dist/web-publish/origin-engine.js +8313 -0
- package/dist/web-publish/origin-engine_bg.wasm +0 -0
- package/dist/web-publish/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/dist/web-publish-2d/origin-engine-lanes.js +8509 -0
- package/dist/web-publish-2d/origin-engine-lanes_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine.d.ts +1176 -0
- package/dist/web-publish-2d/origin-engine.js +8313 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm +0 -0
- package/dist/web-publish-2d/origin-engine_bg.wasm.d.ts +347 -0
- package/dist/web-publish-2d/snippets/wasm-bindgen-rayon-38edf6e439f6d70d/src/workerHelpers.no-bundler.js +77 -0
- package/knowledge/README.md +31 -0
- package/knowledge/knowledge.json +13 -0
- package/knowledge/looks/catalogue.json +1871 -0
- package/knowledge/package.json +33 -0
- package/knowledge/rules/platform.json +329 -0
- package/knowledge/scaffold/README.md +27 -0
- package/knowledge/scaffold/distribution.json +48 -0
- package/knowledge/scaffold/origin-engine.json +19 -0
- package/knowledge/scaffold/template/.origin/setup +14 -0
- package/knowledge/scaffold/template/LICENSES.md +23 -0
- package/knowledge/scaffold/template/README.md +37 -0
- package/knowledge/scaffold/template/assets/ai/guard.ogt +48 -0
- package/knowledge/scaffold/template/assets/audio/click.ogt +9 -0
- package/knowledge/scaffold/template/assets/fonts/NotoSansSC-Regular-subset.ttf.ogt +12 -0
- package/knowledge/scaffold/template/assets/fonts/OFL.txt +93 -0
- package/knowledge/scaffold/template/assets/looks/origin-game.ogt +12 -0
- package/knowledge/scaffold/template/assets/nav/yard.ogt +27 -0
- package/knowledge/scaffold/template/assets/scenes/main.ogt +34 -0
- package/knowledge/scaffold/template/assets/strings/en.ogt +14 -0
- package/knowledge/scaffold/template/assets/textures/stone_n.png.ogt +7 -0
- package/knowledge/scaffold/template/gitattributes +22 -0
- package/knowledge/scaffold/template/gitignore +8 -0
- package/knowledge/scaffold/template/package.json +18 -0
- package/knowledge/scaffold/template/project.ogt +25 -0
- package/knowledge/scaffold/template/src/main.ts +246 -0
- package/knowledge/scaffold/template/tests/guard.og.ts +67 -0
- package/knowledge/scaffold/template/tests/smoke.og.ts +43 -0
- package/knowledge/scaffold/template/tsconfig.json +17 -0
- package/knowledge/scripts/lib.ts +207 -0
- package/knowledge/scripts/mount.ts +210 -0
- package/knowledge/scripts/pack.ts +25 -0
- package/knowledge/scripts/scaffold.ts +72 -0
- package/knowledge/scripts/validate.ts +749 -0
- package/knowledge/skills/og-ai/SKILL.md +386 -0
- package/knowledge/skills/og-audio/SKILL.md +191 -0
- package/knowledge/skills/og-build/SKILL.md +330 -0
- package/knowledge/skills/og-core/SKILL.md +285 -0
- package/knowledge/skills/og-feel/SKILL.md +385 -0
- package/knowledge/skills/og-game/SKILL.md +1479 -0
- package/knowledge/skills/og-i18n/SKILL.md +220 -0
- package/knowledge/skills/og-platform-sdk/SKILL.md +199 -0
- package/knowledge/skills/og-taste/SKILL.md +537 -0
- package/knowledge/skills/og-ui/SKILL.md +444 -0
- package/knowledge/wiki/ai.md +627 -0
- package/knowledge/wiki/anim.md +1187 -0
- package/knowledge/wiki/atmosphere.md +636 -0
- package/knowledge/wiki/audio.md +732 -0
- package/knowledge/wiki/camera.md +711 -0
- package/knowledge/wiki/commands.md +357 -0
- package/knowledge/wiki/game.md +220 -0
- package/knowledge/wiki/i18n.md +213 -0
- package/knowledge/wiki/index.md +1027 -0
- package/knowledge/wiki/input.md +1714 -0
- package/knowledge/wiki/load.md +1280 -0
- package/knowledge/wiki/math.md +166 -0
- package/knowledge/wiki/nav.md +1154 -0
- package/knowledge/wiki/physics.md +3142 -0
- package/knowledge/wiki/platform.md +857 -0
- package/knowledge/wiki/probe.md +1692 -0
- package/knowledge/wiki/reflect.md +285 -0
- package/knowledge/wiki/render.md +3511 -0
- package/knowledge/wiki/rng.md +393 -0
- package/knowledge/wiki/stream.md +376 -0
- package/knowledge/wiki/system.md +197 -0
- package/knowledge/wiki/systems.md +43 -0
- package/knowledge/wiki/terrain.md +727 -0
- package/knowledge/wiki/tilemap.md +422 -0
- package/knowledge/wiki/time.md +150 -0
- package/knowledge/wiki/transform.md +230 -0
- package/knowledge/wiki/tween.md +580 -0
- package/knowledge/wiki/ui.md +8000 -0
- package/knowledge/wiki/world.md +909 -0
- package/package.json +68 -4
- package/schema/api.schema.json +29675 -0
- package/shell/README.md +22 -0
- package/shell/brand/README.md +23 -0
- package/shell/brand/icon-192.png +0 -0
- package/shell/brand/icon-512.png +0 -0
- package/shell/brand/mark-on-paper.svg +6 -0
- package/shell/brand/origin-mark.svg +5 -0
- package/shell/brand/origin-motion.css +251 -0
- package/shell/brand/tokens.css +20 -0
- package/shell/brand.ts +132 -0
- package/shell/page.ts +715 -0
- package/shell/platform.ts +203 -0
- package/shell/player.ts +238 -0
- package/shell/shell.css +185 -0
- package/shell/sw.ts +177 -0
- package/shell/transport.ts +515 -0
- package/shell/wire.ts +369 -0
- package/src/e6.ts +69 -0
- package/src/env.d.ts +100 -0
- package/src/generated/abi.ts +980 -0
- package/src/index.ts +193 -0
- package/src/node/audio.ts +86 -0
- package/src/node/build.ts +109 -0
- package/src/node/device.ts +981 -0
- package/src/node/doctor.ts +60 -0
- package/src/node/index.ts +102 -0
- package/src/node/platform.ts +24 -0
- package/src/node/playtest-run.ts +249 -0
- package/src/node/png.ts +130 -0
- package/src/node/polish.ts +976 -0
- package/src/node/run.ts +373 -0
- package/src/node/web-build.ts +808 -0
- package/src/p2.ts +35 -0
- package/src/platform-local.ts +211 -0
- package/src/playtest.ts +131 -0
- package/src/runtime.ts +1509 -0
- package/src/systems/cameras.ts +247 -0
- package/src/ui-kit/bind.ts +141 -0
- package/src/ui-kit/huds.ts +303 -0
- package/src/ui-kit/index.ts +36 -0
- package/src/ui-kit/menus.ts +187 -0
- package/src/ui-kit/settings.ts +131 -0
- package/src/ui-kit/store.ts +222 -0
- package/src/ui-kit/story.ts +89 -0
- package/src/ui-kit/util.ts +106 -0
- package/src/web/a11y.ts +429 -0
- package/src/web/audio-worklet.ts +193 -0
- package/src/web/audio.ts +307 -0
- package/src/web/basis.ts +239 -0
- package/src/web/gamepad.ts +123 -0
- package/src/web/host.ts +166 -0
- package/src/web/input.ts +237 -0
- package/src/web/lanes.ts +21 -0
- package/src/web/play.ts +20 -0
- package/src/web/rpc-ring.ts +110 -0
- package/src/web/settle.ts +122 -0
- package/src/web/shell.ts +643 -0
- package/src/web/text-input.ts +248 -0
- package/src/web/worker.ts +1002 -0
- package/types/bindings.json +13654 -0
- package/types/index.d.ts +20090 -0
- package/vendor/basis/LICENSE +201 -0
- package/vendor/basis/NOTICE +15 -0
- package/vendor/basis/README.md +18 -0
- package/vendor/basis/basis_transcoder.js +19 -0
- package/vendor/basis/basis_transcoder.wasm +0 -0
- package/README.md +0 -3
|
@@ -0,0 +1,444 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: og-ui
|
|
3
|
+
description: Game screens and HUDs on Origin Engine with og.ui — which screen template to pick (title, pause, settings, five HUDs, cutscene, shop, inventory, level select, results, leaderboard, tutorial, loading, dialogs, toasts), how to feed it data and read what it answers, the widget set beneath, focus for keyboard and gamepad, accessibility (the screen-reader mirror), phones, touch gestures and the safe area, text (fields, text areas, selection and the clipboard, input methods, ellipsis, kerning, shaping, font fallback), and the look: skins (style packs) and how their frames tile, count-ups, bursts, shakes, screen transitions and UI sounds. Use when building or changing menus, HUDs or any UI in a project with a project.ogt.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
`og.ui` is the engine's immediate-mode UI: a system describes the tree every
|
|
7
|
+
tick, the engine lays it out, draws it in the platform's design tokens and
|
|
8
|
+
answers the probe (`dump_ui`, `touch_move_to`, `move_to_text`). The API
|
|
9
|
+
reference is `reference/ui.md`; this skill is how to use it well.
|
|
10
|
+
|
|
11
|
+
## What works
|
|
12
|
+
|
|
13
|
+
Two games written from this package alone (`examples/lantern-vale`,
|
|
14
|
+
`examples/hollowbell`) used the main menu, pause, cutscene, shop and
|
|
15
|
+
results templates (the results with a localised Best and New best) and a
|
|
16
|
+
text field with input-method composition, and none needed wiring beyond
|
|
17
|
+
its desc; a skin pack copied into the game restyled every screen, and
|
|
18
|
+
`og polish` measured taste 99 for both. The Kestrel Stair
|
|
19
|
+
(`examples/kestrel-stair`) used the cutscene, tutorial overlay,
|
|
20
|
+
inventory with drag and drop, level select, leaderboard with its honest
|
|
21
|
+
states, settings, results and pause: each worked from its desc, the paper pack
|
|
22
|
+
restyled them all, and every word they draw came from its string tables.
|
|
23
|
+
|
|
24
|
+
## 1. The rule of the kit
|
|
25
|
+
|
|
26
|
+
Every template and widget is **one call per tick, data in, answer out**:
|
|
27
|
+
|
|
28
|
+
```ts
|
|
29
|
+
const r = og.ui.shopScreen("Shop", { balance: run.coins, items: SHOP, categories: ["Gear", "Items"] });
|
|
30
|
+
if (r.bought !== null) { /* take the price, grant the item */ }
|
|
31
|
+
if (r.closed) state.screen = "map";
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
- Keep game state (open flags, the current line, the settings, slots) in a
|
|
35
|
+
component or resource, pass it in every tick, store what comes back. Never
|
|
36
|
+
keep UI state in module variables. A field's or an area's text is game
|
|
37
|
+
state too: keep it in a `str(capacity)` field (og-core §4), four bytes per
|
|
38
|
+
character of the widget's `maxChars` (`name: str(64)` for `maxChars: 16`),
|
|
39
|
+
so the text always fits.
|
|
40
|
+
- What only the UI needs between ticks (hover, focus, scroll, a menu being
|
|
41
|
+
open, the chosen tab of a template, animation progress, toasts) is UI state:
|
|
42
|
+
snapshots and replays carry it. A template that needs a number of its own
|
|
43
|
+
uses `og.ui.memory(id, initial)` / `og.ui.remember(id, value)`.
|
|
44
|
+
- Call UI only inside a system, and read and write the state you pass it
|
|
45
|
+
through the archetype's column views (og-core §4;
|
|
46
|
+
`templates/ui-kit/src/main.ts` shows the pattern).
|
|
47
|
+
|
|
48
|
+
## 2. Pick a template
|
|
49
|
+
|
|
50
|
+
Screenshots of every template at desktop (1280×720) and on a phone in portrait
|
|
51
|
+
(390×844 at 3×) are in the engine repository under `templates/ui-kit/screens/`;
|
|
52
|
+
the full-size reference frames are `crates/og-ui/references/metal/kit-*.png`,
|
|
53
|
+
and every style pack's screens on a phone `kit-skin-<pack>-phone.png`.
|
|
54
|
+
|
|
55
|
+
| The game needs | Call | It answers |
|
|
56
|
+
|---|---|---|
|
|
57
|
+
| A title screen | `og.ui.mainMenu(id, { title, subtitle, items, footer, instant })` (`instant` while it is the screen the game opens on: §6) | `selected`: the item id |
|
|
58
|
+
| Pause | `og.ui.pauseMenu(id, { open, items })` (Resume, Settings, Quit by default) | `selected`; Back answers `resume` |
|
|
59
|
+
| Settings | `og.ui.settingsScreen(id, { open, settings, languages, onChange, onSave })` | the new `GameSettings`, `changed`, `closed` |
|
|
60
|
+
| A platformer HUD | `og.ui.platformerHud(id, { lives, maxLives, coins, score, level, timeLeft, touch })` (what it is not given it does not draw: `maxLives: 0` no hearts, no `coins` or `score` no capsule for them) | `paused`, `moveX`, `action: "jump"` |
|
|
61
|
+
| A shooter HUD | `og.ui.shooterHud(id, { health, armor, ammo, reserve, abilities, map, touch })` | `paused`, `action` (ability id, `fire`), stick |
|
|
62
|
+
| A puzzle HUD | `og.ui.puzzleHud(id, { level, score, target, stars, moves, timeLeft })` | `paused` |
|
|
63
|
+
| An idle or incremental HUD | `og.ui.idleHud(id, { currencies, generators, tapLabel })` | `action: "tap"` or `"buy:<id>"` |
|
|
64
|
+
| A racing HUD | `og.ui.racingHud(id, { speed, gear, lap, laps, position, racers, time, delta, best, splits, boost, map, touch })` (a time trial: `racers: 0` hides the place, `boostMeter: false` the meter, `delta` and `splits: [{ label, time, delta }]` show its sectors; the words `lapLabel`, `bestLabel`, `pauseLabel`, `boostLabel`, `brakeLabel`, `leftLabel`, `rightLabel`, `unit` from the string table, og-i18n §2) | `paused`, steering, `held` |
|
|
65
|
+
| Dialogue or a cutscene | `og.ui.cutscene(id, { open, lines, index })` | the next `index`, `finished`, `skipped` |
|
|
66
|
+
| A shop | `og.ui.shopScreen(id, { balance, currency, items, categories })` | `bought`, `refused` (Buy pressed short of the price: it shook with the error sound), `closed` |
|
|
67
|
+
| An inventory | `og.ui.inventoryScreen(id, { slots, items })` | `selected`, `moved` (drag and drop), `used`, `closed` |
|
|
68
|
+
| Level select | `og.ui.levelSelect(id, { levels, backLabel, starsLabel, lockedLabel })` (a card is named for a screen reader by its level and `starsLabel`, "2 of 3 stars", a locked one by `lockedLabel`, "3, locked") | `chosen`; `closed` from its Back button, a tap on the scrim, Escape or B |
|
|
69
|
+
| Results | `og.ui.resultsScreen(id, { open, score, best, newBest, stars, stats, shareText, onShare, scoreLabel, bestLabel, newBestLabel })` (the labels from the string table, og-i18n §2); a time trial passes `format: "time"` with the score and best in seconds: the headline counts up as m:ss.mmm (1:36.233) and the lower time is the best | `selected` (`retry`, `next`, `menu`, `share`) |
|
|
70
|
+
| A leaderboard | `og.ui.leaderboardScreen(id, { scopes, entries, loading, error })` | `scope`, `changed`, `retry`, `closed` |
|
|
71
|
+
| Onboarding | `og.ui.tutorialOverlay(id, { open, steps, index })` (each step names an element id) | the next `index`, `finished`, `skipped` |
|
|
72
|
+
| Loading | `og.ui.loadingScreen(id, { title, progress, tips })` | nothing |
|
|
73
|
+
| A yes/no question | `og.ui.confirmDialog(id, { open, title, message, confirmLabel, danger })` | `confirmed`, `cancelled` |
|
|
74
|
+
| A notice | `og.ui.alertDialog(id, { open, title, message })` | true when dismissed |
|
|
75
|
+
| A short message | `og.ui.toast(world, text, { tone })` (anywhere: systems, commands) | its id |
|
|
76
|
+
|
|
77
|
+
Compose them: a HUD with `pauseMenu` over it, `settingsScreen` opened from
|
|
78
|
+
pause, `confirmDialog` over settings. Dialogs stack and the topmost owns focus.
|
|
79
|
+
|
|
80
|
+
Honest data: `loadingScreen` shows a spinner when progress is unknown, never a
|
|
81
|
+
bar that runs ahead; `leaderboardScreen` shows skeletons while loading, the
|
|
82
|
+
error with Retry, and its empty text; never invent scores or counts.
|
|
83
|
+
|
|
84
|
+
## 3. Below the templates: the widget set
|
|
85
|
+
|
|
86
|
+
Layout (`row`, `column`, `panel`, `scroll`, `anchor`), text and controls
|
|
87
|
+
(`label`, `button`, `toggle`, `slider`, `textField`, `textArea`, `checkbox`,
|
|
88
|
+
`checkboxList`, `radio`, `tabs`, `select`, `stepper`, `chip`, `badge`,
|
|
89
|
+
`keyValue`, `divider`, `spacer`, `icon`, `image`, `progress`, `skeleton`,
|
|
90
|
+
`spinner`), HUD parts (`meter` for health, mana and xp with a trail that
|
|
91
|
+
follows a drop; `cooldown` rings; `minimap`; `dialogue` with typed text),
|
|
92
|
+
touch (`joystick`, `dpad`, `actionButton`, each at least 44 CSS px), lists
|
|
93
|
+
(`list` and `grid`, virtualised: only rows in view are built; items report
|
|
94
|
+
drag and drop; `scroll` with `axes: "horizontal"` or `"both"` for a strip
|
|
95
|
+
of cards or a map, which hands the wheel and drags it cannot use to the
|
|
96
|
+
scroller around it, keeps a flick's momentum and has grabbable
|
|
97
|
+
scrollbars; two fingers on a scroller scroll it on a phone, where one
|
|
98
|
+
finger would press a row), `zoomView` for a map or an artwork two fingers
|
|
99
|
+
pinch and pan (the wheel zooms it on a desktop), `aspect` on `image` (and
|
|
100
|
+
any element) to keep a picture's proportions in a growing column,
|
|
101
|
+
layers (`anchor` for HUD corners, `modal` for dialogs,
|
|
102
|
+
`transition` for fade, slide and scale by the tick, `spotlight`) and
|
|
103
|
+
`toast`. Ids are stable strings: give every control a meaningful id so a
|
|
104
|
+
playtest can `touch_move_to` it (`Shop/Panel/Detail/Buy` resolves as
|
|
105
|
+
`Detail/Buy`).
|
|
106
|
+
|
|
107
|
+
Mixed styles in one paragraph are `richText(id, markup)`: `[b]`,
|
|
108
|
+
`[color=danger]` (a token, or `#rrggbb`), `[size=lg]`, `[font=mono]`,
|
|
109
|
+
`[icon=coin]`, `[link=shop]…[/link]` and the per-glyph `[wave]`, `[shake]`,
|
|
110
|
+
`[pulse]`. Write the markup in the string table, not in code, so each
|
|
111
|
+
translation places its own emphasis (`confirmDialog` and `alertDialog`
|
|
112
|
+
messages are markup). A link is a child element (`Hint/shop`, kind `link`)
|
|
113
|
+
that keys, pads and touch reach like a button; the result's `link` names
|
|
114
|
+
the one clicked. A typo in a tag stays visible as text and warns.
|
|
115
|
+
|
|
116
|
+
`textField` and `textArea` edit as a player's system does, with nothing to
|
|
117
|
+
wire. On a Mac (`Viewport.macKeys`: the web shell sets it from the
|
|
118
|
+
browser's platform, a native host on macOS) Cmd+A, C, X, Z are the
|
|
119
|
+
shortcuts, Cmd+←/→ go to the line's ends, Option+←/→ (and
|
|
120
|
+
Option+Backspace or Delete) move by words, and Ctrl takes the emacs keys
|
|
121
|
+
(A/E the line's ends, F/B a character, N/P a line, D/H delete one, K to
|
|
122
|
+
the paragraph's end, U to its start, W the word before); elsewhere Ctrl
|
|
123
|
+
takes the shortcuts and moves by words. Words are Unicode's (UAX #29):
|
|
124
|
+
`can't` is one, each Chinese character is one, Thai moves a cluster at a
|
|
125
|
+
time. The caret and Backspace step over a whole grapheme cluster (`é`
|
|
126
|
+
typed as e and an accent, an emoji with its skin tone), and ←/→ move as
|
|
127
|
+
they point: in Hebrew or Arabic ← goes on in the text. A double click selects a word and a triple
|
|
128
|
+
click the line, Shift+Z or Y redoes (typing undoes a word at a time, an
|
|
129
|
+
input method's commit at a time), and a drag past an area's edge scrolls
|
|
130
|
+
it. A text the game sets starts the field's undo history again. Script a
|
|
131
|
+
chord with the modifier before the key: `key_down:Meta+ArrowLeft`
|
|
132
|
+
(Command), `key_down:Ctrl+Shift+KeyZ`. A native run on macOS takes the
|
|
133
|
+
Mac's keys, so a script that holds Ctrl or Meta and must edit the same on
|
|
134
|
+
every OS pins them with `og.ui.previewViewport` (`macKeys`).
|
|
135
|
+
|
|
136
|
+
Scripts: a label mixing scripts (`"Level 3 · 古い水車小屋"`) needs no font
|
|
137
|
+
of its own; which face draws which part is one rule, §7 (Fonts).
|
|
138
|
+
|
|
139
|
+
Icons: an icon set cooked from SVGs (an `.ogt` of `type icons` naming
|
|
140
|
+
them; the UI kit ships 72 iconoir icons as `icons/iconoir.ogt`, also under
|
|
141
|
+
its own names `coin`, `gear`, `fire`…, and every kit screen draws them) loads with
|
|
142
|
+
`og.load.font` and becomes the atlas with `og.ui.configure(world, { icons:
|
|
143
|
+
{ font } })`; `og.ui.icon(id, "heart", { size, tint })` and `[icon=heart]`
|
|
144
|
+
then draw crisp distance-field glyphs at any size in their ink. A texture
|
|
145
|
+
grid (`icons: { texture, columns, rows, names }`) still works for painted
|
|
146
|
+
icons. Icons sit in `fgMuted` or `fgSubtle` (design language: icons are
|
|
147
|
+
neutral). Sets name their icons differently (iconoir's `coins` and
|
|
148
|
+
`fire-flame`, the skin grids' `coin` and `fire`): an unknown name draws an
|
|
149
|
+
empty slot and warns, so a template that may meet either asks
|
|
150
|
+
`og.ui.hasIcon(name)` first and draws its fallback, or names an icon twice
|
|
151
|
+
in its set (`icon "coin" "coins"`). A path's `stroke-linejoin` is kept
|
|
152
|
+
(a miter stays sharp); a set whose SVGs leave it out says `join round` (or
|
|
153
|
+
`miter`, `bevel`) once.
|
|
154
|
+
|
|
155
|
+
## 4. Focus: keyboard and gamepad
|
|
156
|
+
|
|
157
|
+
Every element carries an accessibility role, name, value and states
|
|
158
|
+
(`dump_ui`'s `role`, `name`, `value`, `states`; `og.ui.a11y()` answers the
|
|
159
|
+
tree with its focus order). Give every control visible text or a meaningful
|
|
160
|
+
id: an icon-only button is named by its icon, a card by the text inside it,
|
|
161
|
+
else by its id, and a playtest can require that no `focusable` element has
|
|
162
|
+
an empty name (ui-kit's `tests/a11y.og.ts`). Tab walks the tree's order.
|
|
163
|
+
On the web the shell mirrors this tree into the page for screen readers
|
|
164
|
+
(invisible ARIA elements over the canvas, toasts announced, the reader's
|
|
165
|
+
clicks pressing the control), so the names you give are what a blind
|
|
166
|
+
player hears. The page's `prefers-reduced-motion` and `prefers-contrast:
|
|
167
|
+
more` turn on the reduced-motion and high-contrast themes on top of the
|
|
168
|
+
game's own settings: nothing to wire. A stepper is adjusted as a whole
|
|
169
|
+
(VoiceOver's swipe up or down on it steps it), like a slider, and both
|
|
170
|
+
tell the reader their bounds (a node's `valueMin`, `valueMax`, `valueNow`:
|
|
171
|
+
a slider's `min` and `max`, a stepper's when it has them).
|
|
172
|
+
|
|
173
|
+
There are no role fields to set: a node's `role` comes from the
|
|
174
|
+
widget (button, link, text, heading, image, checkbox, slider, textbox,
|
|
175
|
+
progressbar, dialog, status…; layout containers have none) and its `name`
|
|
176
|
+
from its text, then the text inside it, then the label just before it in
|
|
177
|
+
its row, then its id. A group a player never reads as text names itself
|
|
178
|
+
from its desc's `label` (`tabs`, `radio`, `select`: "Apartado", not the id
|
|
179
|
+
`Section`), and an icon or image from its `label`: an icon left without one
|
|
180
|
+
is named by its atlas name, an English word (`star`), so a card of a name
|
|
181
|
+
and three stars reads "1. La escalera star star star" in every language.
|
|
182
|
+
Name the meaningful one in the game's words and leave the rest `""`
|
|
183
|
+
(decoration): `og.ui.icon("Star#0", "star", { label: og.i18n.t(world,
|
|
184
|
+
"stars", { n: 2 }) })`, then `{ label: "" }` for the others. A text field is named by that label (put the label
|
|
185
|
+
and the field in one `row`), else by its placeholder, and the placeholder
|
|
186
|
+
is its `description`: `"Lamplighter"`, then `"Your name"`, never the text
|
|
187
|
+
typed in it (that is its `value`). So the work is in the ids and texts:
|
|
188
|
+
`"Buy"` reads well, `"btn3"` is what a blind player hears. `og.ui.a11y()` answers `{ nodes, focusOrder, focused }`, each
|
|
189
|
+
`A11yNode` with `role`, `name`, `description`, `value`, `states` (disabled, focused,
|
|
190
|
+
checked, selected, expanded, busy…) and its rectangle. A screen reader's
|
|
191
|
+
own requests (focus, activate, increment, decrement) arrive as
|
|
192
|
+
`og.A11yAction` events and are applied to the control at the next frame
|
|
193
|
+
like a press. Prove it with a playtest that walks the tree:
|
|
194
|
+
|
|
195
|
+
```ts
|
|
196
|
+
const tree = og.ui.a11y();
|
|
197
|
+
const unnamed = tree.nodes.filter((n) => n.states.includes("focusable") && n.name === "");
|
|
198
|
+
// a fact the playtest asserts is 0 (templates/ui-kit/tests/a11y.og.ts)
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
The mirror exists on the web only; natively the tree is still answered
|
|
202
|
+
for tests.
|
|
203
|
+
|
|
204
|
+
Arrows and the d-pad move focus to the nearest control that way (wrapping at
|
|
205
|
+
the edges), Tab and Shift+Tab walk reading order, Enter, Space and A press,
|
|
206
|
+
Escape and B go back: the topmost dialog or menu closes, otherwise
|
|
207
|
+
`og.ui.back()` answers true once (open pause on it). While a dialog or menu
|
|
208
|
+
is open `og.ui.back()` answers false even when asked before the dialog is
|
|
209
|
+
built that tick, so a HUD drawn under pause or the settings never takes the
|
|
210
|
+
Back that closes them (a locked dialog, which Back does not close, lets it
|
|
211
|
+
through). A gameplay HUD whose keys
|
|
212
|
+
move the player calls `og.ui.setNavigation(false)` (the HUD templates do);
|
|
213
|
+
dialogs turn navigation back on while open. What opens takes the focus:
|
|
214
|
+
a dialog (every modal template: pause, settings, shop, results, confirm)
|
|
215
|
+
its first control, a dialogue box or cutscene the box, so Enter, Space and
|
|
216
|
+
A answer it at once without a Tab first. The UI reads pad 0 from
|
|
217
|
+
`og.Gamepad` itself (d-pad, A, B, the left stick); for a custom binding,
|
|
218
|
+
queue intents with `og.ui.navigate(world, intent)`. A focus move plays the
|
|
219
|
+
`hover` sound and Enter, Space or A the `press` sound, as the pointer does
|
|
220
|
+
(og-audio §3). Script a pad in a
|
|
221
|
+
playtest with `gamepad:a` then `gamepad:-` (release), or `gamepad:-:0,0.9`
|
|
222
|
+
for the stick.
|
|
223
|
+
|
|
224
|
+
Local multiplayer on one screen (a lobby, a versus character select): call
|
|
225
|
+
`og.ui.setPlayers(4)` every tick the screen shows. Pads 1 to 3 then each
|
|
226
|
+
move a ring of their own (in the player's colour) among the same controls
|
|
227
|
+
and press with A; `response.pad` says whose press it was (null for the
|
|
228
|
+
pointer and keyboard, 0 for pad 0), `og.ui.focus(pad)` where each ring is.
|
|
229
|
+
Script them with `gamepad:right@2` then `gamepad:-@2`.
|
|
230
|
+
|
|
231
|
+
The touch controls (joystick, d-pad, action buttons) answer several fingers
|
|
232
|
+
at once: a thumb holding the stick and another on a button both count. Test
|
|
233
|
+
it with `touch_start` on the stick and `touch:1:down:<button id>` /
|
|
234
|
+
`touch:1:up:<button id>` for the second finger.
|
|
235
|
+
|
|
236
|
+
The UI is drawn over the finished frame (after bloom, grading and TAA), so
|
|
237
|
+
its colours are exactly the tokens and it stays sharp whatever camera the
|
|
238
|
+
game uses; a scroll list cuts its rows at the edge per pixel.
|
|
239
|
+
|
|
240
|
+
## 5. Phones, tablets and the safe area
|
|
241
|
+
|
|
242
|
+
Anchored groups sit inside the safe area (notch, home indicator) at
|
|
243
|
+
`topLeft` … `bottomRight` or `center`, inset by the density's gutter.
|
|
244
|
+
They also keep out of the host's own control over the canvas: on a
|
|
245
|
+
published page the web shell's round Back to the Portal sits top left
|
|
246
|
+
(about 12, 12 to 52, 52 CSS px) and says where in
|
|
247
|
+
`og.ui.frameViewport().covered`, so a group it would overlap moves along
|
|
248
|
+
its edge past it (the racing HUD's place and lap to its right), or, in a
|
|
249
|
+
corner where that would land it on another group (a shooter's vitals on a
|
|
250
|
+
phone), down past it. Every HUD template and any HUD built from
|
|
251
|
+
`og.ui.anchor` groups gets this; a corner drawn another way (screen
|
|
252
|
+
sprites, a row placed by hand) reads `covered` itself, so put a game's own
|
|
253
|
+
HUD in anchored groups. Preview it
|
|
254
|
+
with `og.ui.previewViewport(world, { ..., covered: { x: 12, y: 12, width: 40, height: 40 } })`.
|
|
255
|
+
`og.ui.frameViewport().breakpoint` is `phonePortrait`, `phoneLandscape`,
|
|
256
|
+
`tablet` or `desktop`; templates rearrange themselves by it (tabs become a
|
|
257
|
+
select, side panels move under, touch controls appear when `touch` is set).
|
|
258
|
+
Preview a phone headless with a game command that calls
|
|
259
|
+
`og.ui.previewViewport(world, { width: 390, height: 844, dpr: 3, safeArea: { top: 47, bottom: 34, left: 0, right: 0 }, coarsePointer: true })`
|
|
260
|
+
and `og run --size 1170x2532`.
|
|
261
|
+
|
|
262
|
+
Gestures the UI owns: one finger presses and drags (a slider, a scroll's
|
|
263
|
+
flick, a drag and drop), two fingers on a scroller scroll it (and fling it
|
|
264
|
+
as they lift), two on a
|
|
265
|
+
`zoomView` pinch about their centre and pan it, and an `actionButton` is
|
|
266
|
+
pressed while held and clicked on a tap. There is no swipe, long-press or
|
|
267
|
+
double-tap event: a game that wants one reads `og.Pointers` (`down(id)`,
|
|
268
|
+
`start(id)`, `position(id)`; pointer 0 and up to four more) over ticks,
|
|
269
|
+
deciding by distance and ticks held, which replays exactly. A map or an
|
|
270
|
+
artwork to explore:
|
|
271
|
+
|
|
272
|
+
```ts
|
|
273
|
+
const z = og.ui.zoomView("Map", { texture: map, width: { kind: "grow" }, aspect: 16 / 9, maxZoom: 4 }); // ZoomResult: zoom, centerX, centerY (UI state)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
Tab (or a pad) reaches a zoom view: `+` and `-` zoom it, `0` fits it
|
|
277
|
+
again, the arrows pan it (they stay its own while it is focused). Script two
|
|
278
|
+
fingers with `touch_start` for the first and `touch:1:down:<x,y>` /
|
|
279
|
+
`touch:1:move:<x,y>` / `touch:1:up` for the second
|
|
280
|
+
(`templates/ui-kit/tests/gestures.og.ts`).
|
|
281
|
+
|
|
282
|
+
## 6. Theming: skins, motion, transitions, sounds
|
|
283
|
+
|
|
284
|
+
The default look is the platform's tokens; a game should have its own. A
|
|
285
|
+
**skin** (`og.load.skin`, an `.ogt` of `type skin`) replaces the palette,
|
|
286
|
+
radii, type ramp, durations, fonts and icons, and dresses every element
|
|
287
|
+
class in nine-slice frames from its atlas (panel, card, button with hover,
|
|
288
|
+
pressed and disabled, field, slot, tooltip, HUD capsule, progress track and
|
|
289
|
+
fill), with ornaments (panel corners, the divider, a heading flourish), a
|
|
290
|
+
panel texture, shadows and glows, press squash and hover lift, entrances and
|
|
291
|
+
sounds. Choosing one is og-taste §1. Six packs to start from are in
|
|
292
|
+
the engine repository under `templates/ui-kit/assets/skins/` (`pixel`,
|
|
293
|
+
`cartoon`, `fantasy`, `scifi`, `paper`, `neon`; copy the directory into the
|
|
294
|
+
game's `assets/skins/` and rename it at will: a skin's ids are read
|
|
295
|
+
relative to its file first, then from `assets/`; og-taste §1 has the one
|
|
296
|
+
line to check, the CJK `fallback`):
|
|
297
|
+
|
|
298
|
+
```ts
|
|
299
|
+
const skin = og.load.skin(world, "skins/fantasy/skin.ogt"); // atlas, fonts, icons load with it
|
|
300
|
+
og.ui.configure(world, {
|
|
301
|
+
skin: skin.ok ? skin.value : null,
|
|
302
|
+
contrast: "high", // still applies over a skin
|
|
303
|
+
reducedMotion: settings.reducedMotion, // every motion becomes a fade
|
|
304
|
+
sounds: { hover, press, open, close, success, error }, // og.load.sound handles, played on the ui bus
|
|
305
|
+
});
|
|
306
|
+
og.ui.configure(world, { reducedMotion: true }); // later, from the settings: the skin, fonts and sounds stay
|
|
307
|
+
```
|
|
308
|
+
|
|
309
|
+
`configure` takes a partial UiDesc: a field left out keeps the value the
|
|
310
|
+
last call gave it (or the skin a look applied, `GameDesc.look` and
|
|
311
|
+
`og.render.applyLook`), and `null` clears one (`skin: null` is the
|
|
312
|
+
platform tokens again). A UI left with no font at all warns that it draws
|
|
313
|
+
no text.
|
|
314
|
+
|
|
315
|
+
A skin's `slice` line says how a frame's edges and centre fill: `stretch`
|
|
316
|
+
(the default), `repeat` (tiled at the art's size from the corner, the last
|
|
317
|
+
tile cut) or `round` (a whole number of tiles resized to fit, as CSS
|
|
318
|
+
`round`): tile patterned borders (a rope, a rivet row, a pixel bevel),
|
|
319
|
+
stretch plain ones. `glass <part> <blur> <fill> [tint]` makes a part glass:
|
|
320
|
+
the game behind it blurred (`blur` CSS px), tinted by the part's fill, under
|
|
321
|
+
its frame drawn at `fill` opacity (the scifi pack's dialogs:
|
|
322
|
+
`glass panel 16 0.85 0.15`). Glass blurs the scene and the UI drawn under
|
|
323
|
+
it (a HUD under a glass dialog); it costs a frame copy and a blur pyramid
|
|
324
|
+
when any is on screen (about 0.8 ms at 1280×720 on an M4 Pro), and one more
|
|
325
|
+
pyramid for each glass layer with UI under it, nothing without glass.
|
|
326
|
+
A player whose system asks for less transparency (`prefers-reduced-
|
|
327
|
+
transparency`, or `reducedTransparency: true` in `og.ui.configure` from a
|
|
328
|
+
settings toggle) gets glass parts as opaque surfaces and dialog scrims and
|
|
329
|
+
translucent fills (secondary buttons, chips, tracks) flat. Your own
|
|
330
|
+
see-through overlays should follow: read `og.ui.frameTheme().reducedTransparency`
|
|
331
|
+
(and `reducedMotion`) and draw them opaque.
|
|
332
|
+
|
|
333
|
+
A skin of the game's own art: draw each part as a nine-patch PNG
|
|
334
|
+
(`panel.9.png`, `button.9.png`, `buttonHover.9.png`, ..., `soft.9.png`: the
|
|
335
|
+
art inside a one-pixel border whose top row and left column mark in opaque
|
|
336
|
+
black what stretches) and any ornament as a plain PNG (`corner.png`,
|
|
337
|
+
`divider.png`, `flourish.png`, `tile.png`) in the skin's directory, then
|
|
338
|
+
`og skin pack assets/skins/<name>`: it packs them into `atlas.png` and
|
|
339
|
+
writes the skin file's `atlas`, `slice` and `soft` lines and the ornaments'
|
|
340
|
+
regions, keeping every other line (palette, fonts, a slice's `tint` or
|
|
341
|
+
`repeat`). Re-run it after redrawing a part.
|
|
342
|
+
|
|
343
|
+
Never pass hex colours for chrome: use `{ token: "accent" }`, `"fgMuted"`,
|
|
344
|
+
`"surface"` and the type steps (`displayLg`, `xl`, `meta`); the skin's
|
|
345
|
+
palette answers them. A container that should look like a HUD capsule or a
|
|
346
|
+
card takes `frame: "capsule"` (or `"card"`, `"slot"`…) in its desc; titles
|
|
347
|
+
take `heading: true`. Without a skin both are plain.
|
|
348
|
+
|
|
349
|
+
Motion and juice, all by the tick and in the UI state:
|
|
350
|
+
|
|
351
|
+
| Want | Call |
|
|
352
|
+
|---|---|
|
|
353
|
+
| A score, coins or a reward counting up | `og.ui.counter(id, value, { from: 0, font: "display", step: "displayLg" })` |
|
|
354
|
+
| Sparks when a star or reward lands | `og.ui.burst({ count: 10 })` right after the element |
|
|
355
|
+
| A panel or item popping in | `og.ui.transition(id, show, { kind: "pop" })` (also `fade`, `slideUp`…, `scale`) |
|
|
356
|
+
| A change of game screen | `const s = og.ui.screenTransition("Screens", state.screen, { kind: "iris" }).shown;` then build `s` (`fade`, `wipe`, `iris`, `dissolve`, `slide`; a colour or a texture) |
|
|
357
|
+
| A wrong answer, a locked door, a refused purchase | `og.ui.shake({ sound: true })` right after the element, once, on the tick it happens: it swings three times and comes to rest by itself (the skin's `motion.shake`, else 6 px), with the `error` sound |
|
|
358
|
+
|
|
359
|
+
Skins add the rest themselves: dialog and panel entrances with staggered
|
|
360
|
+
controls, hover lift and press squash on springs (the `hoverSpring 1 350` and `pressSpring 0.55 120` lines of a
|
|
361
|
+
skin's `motion` block: damping ratio and response in ms; below 1 bounces), eased progress bars, toasts flying in,
|
|
362
|
+
a shake and the error sound on a press of a disabled button. Reduced motion
|
|
363
|
+
keeps every shake still and turns the rest into fades. The screen the game
|
|
364
|
+
opens on is built settled (`instant: true` on its `mainMenu`, `anchor` or
|
|
365
|
+
`transition` while it is that screen): the web draws it behind the play
|
|
366
|
+
gate, where an entrance would stand part-way until the tap (og-platform-sdk
|
|
367
|
+
§3). How long and how
|
|
368
|
+
much is og-taste §5 and §6; camera shake is never for UI (og-feel §4).
|
|
369
|
+
|
|
370
|
+
## 7. Text: labels, fields, areas, input methods
|
|
371
|
+
|
|
372
|
+
Labels lay out by themselves: words break at spaces, CJK between
|
|
373
|
+
characters under kinsoku (og-i18n §3), right-to-left runs reorder, complex
|
|
374
|
+
scripts are shaped (Arabic joins, Devanagari conjuncts) and every font's
|
|
375
|
+
kerning pairs apply. What a game sets:
|
|
376
|
+
|
|
377
|
+
```ts
|
|
378
|
+
og.ui.label("Quest", quest.text, { width: { kind: "grow" }, maxLines: 2, ellipsis: true, color: { token: "fgMuted" } }); // cut with … on the last line
|
|
379
|
+
og.ui.label("Gold", gold, { font: "rounded", step: "xl", nowrap: true }); // body, display, mono or rounded
|
|
380
|
+
```
|
|
381
|
+
|
|
382
|
+
`ellipsis` cuts a `nowrap` label (which may then shrink in its row) or the
|
|
383
|
+
last of `maxLines`; the probe still reads the whole text, so a playtest
|
|
384
|
+
can find it. Keep long text in a `scroll` instead of cutting what the
|
|
385
|
+
player must read.
|
|
386
|
+
|
|
387
|
+
Text entry is a field (one line) or an area (several), each one call that
|
|
388
|
+
takes the value and answers it, like every widget (§1):
|
|
389
|
+
|
|
390
|
+
```ts
|
|
391
|
+
// Save = defineResource("Save", { name: str(64), note: str(1024) }): maxChars × 4 bytes always fits.
|
|
392
|
+
const name = og.ui.textField("Name", save.name, { placeholder: "Your name", maxChars: 16 });
|
|
393
|
+
if (name.response.changed) save.name = name.value;
|
|
394
|
+
if (name.response.submitted) state.screen = "play"; // Enter, once
|
|
395
|
+
const note = og.ui.textArea("Note", save.note, { placeholder: "A note for later", rows: 3, maxChars: 256, width: { kind: "grow" } });
|
|
396
|
+
save.note = note.value;
|
|
397
|
+
```
|
|
398
|
+
|
|
399
|
+
Both take the press point as the caret, select by drag, Shift with
|
|
400
|
+
arrows, and Cmd (on a Mac) or Ctrl with A; copy, cut and paste use the page's
|
|
401
|
+
clipboard; typed text replaces the selection; the text scrolls to keep the
|
|
402
|
+
caret in view and the caret blinks by the tick (steady under reduced
|
|
403
|
+
motion). An area's Enter is a new line, Up and Down move between lines
|
|
404
|
+
and stay out of focus navigation while it is edited, and `maxChars`
|
|
405
|
+
counts its line breaks. On the web the shell keeps a hidden text box over
|
|
406
|
+
the focused field, so a phone's keyboard opens and an input method
|
|
407
|
+
(Chinese, Japanese, Korean) shows its candidates there; the composition
|
|
408
|
+
is drawn underlined at the caret until it commits. Script them:
|
|
409
|
+
`type:<text>` (the text to the `;` exactly, a trailing space included),
|
|
410
|
+
`compose:<text>` (`compose:` alone ends it) and `commit:<text>`, then read
|
|
411
|
+
the field's value from `dump_ui` or the game's facts
|
|
412
|
+
(`templates/ui-kit/tests/text.og.ts`). Fields take the typed-text stream
|
|
413
|
+
(`og.TextInput`: the page's typing, `type:` and `commit:`); until a world
|
|
414
|
+
has had any, a printable key pressed (`key_down:KeyM`) types its US-layout
|
|
415
|
+
character, and from the first on keys only edit (Backspace, arrows), so
|
|
416
|
+
script text with `type:`, not `key_down`.
|
|
417
|
+
|
|
418
|
+
Fonts (the one rule for every label, field and button):
|
|
419
|
+
|
|
420
|
+
1. The role's font (body, display, mono, rounded; the skin's or
|
|
421
|
+
`og.ui.configure`'s) when it has every character: the whole label in it.
|
|
422
|
+
2. Else the first of the locale's fonts (its string table's `fonts` line)
|
|
423
|
+
that has every character: the whole label in that face, its Latin and
|
|
424
|
+
digits too. A CJK subset that carries Latin-1, as the archetypes cook
|
|
425
|
+
theirs, therefore sets `"Lantern Vale · 灯笼谷"` entirely in Noto.
|
|
426
|
+
3. Else run by run: each run of the label from the first font that has it,
|
|
427
|
+
the role's font first, then the locale's fonts, `UiDesc.fallbackFonts`
|
|
428
|
+
(a Hebrew or Devanagari face, a CJK face cooked with `runtime true`) and
|
|
429
|
+
the skin's `fallback`; a letter and its accents stay in one face. A
|
|
430
|
+
CJK-only subset (lantern-vale's `tools/subset-font.py`) gives this: the
|
|
431
|
+
Latin in the skin's face, the Chinese in Noto, on one line.
|
|
432
|
+
|
|
433
|
+
So choose by the subset: keep Latin in the CJK subset for one face per
|
|
434
|
+
label, leave it out to keep the game's Latin face beside CJK. Tracking
|
|
435
|
+
applies to Latin text only.
|
|
436
|
+
|
|
437
|
+
## 8. Proving it
|
|
438
|
+
|
|
439
|
+
`templates/ui-kit` in the engine repository runs every template; its
|
|
440
|
+
playtests show how to drive UI from a script: `move_to_text:=shop` then
|
|
441
|
+
`touch_start`/`touch_end`, `key_down:Tab`/`key_up:Tab`, a game command that
|
|
442
|
+
calls `og.ui.navigate`, `dump_ui` for bounds and texts, and frame comparison
|
|
443
|
+
against references. `og polish .` scores the screens' contrast, text size,
|
|
444
|
+
transitions and sounds (og-build §5).
|