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>
12 KiB
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):
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)andSetState(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
WidgetAnimationper state inWBP_Button, copy pattern to others. No animation = feels dead.
Optional stylized overlays
WBP_HUDPanelcan 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_LabelMono variant can use a chromatic-aberration material instance.- Background of every HUD piece: prefer a themed
MI_UI_Surfacematerial 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, ...):
- 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) - Confirm with user (one screen = one confirmation step, not per widget).
- Build with
widget_op apply_tree(single transaction, single undo). - Compile + screenshot.
- 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)
- ❌ Hardcoded color
FLinearColor(0.2, 0.2, 0.2)anywhere. → Use theme. - ❌ Hardcoded padding
Padding=8. → UseTheme->Spacing[1]. - ❌ One giant CanvasPanel screen with absolute coords for everything. → Use HorizontalBox/VerticalBox/Grid composition.
- ❌ "Just add a
Borderwidget" for a card. → UseWBP_Cardcomponent. - ❌ Per-screen unique button styling. → Add a variant to
WBP_Button. - ❌ Default UE font (Roboto). → Always pick from
Theme->Type. - ❌ Default
Buttonwidget visible to the user. → Always wrap inWBP_Buttonbecause state animations live there. - ❌ Skip Step D. → If you didn't screenshot, you didn't finish.
- ❌ Generate icons inline as text "≡" or "✕". → Use
WBP_Iconwith 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_UIThemepalette viavalidation_op get_referencesor 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.