posted an update —

Heroes Required: Building the Prototype With the Machine That Knows the Design

From design documents to a working UI in a single session — then porting it to Horizon · devlog gamedev unity ai mcp


Post 1 covered the rules engine. Post 2 covered the content pipeline. This post is about the part we didn't expect to talk about: using an AI coding agent connected directly to Unity to build the entire game interface from the design documents, without touching the Unity Editor by hand.

This is the part that changed how we think about prototyping.


The Constraint That Made This Work

We had a complete design. The GDD defined the session loop, the data schema, the bridge contract between backend and client, and the visual hierarchy of every screen. What we didn't have was time to translate all of that into Unity scenes manually.

The Unity MCP server connects Claude directly to a live Unity Editor instance. Instead of telling the editor what to do through menus and inspectors, you describe what you want to exist and the agent writes C# code that creates it.

The decision we made early: don't build scenes interactively. Build builders — Editor scripts that construct scenes programmatically from data, so the UI can be rebuilt from scratch any time the data changes.


Code That Builds Code

The entire UI lives in five Editor scripts under Assets/Editor/. Each one is a static class with a [MenuItem] method that creates a complete Unity scene when triggered:

Tools ▸ Heroes Required ▸ Scenes ▸ Build Notice Board
Tools ▸ Heroes Required ▸ Scenes ▸ Build Encounter
Tools ▸ Heroes Required ▸ Scenes ▸ Build Party
Tools ▸ Heroes Required ▸ Scenes ▸ Build Guild Hub
Tools ▸ Heroes Required ▸ Scenes ▸ Build Bootstrap (runtime test)

Every primitive — image panels, text labels, buttons, accent strips — is created through a shared HrUiKit library of factory methods. HrPalette defines the brand colours. Nothing is placed by hand.

// From HrUiKit — the entire UI vocabulary is these four calls
public static Image  Image(string name, Transform parent, Color c, Sprite sprite = null)
public static Image  Icon(string name, Transform parent, Sprite sprite)
public static TextMeshProUGUI Text(string name, Transform parent, string text, ...)
public static Button Button(string name, Transform parent, string label, Color c, ...)

Position is declared as anchor ratios, not pixel coordinates — the canvas scales to mobile portrait (1080×1920, match height) so the same numbers work at any resolution. A full screen layout is about 20 lines of anchor calls:

var bg     = HrUiKit.Image("Board_BG", canvas.transform, HrPalette.WoodFrame, frame);
HrUiKit.Fill(bg.rectTransform);

var header = HrUiKit.Text("Header", canvas.transform, "NOTICE  BOARD", 90, HrPalette.Gold);
HrUiKit.Anchor(header.rectTransform, 0.05f, 0.9f, 0.95f, 0.985f);

The MCP agent wrote all of this from a brief: "build a portrait-mode fantasy UI with parchment cards, a wood-frame border, and a corkboard background. Use the HONETi asset pack textures at these paths." It read the asset paths from the project, matched them to the design, and produced the builder scripts in one pass.


The Integration That Made the UI Real

The Notice Board builder does something the others don't: it reads the actual content engine output.

// NoticeBoardBuilder.LoadFromExport() — THE integration point
var path = Path.Combine(Application.streamingAssetsPath, "world_export_sample.json");
var export = JsonUtility.FromJson<WorldExportPayload>(File.ReadAllText(path));
foreach (var e in export.encounters)
{
    list.Add(new CDef
    {
        Id = e.id, Title = e.title, Reward = e.reward,
        Power = e.powerLevel, MinPlayers = e.minPlayers,
        Mode  = e.minPlayers > 1 ? $"GROUP {e.minPlayers}" : "SOLO",
        Accent = e.powerLevel < 30 ? HrPalette.Safe
               : e.powerLevel < 100 ? HrPalette.Risky : HrPalette.Mystery,
    });
}

The workflow is:

  1. Backend generates a new world state
  2. Tools ▸ Backend ▸ Pull Live Export calls GET /api/world/export and writes the result to StreamingAssets/world_export_sample.json
  3. Tools ▸ Scenes ▸ Build Notice Board rebuilds the scene from that file
  4. The board now shows the real quest cards with the real titles, rewards, and power ratings that the content engine produced

Change what the generator produces, rebuild, the board changes. No manual data entry. No placeholder text that someone has to remember to update.

Power level drives card colour automatically — green accent for safe contracts, amber for risky ones, crimson for mystery-tier. The NPC mood from dialogueMood drives the encounter screen's colour and approach logic. The data schema we designed for the backend became the visual language of the UI without a translation step.


Art References, Not Art

The bridge contract has a deliberate decision about images: the backend never ships binary data. It ships prompts.

public sealed class ArtRef
{
    public int Revision { get; init; }          // bumped when narrative state warrants new art
    public string Prompt { get; init; }         // derived from current mood, plot beats, accumulated change
    public List<string> Tags { get; init; }
    public bool UsePreviousAsReference { get; init; } = true; // img2img continuity hint
}

Each NPC and each encounter contract has an ArtRef. The art key is deterministic: Assets/HeroArt/npc_{NpcId}.png for characters, Assets/HeroArt/contract_{Id}.png for mission cards.

// EncounterBuilder.cs — portrait slot
var pArt = HrUiKit.HeroArt("npc_" + NpcId);  // → Assets/HeroArt/npc_2001.png
if (pArt != null) HrUiKit.Anchor(HrUiKit.Icon("Art", portrait.transform, pArt).rectTransform, ...);
else HrUiKit.Fill(HrUiKit.Text("ph", portrait.transform, "ART", 40, HrPalette.Gold).rectTransform);

If the file exists at that path, it slots in. If it doesn't, the UI shows a scroll icon placeholder. The scene builder doesn't know or care whether the image has been generated yet — it just checks the path.

The Horizon developer skill took the same prompt data and used it to describe character and location art for generation: not the technical format (PNG at a path), but the same underlying thing — the backend's current narrative state expressed as a visual description. An NPC who's been marked wary after three weeks of failed expeditions has a different Prompt than they had at season launch. The art reflects the story.


The Session Log

AgentHistory.txt in the project root captures what MCP actually did to build this. The pattern is consistent throughout:

Read the existing shape. Write the next piece. Run the tests. Report the count. Ask what's next.

The backend went from domain entities to a running API in one session: domain model → simulation engine → export pipeline → REST controllers → DI wiring → Hangfire scheduling. 144 tests, 0 failures, all written by the agent against the design documents.

Then the Unity side: the agent read that the unity/ folder was empty ("greenfield"), scaffolded the script structure, wrote WorldSyncService, WorldManager, NpcBehaviorApplicator, and the [Serializable] DTOs that mirror the backend JSON — all in one pass, all matching the bridge contract we'd defined.

The editor builders followed: the agent read the HONETi asset paths from the project, matched them to the palette from the GDD, and produced five scene builders that construct the full session loop from scratch on demand.

The only manual work was approving what it produced.


The Session Loop That Came Out of It

Five scenes, one loop:

04_GuildHub       ← session start; gold, renown, week, vassal roster
      ↓ "To the Notice Board"
01_NoticeBoard    ← live encounter cards from the content engine
      ↓ ACCEPT
03_Party          ← vassal selection, ≤3, combined power vs requirement
      ↓ DEPLOY
02_Encounter      ← NPC portrait, mood, three approach cards
      ↓ CONTINUE
04_GuildHub       ← back with updated gold/renown/consequences

The Encounter screen shows an NPC portrait (if generated), their mood in the HrPalette.Risky amber or HrPalette.Champion crimson, and three approach cards colour-coded by identity — Champion, Diplomat, Shadow. The outcome text is driven by the NPC's mood combined with the chosen approach. A WARY NPC responds differently to the Champion approach than a WELCOMING one.

All of this was running in Play mode, reading from the live backend, before we touched the Horizon tooling.


Where We Are Now

The Horizon developer skill took over once the Unity prototype had a working loop. The asset pipeline is the same: the backend generates ArtRef prompts, images are produced and placed at the deterministic paths, and the UI slots them in. The data schema didn't change — the same WorldExportPayload, the same encounter cards, the same NPC mood values — just a different runtime consuming them.

The gameplay scripts are being wired against the live content: the world the generator has been running for several weeks of testing, with real NPC mutation history, real relationship scores, and real vassal drama accumulated across simulated expeditions.

What we're testing now is the thing that was always the real question: when an NPC has been through five weeks of weekly mutation, has relationships with specific vassal types, and has an ArtRef Prompt that reflects all of that accumulated state — does the encounter actually feel different? Does the quest feel like it was written for this NPC in this week, or does it feel like a procedurally generated filler contract?

That's the question the Unity prototype couldn't answer. It required the live world.

Next post: what it looks like when the content engine's week-12 output runs against real players, and what the mutation logs tell us about whether narrative pressure is doing its job.

Log in or sign up for Devpost to join the conversation.