* Write the Unreal documentation set: design, steps, ideas, decisions and specs Rewrites the design and engineering docs of the two earlier Unity projects (Adventurer Guild, Project Malleable) for an Unreal Engine 5 prototype that builds and proves three things in order: a movement controller, fighting and crafting. Neither earlier core loop is carried; their ideas are catalogued as features with what each needs. - Docs/Design.md: the human summary (pillars, fixed decisions, glossary) - Docs/Steps.md: a ladder of fifteen proofs, the first six in full - Docs/Ideas.md: salvaged ideas from both projects, none scheduled - Docs/Decisions.md: D-01..D-36, open decisions OD-01..OD-08, deferrals - Docs/Spec/: notation, Architecture, Movement, Interaction, Combat, Crafting, Networking, Telemetry, UI, written as Unreal C++ skeletons - CLAUDE.md and README.md for the repository * Add the stat block: one attribute set on every body, every influence an effect Every body (player, enemy, NPC) carries the same UStatBlockAttributeSet, and every outside influence on it is a gameplay effect: base values, buffs, debuffs, ground surfaces, carried weight, gear, being downed. Counters are tags and attributes on the receiver: immunity blocks by tag, resistance scales by attribute, cleanse removes by tag. Same status from several sources: strongest wins; different statuses multiply. - Docs/Spec/Stats.md: the block, effects and their five sources (abilities, areas, surfaces, items, the world), stacking, counters, readers, tests - Movement: the movement component's tagged speed-multiplier map is removed; speed is tuning times the MoveSpeed attribute; the ground surface trace turns mud into an effect; the gym gets mud and ice patches - Combat: the attribute set moves to Stats; enemies and kits carry FStatBlockDefaults; the funnel reads MagicResist and Immune.Damage tags; taunt and mark are statuses - Interaction: carried weight is GE_Encumbered against CarryCapacity - Crafting: class bonuses are the WorkSpeed and WorkQuality attributes; enchantments may grant a holder effect - Steps: step 5 builds the block with mud, a volume and an immunity - Decisions D-37 and D-38; design summary, CLAUDE.md, indexes, worklog
178 lines
10 KiB
Markdown
178 lines
10 KiB
Markdown
# UI
|
|
|
|
Owns what interface exists, what it is built on, and the rules that keep it one interface rather than a dozen:
|
|
the HUD, the prompt, the action bar, the one theme asset, world-space text on props, localisation and the menu.
|
|
It does **not** own what any readout means; the health number belongs to [Combat.md](Combat.md), the bench panel to
|
|
[Crafting.md](Crafting.md).
|
|
|
|
Read [Architecture.md](Architecture.md) first. There is deliberately little here. A prototype about moving,
|
|
fighting and crafting needs a crosshair, a health number, a prompt, four ability slots and a way to quit.
|
|
|
|
## The rule
|
|
|
|
```
|
|
[SALVAGED] The player acts on the world physically; the game reports state conventionally.
|
|
|
|
Everything the player DOES is a thing in the world reached through the interaction system: a bench, a station,
|
|
a weapon on the ground, a downed teammate. Screen space is for readouts: health, the prompt, the crosshair, the
|
|
action bar. The test for anything new: can the player do it by touching something in the world? If yes it is a
|
|
prop. If it only tells them something, it may be HUD.
|
|
|
|
The earlier guild project made this a pillar and forbade all menus but Escape. This project keeps the test and
|
|
drops the absolutism: a world this is headed for will need screens the prototype does not, and the rule's job is
|
|
to make every one of them earn its place, not to forbid them.
|
|
```
|
|
|
|
## Decisions
|
|
|
|
```
|
|
[DECIDED] UMG with CommonUI. Widgets are Blueprint children of C++ base classes that own the data binding; the
|
|
Blueprint owns layout and look only. CommonUI supplies input routing, gamepad focus and per-device glyphs, which
|
|
is exactly the part nobody wants to write twice.
|
|
```
|
|
|
|
```
|
|
[DECIDED] One theme asset. Every colour, type size, spacing step and motion duration is a token on one data asset,
|
|
named by role and never by hue. No widget holds a literal colour, size or duration. A colourblind palette is a
|
|
second asset with the same roles.
|
|
```
|
|
|
|
```
|
|
[DECIDED] Prop text is world-space and has no canvas. Text on a thing in the world is a UTextRenderComponent, or a
|
|
world-space UWidgetComponent when it needs layout (the bench panel). It reads the same theme.
|
|
```
|
|
|
|
```
|
|
[DECIDED] Everything a player reads is FText from a string table. FString is for identifiers and logs.
|
|
```
|
|
|
|
## Layout
|
|
|
|
```
|
|
Source/<Project>/UI/
|
|
├── UITheme.h // UPrimaryDataAsset: the tokens
|
|
├── ThemedText.h // UCommonTextBlock subclass reading a type token and a colour role
|
|
├── HudWidget.h // the base: health, crosshair, prompt, action bar, downed state; binds to the local player
|
|
├── InteractionPromptWidget.h // verb, target, reason, hold fill, live glyph
|
|
├── ActionBarWidget.h // four slots: glyph, name, cooldown; reads the ability system and the input subsystem
|
|
├── MenuWidget.h // Escape: Resume, Settings, Quit. Never pauses.
|
|
└── SettingsWidget.h // comfort sliders, motion scale, rebinding rows through the engine's user settings
|
|
|
|
Content/UI/
|
|
├── DA_Theme_Default
|
|
├── WBP_Hud, WBP_InteractionPrompt, WBP_ActionBar, WBP_ActionBarSlot, WBP_Menu, WBP_Settings
|
|
└── ST_Game // the string table; one key per readable string, prop prompts included
|
|
```
|
|
|
|
## The theme
|
|
|
|
```cpp
|
|
UCLASS(BlueprintType)
|
|
class UUITheme : public UPrimaryDataAsset
|
|
{
|
|
GENERATED_BODY()
|
|
public:
|
|
// Roles, never hues. A widget names a role; the asset says what it looks like.
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") FLinearColor HudNeutral, HudGood, HudWarning, HudDanger;
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") FLinearColor PromptAvailable, PromptBlocked;
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") FLinearColor OutlineInRange, OutlineTargeted;
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") FLinearColor Scrim, PanelSurface, PanelText;
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") TMap<FGameplayTag, FLinearColor> DomainColours; // Domain.Forge, .Wood, .Enchant
|
|
UPROPERTY(EditDefaultsOnly, Category = "Colour") TArray<FLinearColor> PlayerColours; // by join order, low saturation, rings not fills
|
|
|
|
// Type scale, in steps rather than free numbers. HUD sizes in pixels at 1080p; world sizes in centimetres.
|
|
UPROPERTY(EditDefaultsOnly, Category = "Type") FSlateFontInfo HudPrimary, HudSecondary, Prompt, Caption;
|
|
UPROPERTY(EditDefaultsOnly, Category = "Type") float PropTitleCm = 12.f, PropBodyCm = 7.f, PropSmallCm = 5.f;
|
|
|
|
UPROPERTY(EditDefaultsOnly, Category = "Space") TArray<float> SpacingSteps = {4, 8, 16, 24, 40};
|
|
UPROPERTY(EditDefaultsOnly, Category = "Motion") float Fast = 0.12f, Normal = 0.2f, Slow = 0.4f; // every one scaled by the motion setting
|
|
};
|
|
```
|
|
|
|
Meaning never rides on hue alone: a blocked prompt is grey **and** worded differently; a danger readout is red
|
|
**and** changes shape. `HudGood` leans teal and `HudDanger` leans orange so the two separate under deuteranopia.
|
|
Substance colours are not theme tokens; they are on the substance definitions because the substance is the palette.
|
|
|
|
The placeholder look: neutral, stylised, legible. The two source projects each had a fully specified palette (a
|
|
parchment-and-ink guild hall; a warm forge with domain tints). Neither is this game's, so the default asset is
|
|
plain white HUD over greybox and the domain tints, and an art direction decides the rest when there is one.
|
|
|
|
## The HUD
|
|
|
|
One widget, added by the player controller for the local player, reading the local player state's ability system
|
|
and the pawn's interaction component. It never computes: health is the attribute, the prompt is
|
|
`UInteractionComponent::GetCurrentPrompt`, the cooldown is the cooldown tag's remaining time.
|
|
|
|
| Element | Reads | Shows |
|
|
| --- | --- | --- |
|
|
| Crosshair | nothing | four pixels, `HudNeutral` |
|
|
| Health | `Health`, `MaxHealth`, `State.Downed` | number plus a status line; colour by fraction, and shape when downed |
|
|
| Prompt | `GetCurrentPrompt` | `[glyph] Verb Target`, greyed with the reason when blocked, a radial fill while holding |
|
|
| Action bar | the four `Input.Ability.n` abilities, the input subsystem | glyph (live binding, per device), name, seconds left; dim while empty or cooling |
|
|
| Downed | `State.Downed`, `GE_BleedOut` remaining | "Downed: 24 s" and who is nearest |
|
|
| Carry | `UCarryComponent::GetHeld` | the held object's name, and "hands full" on a blocked verb |
|
|
| Statuses | active `Status.*` tags and remaining durations | a row of icons from `DT_StatusIcons`, debuffs first; the "Immune" flash on a blocked one comes from the cue, not the HUD |
|
|
|
|
Two canvases, and the split is a rebuild-cost decision: the static one (crosshair, chrome) and the volatile one
|
|
(everything that changes). Neither has hit testing; the HUD is never clicked.
|
|
|
|
## Input modes
|
|
|
|
One place decides what the cursor does and which mapping context is live, and it is CommonUI's input routing on
|
|
the activatable widget stack. Gameplay input stops while the menu is up; **the world does not.** Enemies keep
|
|
moving, the forge keeps burning. This is the difference between a mode switch and a pause, and it is the design:
|
|
opening Settings in a fight is a bad idea, which is correct, and it means no second behaviour for the same key that
|
|
would be untested in one of the two modes.
|
|
|
|
The prompt's glyph and the action bar's keys come from the engine's Enhanced Input user settings through the
|
|
input subsystem, by the device the player touched last. No widget prints a literal key.
|
|
|
|
## World-space text
|
|
|
|
Text on a prop is a `UTextRenderComponent` reading the theme's world sizes and a colour role, on the prop itself,
|
|
with no canvas: it stays readable as you walk round it and lights with the scene. The surface rule: light surface,
|
|
`PanelText`; dark surface, `HudNeutral`. The bench's assembly panel, which needs slots and a gauge, is a
|
|
`UWidgetComponent` in world space reading the same theme and laid out by the family's authored grid so a sword
|
|
reads as a sword. Its quality gauge shows the synergy segment separately, so players learn why matching themes
|
|
score higher.
|
|
|
|
## The menu
|
|
|
|
Escape: Resume, Settings, Quit. Settings: FOV per mode, head stabilisation, bob, motion scale, text scale, and a
|
|
rebind row per bindable action through the engine's user settings. Nothing else, until a step needs something
|
|
else and says why here.
|
|
|
|
## Localisation
|
|
|
|
Every readable string is a key in `ST_Game`, resolved through `FText::FromStringTable`. Prompts are keyed by verb
|
|
tag (`Interact.Verb.Revive` resolves to `prompt.verb.revive`), reasons by reason tag, ability names on the ability
|
|
class as `FText`. The engine's localisation dashboard gathers from string tables and `FText` properties; nothing
|
|
else is needed until there is a second language.
|
|
|
|
## Accessibility hooks
|
|
|
|
Hooks now, content later; none of the later work touches a widget.
|
|
|
|
| Hook | Exists | Filled in later |
|
|
| --- | --- | --- |
|
|
| Text scale | `ThemedText` multiplies every size by it | a slider |
|
|
| Colourblind palettes | roles on the theme, no literals in widgets | alternative theme assets |
|
|
| Motion scale | every theme duration, camera kick, hit stop and bob multiplies by it | the slider ships in step 3, a comfort requirement for first person |
|
|
| Rebinding | prompts read the live binding | the rows ship in step 3 through engine settings |
|
|
| Hold-to-confirm | on irreversible verbs already | an option to extend it to every verb |
|
|
|
|
## Telemetry
|
|
|
|
`settings_changed` with `setting_id` as the dotted path (`camera.fov.first_person`, `input.bindings.interact`) and
|
|
the new value. Nothing else: prompt visibility and menu opens are high frequency and low value, and
|
|
`prop_interacted` already answers whether players find things.
|
|
|
|
## Open questions
|
|
|
|
- **Q1. World-space widget legibility.** A `UWidgetComponent` at bench distance in both camera modes has to be
|
|
checked once with real text before the assembly panel is built on it. Ten minutes in step 13, not a spike.
|
|
- **Q2. A font.** The engine's default until a look exists. When one is chosen it is one face with material
|
|
presets, not three font assets.
|
|
- **Q3. Does the HUD scale with resolution?** Scale with screen size at 1080p reference is assumed; ultrawide needs
|
|
one look.
|