Merge of the two project copies into one self-contained plugin (the superset: variable_op + variables.py, full pcg_op runtime/declarative/preset ops, the CreateWidgetFloatAnimation widget tool, and full Voxel graph authoring). Made fully project- and machine-agnostic — no hardcoded paths: - New src/projectPaths.js auto-detects the host .uproject (walk-up), project name, Editor build target, log file, and engine install (EngineAssociation via launcher manifest/registry, else installed-engine scan). All overridable via UE_* env vars. - Rewired buildOrchestrator/insights/launcher/insightsExporter/server.js and the Python workers (cpp_scaffold, live_coding, apply_graph, console) off the old C:/Github/ihy, IHY*, E:/UE_Versions and UE_5.7 literals onto the resolver. - Voxel made optional: Build.cs auto-detects the Voxel plugin (env UE_MCP_WITH_VOXEL override) and the C++ compiles to stubs under WITH_MCP_VOXEL, so the module builds in projects without Voxel; .uplugin marks Voxel optional. - De-branded the agent-gateway and docs; scrubbed a leaked API key; excluded node_modules/Binaries/Intermediate/__pycache__/secrets from the repo. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
274 lines
12 KiB
Markdown
274 lines
12 KiB
Markdown
# UI_WORKFLOW — UMG/Slate UI authoring guide for AI agents
|
|
|
|
> **READ THIS BEFORE EDITING ANY `WBP_*` / UMG / SLATE / UI.**
|
|
> If you start a UI task without following this file, you will produce a generic
|
|
> placeholder ("prototype look"). This document is the contract that turns
|
|
> "ugly UMG demo" into "shippable UI".
|
|
|
|
## 0. Quick decision flow
|
|
|
|
```
|
|
user asks for UI work
|
|
│
|
|
├─ Is there a DA_UITheme asset in /Game/UI/Theme/ ? ── no ─→ STEP A. Build theme first.
|
|
│ (one-time setup, ~5 min)
|
|
├─ Are there base components in /Game/UI/Components/ ? ── no ─→ STEP B. Build component library.
|
|
│
|
|
├─ Did the user give a reference image / mood / brand? ── no ─→ STEP C. Ask for one before coding.
|
|
│
|
|
└─ All three present? ─→ STEP D. Compose screen → screenshot → iterate.
|
|
```
|
|
|
|
You **must not** invent palettes, spacing, or component shapes on the fly. If
|
|
a token is missing, you ADD it to the theme. If a component is missing, you
|
|
CREATE it in the library. The screen WBP only composes.
|
|
|
|
---
|
|
|
|
## STEP A — The Theme (one DataAsset to rule them all)
|
|
|
|
### A.1 Class
|
|
Create C++ DataAsset `UProjectUITheme : UPrimaryDataAsset` (use `cpp_scaffold_op`):
|
|
|
|
```cpp
|
|
USTRUCT(BlueprintType) struct FProjectUIPalette {
|
|
GENERATED_BODY()
|
|
UPROPERTY(EditAnywhere) FLinearColor Primary = FLinearColor::White;
|
|
UPROPERTY(EditAnywhere) FLinearColor Accent = FLinearColor(1, 0.5, 0.1, 1);
|
|
UPROPERTY(EditAnywhere) FLinearColor Danger = FLinearColor::Red;
|
|
UPROPERTY(EditAnywhere) FLinearColor Surface = FLinearColor(0.05, 0.05, 0.05, 1);
|
|
UPROPERTY(EditAnywhere) FLinearColor SurfaceAlt = FLinearColor(0.1, 0.1, 0.1, 1);
|
|
UPROPERTY(EditAnywhere) FLinearColor Ink = FLinearColor(0.9, 0.9, 0.85, 1);
|
|
UPROPERTY(EditAnywhere) FLinearColor Muted = FLinearColor(0.5, 0.5, 0.45, 1);
|
|
};
|
|
|
|
USTRUCT(BlueprintType) struct FProjectUIType {
|
|
GENERATED_BODY()
|
|
UPROPERTY(EditAnywhere) FSlateFontInfo H1; // 48pt display
|
|
UPROPERTY(EditAnywhere) FSlateFontInfo H2; // 32pt headings
|
|
UPROPERTY(EditAnywhere) FSlateFontInfo Body; // 16pt text
|
|
UPROPERTY(EditAnywhere) FSlateFontInfo Caption;// 12pt small
|
|
UPROPERTY(EditAnywhere) FSlateFontInfo Mono; // monospace / terminal text
|
|
};
|
|
|
|
UCLASS()
|
|
class PROJECTUI_API UProjectUITheme : public UPrimaryDataAsset {
|
|
GENERATED_BODY()
|
|
public:
|
|
UPROPERTY(EditAnywhere, Category="Theme") FProjectUIPalette Palette;
|
|
UPROPERTY(EditAnywhere, Category="Theme") FProjectUIType Type;
|
|
// Spacing rhythm — use index, not raw px.
|
|
UPROPERTY(EditAnywhere, Category="Theme") TArray<float> Spacing = {4, 8, 12, 16, 24, 32, 48, 64};
|
|
UPROPERTY(EditAnywhere, Category="Theme") float CornerRadius = 4.f;
|
|
UPROPERTY(EditAnywhere, Category="Theme") float BorderWeight = 1.f;
|
|
// Named brushes — referenced by their FName key from WBPs.
|
|
UPROPERTY(EditAnywhere, Category="Theme") TMap<FName, FSlateBrush> Brushes;
|
|
};
|
|
```
|
|
|
|
### A.2 Instance
|
|
Create instance at `/Game/UI/Theme/DA_UITheme` (one per visual mode — fork as
|
|
`DA_UITheme_<Mode>` if a project needs several). Example tuning:
|
|
|
|
| Token | Value | Why |
|
|
|---|---|---|
|
|
| Palette.Surface | `(0.04, 0.05, 0.05, 1)` | near-black base |
|
|
| Palette.SurfaceAlt | `(0.09, 0.1, 0.09, 1)` | row striping |
|
|
| Palette.Ink | `(0.87, 0.91, 0.83, 1)` | off-white, never pure |
|
|
| Palette.Accent | `(0.95, 0.62, 0.21, 1)` | amber highlight |
|
|
| Palette.Danger | `(0.78, 0.10, 0.10, 1)` | muted red, not hex red |
|
|
| Palette.Muted | `(0.45, 0.50, 0.45, 1)` | green-grey |
|
|
| Type.H1 | font `Roboto Mono Bold`, 48, letter spacing +1 | display |
|
|
| Type.Body | font `Inter Regular`, 16 | readable body |
|
|
| CornerRadius | 0 | sharp / brutalist |
|
|
| BorderWeight | 1.5 | thicker → more readable |
|
|
|
|
### A.3 Access
|
|
Every WBP holds `UPROPERTY(EditDefaultsOnly) UProjectUITheme* Theme;` set in Class
|
|
Defaults to `DA_UITheme`. No hardcoded colors anywhere. If you catch yourself
|
|
typing `FLinearColor(0.7, 0.1, 0.1, 1)` — stop, add a token to the theme instead.
|
|
|
|
---
|
|
|
|
## STEP B — Component Library
|
|
|
|
Build these once, reuse forever. All under `/Game/UI/Components/`.
|
|
|
|
### Required base set
|
|
| Component | Purpose | Variants |
|
|
|---|---|---|
|
|
| `WBP_Button` | All clickable | Primary, Ghost, Danger, Icon-only |
|
|
| `WBP_Card` | Container w/ header/body/footer | Flat, Raised, Outlined |
|
|
| `WBP_HUDPanel` | Framed panel (corner brackets, overlay channel) | Top, Bottom, Floating |
|
|
| `WBP_StatBar` | Numeric stat bar (health/energy/progress) | Horizontal, Vertical, Circular |
|
|
| `WBP_Label` | Text wrapper that auto-picks Type token | H1, H2, Body, Caption, Mono |
|
|
| `WBP_Divider` | 1px line | Solid, Dashed, Glitchy |
|
|
| `WBP_Icon` | Wraps `Image` with theme tint | size=spacing token |
|
|
| `WBP_ListRow` | One row of a list, themed | Selected/Hovered states wired |
|
|
| `WBP_Modal` | Centered overlay with backdrop | Confirm, Form, Info |
|
|
| `WBP_Toast` | Bottom-slide notification | Info, Warn, Error |
|
|
|
|
### Authoring rules
|
|
- **No hardcoded sizes** — use `Theme->Spacing[idx]` (e.g. padding = `Spacing[3]` = 16px).
|
|
- **Every interactive widget has 4 states wired**: Normal, Hovered, Pressed, Disabled. Hooked from theme brushes, not duplicated.
|
|
- **Each component exposes `SetVariant(EVariant)` and `SetState(EState)` BP-callable funcs** — so the screen WBP composes by setting flags, not by hand-tweaking child widgets.
|
|
- **Animation:** every state transition has a 80-150ms ease. Define one `WidgetAnimation` per state in `WBP_Button`, copy pattern to others. No animation = feels dead.
|
|
|
|
### Optional stylized overlays
|
|
- `WBP_HUDPanel` can expose a Niagara/Material overlay channel — bind project-specific material instances (e.g. `MI_UI_Scanline`, `MI_UI_Noise`) as defaults if your project has them.
|
|
- `WBP_Label` Mono variant can use a chromatic-aberration material instance.
|
|
- Background of every HUD piece: prefer a themed `MI_UI_Surface` material over a flat solid color when a stylized look is wanted.
|
|
|
|
---
|
|
|
|
## STEP C — Reference parsing protocol
|
|
|
|
When user supplies a reference image, **do not** start coding. First emit:
|
|
|
|
```
|
|
REFERENCE TOKENS
|
|
================
|
|
Palette (6 colors HEX, ordered by dominance):
|
|
1. Primary surface: #__
|
|
2. Surface alt: #__
|
|
3. Ink (text): #__
|
|
4. Accent: #__
|
|
5. Danger/warning: #__
|
|
6. Muted/secondary: #__
|
|
|
|
Typography:
|
|
H1 size: __pt weight: __ tracking: __
|
|
Body size:__pt weight: __
|
|
Family hint: __ (serif/sans/mono/condensed)
|
|
|
|
Spacing rhythm (3 visible gaps in px): __ __ __
|
|
→ suggested Spacing array: [__, __, __, __, __, __, __, __]
|
|
|
|
Corner radius observed: __px (0 = sharp / brutalist)
|
|
Border weight observed: __px
|
|
|
|
Visual hierarchy (one sentence): __
|
|
|
|
Mood (3 adjectives): __, __, __
|
|
|
|
Departures from current DA_UITheme:
|
|
- Theme.Palette.__ should become #__ (was #__)
|
|
- Theme.Spacing[__] should become __ (was __)
|
|
```
|
|
|
|
Then ask the user: "apply to existing theme or fork as `DA_UITheme_<NewName>`?"
|
|
|
|
Once approved, use `audio_op set_property` / direct asset edits to mutate the
|
|
theme DataAsset. Never edit individual WBPs to match a ref — change the theme,
|
|
all WBPs follow.
|
|
|
|
---
|
|
|
|
## STEP D — The Vision Loop (most important)
|
|
|
|
This is what makes AI UI not suck. After ANY structural change, you MUST:
|
|
|
|
```
|
|
1. widget_op apply_tree / set_property / ... (your edit)
|
|
2. widget_op compile_save {bp_path}
|
|
3. widget_op screenshot {bp_path, size: [1920, 1080]}
|
|
4. Read the returned png_path. If you are a vision-capable model:
|
|
open the file, look at the result, list 3 specific problems before next edit.
|
|
5. Loop until no problems remain.
|
|
```
|
|
|
|
### Hard rule
|
|
- **Maximum 2 blind edits** (without screenshot) per session. After that, MUST screenshot. If a user reports "it looks bad", your first action is `widget_op screenshot`, not asking for clarification.
|
|
- **Before declaring "done"**, last action MUST be a screenshot, and you MUST describe what you see (not what you intended).
|
|
- The screenshot landing path is `<Project>/Saved/MCP/widgets/<bp_name>.png`. Always attach this to your turn output.
|
|
|
|
### What to look for in the screenshot
|
|
- **Alignment grid** — are baselines and gutters consistent? Use a mental 8px grid.
|
|
- **Hierarchy** — can you ID primary action in <1s by squinting?
|
|
- **Density** — is it crammed (Unity-tutorial vibe) or breathing?
|
|
- **Color count** — is anything outside the theme palette? If yes, that's a bug.
|
|
- **Edge cases** — empty list, longest possible label (16 chars), worst-case localization (German is +30%).
|
|
|
|
---
|
|
|
|
## STEP E — Screen composition
|
|
|
|
When user asks for a new screen (`WBP_MainMenu`, `WBP_HUD_InGame`, ...):
|
|
|
|
1. Sketch hierarchy in text first:
|
|
```
|
|
WBP_MainMenu (RootCanvas)
|
|
WBP_HUDPanel "title-bar" (anchored top, span)
|
|
WBP_Label H1 "GAME TITLE"
|
|
WBP_Card "menu-list" (centered, 480x600)
|
|
WBP_Button Primary "Continue"
|
|
WBP_Button Ghost "New Game"
|
|
WBP_Button Ghost "Settings"
|
|
WBP_Button Danger "Quit"
|
|
WBP_Label Caption "v0.4.2 • Build 2026-05-28" (bottom-left)
|
|
```
|
|
2. Confirm with user (one screen = one confirmation step, not per widget).
|
|
3. Build with `widget_op apply_tree` (single transaction, single undo).
|
|
4. Compile + screenshot.
|
|
5. Iterate per Step D loop.
|
|
|
|
---
|
|
|
|
## Naming & paths (enforced)
|
|
|
|
| Asset | Path | Naming |
|
|
|---|---|---|
|
|
| Theme DataAsset | `/Game/UI/Theme/` | `DA_UITheme_*` |
|
|
| Base components | `/Game/UI/Components/` | `WBP_<Name>` |
|
|
| Screens | `/Game/UI/Screens/` | `WBP_<Screen>` (no prefix like "Menu_" — flat) |
|
|
| Materials | `/Game/UI/Materials/` | `M_UI_*`, `MI_UI_*` |
|
|
| Icons | `/Game/UI/Icons/` | `T_UI_Icon_<verb>_<noun>` |
|
|
| Frame brushes | `/Game/UI/Frames/` | `T_UI_Frame_*` |
|
|
|
|
If asked to create UI assets outside these paths, ask why. Probably a mistake.
|
|
|
|
---
|
|
|
|
## Things NEVER to do (anti-patterns AI agents fall into)
|
|
|
|
1. ❌ Hardcoded color `FLinearColor(0.2, 0.2, 0.2)` anywhere. → Use theme.
|
|
2. ❌ Hardcoded padding `Padding=8`. → Use `Theme->Spacing[1]`.
|
|
3. ❌ One giant CanvasPanel screen with absolute coords for everything. → Use HorizontalBox/VerticalBox/Grid composition.
|
|
4. ❌ "Just add a `Border` widget" for a card. → Use `WBP_Card` component.
|
|
5. ❌ Per-screen unique button styling. → Add a variant to `WBP_Button`.
|
|
6. ❌ Default UE font (Roboto). → Always pick from `Theme->Type`.
|
|
7. ❌ Default `Button` widget visible to the user. → Always wrap in `WBP_Button` because state animations live there.
|
|
8. ❌ Skip Step D. → If you didn't screenshot, you didn't finish.
|
|
9. ❌ Generate icons inline as text "≡" or "✕". → Use `WBP_Icon` with a real Texture2D.
|
|
|
|
---
|
|
|
|
## Required first action for any UI session
|
|
|
|
```
|
|
discover_op find_bp_by_parent { parent_class_path: "/Script/ProjectUI.ProjectUITheme" }
|
|
```
|
|
- 0 hits → STEP A (build theme).
|
|
- 1+ hits → fetch the `DA_UITheme` palette via `validation_op get_references` or read directly, cache token names in your working memory.
|
|
|
|
```
|
|
discover_op find_bp_by_parent { parent_class_path: "/Script/UMG.UserWidget" }
|
|
```
|
|
List existing components. If `/Game/UI/Components/WBP_Button` missing → STEP B.
|
|
|
|
Then and only then proceed to the user's actual request.
|
|
|
|
---
|
|
|
|
## Why this works
|
|
|
|
Without this guide, AI generates UMG by sampling "what UMG typically looks like
|
|
on GitHub" — boring, generic, grey. With this guide:
|
|
|
|
- **Decisions are constrained** (theme tokens, component variants) → no more "AI inventing aesthetics from scratch every time".
|
|
- **Composition replaces design** → LLMs are great at composing; bad at painting.
|
|
- **Vision loop closes the feedback gap** → AI sees its output, judges it, fixes it.
|
|
- **References parse to data**, not vibes → reproducible, debuggable, diffable.
|
|
|
|
Result: shippable UI in 1-2 iterations instead of 15.
|