Files
unreal-engine-mcp-system-pl…/UI_WORKFLOW.md
Bonchellon eba71c4ca8 Unified, portable Unreal Engine MCP system plugin
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>
2026-06-24 01:07:24 +03:00

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) 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.