10xkeletonMDC-UI Framework

MDC-UI: AI-First 선언적 게임 UI 프레임워크

Unity에서 Prefab 없이, JSON 선언만으로 모바일 게임 UI를 만드는 프레임워크

바이브코딩(Vibe Coding) 시대의 게임 UI 개발 패러다임


목차

Part I: 왜, 그리고 무엇인가

  1. 왜 이 프레임워크를 만들었는가
  2. 핵심 철학: JSON = 위젯 트리
  3. 아키텍처 전체 조감도

Part II: 핵심 시스템 4. Canvas Unit 좌표계 5. JSON 스크린 포맷 6. 요소 타입 레퍼런스 7. 레이아웃 시스템 8. 데이터 바인딩 9. 액션 시스템 10. 디자인 토큰 (UITheme)

Part III: 도구와 워크플로우 11. Runtime UI Editor 12. ValueResolver 표현식 언어 13. 실전 튜토리얼 14. 프로그레시브 개발 워크플로우 15. AI와 함께 쓰는 법 (바이브코딩)

Part IV: 비교와 분석 16. 기존 방식과의 비교

Part V: 한계와 미래 17. 솔직한 한계와 단점 18. 극복 로드맵 19. 발견된 개선점과 기술 부채 20. FAQ


1. 왜 이 프레임워크를 만들었는가

문제: Unity UI 개발의 3가지 고통

고통 1: Prefab 지옥

Unity의 표준 UI 워크플로우는 Prefab 기반입니다. 버튼 하나를 만들려면:

  1. Hierarchy에서 우클릭 → UI → Button
  2. Inspector에서 속성 하나하나 클릭해서 설정
  3. 위치/크기를 마우스로 드래그
  4. Prefab으로 저장

화면 하나에 50개 요소가 있으면 이 작업을 50번 반복합니다. 수정할 때도 마찬가지. 사람이 하기엔 너무 반복적이고, AI가 하기엔 시각적 도구라 불가능합니다.

고통 2: 반응형의 부재

모바일 게임은 수백 가지 화면 비율에서 동작해야 합니다:

  • iPhone SE (16:9) vs iPhone 15 Pro (19.5:9)
  • iPad (4:3) vs Galaxy Fold (21:9)

Prefab의 고정 좌표는 기기마다 깨집니다. RectTransform의 anchor/pivot/offset 조합은 이해하기 어렵고, 기기별 테스트가 필수입니다.

고통 3: AI 협업 불가

Prefab은 바이너리(또는 복잡한 YAML)입니다. AI에게 "이 버튼을 오른쪽으로 10% 옮겨줘"라고 말할 수 없습니다. AI는 Unity Editor의 마우스를 조작할 수 없으니까요.

해답: 텍스트 기반 선언적 UI

이 프레임워크의 핵심 아이디어는 단순합니다:

UI의 모든 구조를 JSON 텍스트로 선언한다. C#은 데이터만 연결한다.

이렇게 하면:

  • AI가 UI를 만들 수 있습니다 — JSON은 텍스트이므로 AI가 직접 생성/수정 가능
  • 사람도 읽을 수 있습니다 — Flutter 위젯 트리와 1:1 대응
  • 반응형이 자동입니다sw:0.9 = "화면 폭의 90%"
  • 런타임에 수정 가능합니다 — Play Mode에서 바로 편집하고 저장

이것이 바이브코딩입니다. 의도를 말하면 AI가 UI를 만들고, Runtime Editor로 미세 조정하고, Save하면 영구 반영.


2. 핵심 철학: JSON = 위젯 트리

Flutter와의 1:1 매핑

이 프레임워크는 Flutter의 위젯 모델을 Unity에 이식한 것입니다.

FlutterMDC-UI JSON역할
Column"type": "column"세로 배치
Row"type": "row"가로 배치
Expanded"h": "expand"남은 공간 채우기
Container"type": "panel"배경+패딩 컨테이너
Text"type": "text"텍스트 표시
ElevatedButton"type": "button"버튼
SingleChildScrollView"type": "scroll"스크롤 컨테이너
ListView.builder"type": "listview"가상화 리스트
GridView"type": "foreach", "layout": "grid"그리드 뷰
Image"type": "image"이미지 표시
Visibility"visible": "condition"조건부 표시
LinearProgressIndicator"type": "progressbar"진행 바
Slider"type": "slider"슬라이더

원칙

  1. JSON이 구조의 Single Source of Truth — UI의 레이아웃, 색상, 폰트, 크기는 전부 JSON
  2. C#은 데이터 바인딩만 — SetBinding으로 텍스트 값 연결, RegisterAction으로 콜백 연결
  3. 절대 좌표 나열 금지 — 반드시 column/row/expand로 선언
  4. Runtime Editor에서 편집 → JSON 저장 → 영구 반영

3. 아키텍처 전체 조감도

┌─────────────────────────────────────────────────────────┐
│                    Runtime UI Editor                     │
│  (Play Mode에서 UI 선택 → 속성 편집 → JSON 저장)          │
└────────────────────────┬────────────────────────────────┘
                         │ 편집/저장
┌────────────────────────▼────────────────────────────────┐
│              JSON Screen Files (.json)                   │
│  Resources/UI/Screens/village_screen.json                │
│  Resources/UI/Screens/settings_screen.json               │
│  ...                                                     │
└────────────────────────┬────────────────────────────────┘
                         │ Load
┌────────────────────────▼────────────────────────────────┐
│              UIScreenBuilder (엔진)                       │
│  JSON 파싱 → ElementDef 트리 → Unity GameObject 빌드       │
│                                                          │
│  ┌──────────────┐  ┌──────────────┐  ┌──────────────┐   │
│  │ ValueResolver│  │  UITheme     │  │ JsonElement   │   │
│  │ (표현식 해석) │  │ (디자인 토큰)│  │   Marker     │   │
│  └──────────────┘  └──────────────┘  └──────────────┘   │
└────────────────────────┬────────────────────────────────┘
                         │ Build
┌────────────────────────▼────────────────────────────────┐
│           Unity UI Hierarchy (런타임)                     │
│  Canvas → Layer_Screen → [Screen Root]                   │
│            → Layer_Popup → [Dialogs]                     │
│            → Layer_Toast → [Notifications]               │
│            → Layer_Transition → [Fade overlay]           │
└─────────────────────────────────────────────────────────┘

핵심 컴포넌트

컴포넌트파일역할
UIScreenBuilderUIScreenBuilder.csJSON → Unity UI 변환 엔진
ValueResolverUIScreenBuilder.cs 내부sw:0.9, sh:0.08 등 표현식 파싱
UIThemeUITheme.cs디자인 토큰 (색상/폰트/간격)
UIHelperUIHelper.csUI 요소 팩토리 + cu 좌표계
UIComponentsUIComponents.csScrollContainer, FlexRow 등 복합 컴포넌트
UILayerManagerUILayerManager.csZ-order 레이어 관리 (Screen/Popup/Toast)
RuntimeUIEditorRuntimeUIEditorWindow.csPlay Mode 비주얼 에디터
PropertyPanelPropertyPanel.cs속성 편집 패널 (에디터)

4. Canvas Unit 좌표계

개념

모든 UI 크기/위치는 Canvas Unit (cu) 로 표현됩니다. Unity의 CanvasScaler가 기기 해상도를 자동 변환합니다.

기기 해상도 (1170×2532 px)
    ↓ CanvasScaler (matchWidthOrHeight = 0, ref: 1080×1920)
Canvas 좌표 (1080 × ~2340 cu)
    ↓ UIHelper.SW/SH/Font
비율 기반 값 (sw:0.9 = 972 cu)

좌표 공간

         (0, +HalfH)  ← 화면 상단 (기기마다 다름!)
              │
 (-HalfW, 0) ┼ (+HalfW, 0)  ← 좌우 (항상 ±540)
              │
         (0, -HalfH)  ← 화면 하단
  • 폭(X축): 모든 기기에서 동일 (1080 cu, HalfW = 540)
  • 높이(Y축): 기기마다 다름! (iPhone SE: ~1920, iPhone 15 Pro: ~2340)
  • 원점: 화면 중앙 = (0, 0)

사이징 함수

이것만 사용합니다. 리터럴 픽셀값은 절대 금지입니다.

// 폭 기반 (고정)
SW(0.9f)      // 화면 안전영역 폭의 90% → ~972 cu (모든 기기 동일)
SW(0.48f)     // 중간 버튼 폭

// 높이 기반 (가변!)
SH(0.08f)     // 캔버스 높이의 8% → ~154 cu (16:9) or ~187 cu (19.5:9)
SH(0.005f)    // 작은 간격

// 폰트 (최소 20cu 보장)
Font(0.044f)  // 캔버스 폭의 4.4% → ~48 cu
Font(0.019f)  // 본문 → 최소 보장값 20 cu

// 위치
HalfH         // 화면 상단 Y (기기마다 다름)
HalfW         // 화면 우측 X (항상 540)

JSON에서의 표현

{
  "w": "sw:0.9",           // 화면 폭 90%
  "h": "sh:0.08",          // 화면 높이 8%
  "x": "0",                // 중앙
  "y": "HalfH - sh:0.1",  // 상단에서 10% 아래
  "font": "0.044",         // 폭 4.4% 폰트
  "gap": "sh:0.005"        // 높이 0.5% 간격
}

5. JSON 스크린 포맷

기본 구조

모든 화면은 하나의 JSON 파일로 정의됩니다:

{
  "screen": "SettingsScreen",
  "bg": "Bg.Dark",
  "elements": [
    {
      "id": "rootColumn",
      "type": "column",
      "w": "sw:1.0",
      "h": "sh:1.0",
      "children": [
        { "type": "text", "text": "설정", "font": "Display.Small" },
        { "type": "scroll", "h": "expand", "children": [...] },
        { "type": "button", "label": "저장", "action": "onSave" }
      ]
    }
  ]
}

최상위 필드

필드타입설명
screenstringC# 스크린 클래스명
bgstring배경색 (테마 토큰 또는 #RRGGBB)
elementsarray최상위 요소 배열

요소 공통 필드

모든 요소가 사용할 수 있는 필드:

필드타입예시설명
idstring"titleText"고유 식별자 (바인딩/에디터용)
typestring"text"요소 타입 (필수)
xstring"0", "sw:0.1"X 위치
ystring"-sh:0.05"Y 위치
wstring"sw:0.8", "pw:1.0"
hstring"sh:0.04", "expand"높이
visiblestring"isLoggedIn", "tab=1"조건부 표시
anchorstring"TC", "BL"앵커 프리셋
marginstring"sh:0.01"외부 여백
alphafloat0.8투명도
platformstring"ios", "android"플랫폼 필터
onBuildstring"setupPortrait"빌드 후 C# 콜백

6. 요소 타입 레퍼런스

text — 텍스트 표시

{
  "type": "text",
  "text": "$playerName",
  "font": "Headline.Medium",
  "color": "Text.Primary",
  "align": "center",
  "fontStyle": "bold",
  "bestFit": true,
  "outlineColor": "#000000",
  "outlineWidth": "1.5"
}
속성설명기본값
text표시 텍스트 ($key로 바인딩)""
font폰트 크기 (토큰 또는 숫자)"Body.Medium"
color글자색 (토큰 또는 HEX)"Text.Primary"
align정렬 (left, center, right)"center"
fontStyle스타일 (bold, italic, bolditalic)없음
bestFit자동 크기 조절false
outlineColor외곽선 색상없음
outlineWidth외곽선 두께없음

button — 버튼

{
  "type": "button",
  "label": "시작하기",
  "bg": "Accent.Primary",
  "labelFont": "Headline.Small",
  "onClick": "onStartGame",
  "compact": false
}
속성설명
label버튼 텍스트 ($key 바인딩 가능)
bg배경색 ($key 바인딩 가능)
labelFont라벨 폰트 크기
onClick / action클릭 콜백명
compact작은 버튼 모드 (최소 높이 축소)

panel — 컨테이너

{
  "type": "panel",
  "bg": "#1A1A2E",
  "sprite": "Sprites/UI/frame",
  "fit": "contain",
  "padding": "sh:0.02 sw:0.03",
  "children": [...]
}
속성설명
bg배경색
sprite배경 스프라이트
fit"stretch", "contain", "cover", "anchor"
padding내부 여백 (CSS 순서)
children자식 요소 (절대 위치)

column — 세로 배치

{
  "type": "column",
  "gap": "sh:0.008",
  "padding": "sh:0.02 0 0 0",
  "mainAxisAlign": "start",
  "crossAxisAlign": "center",
  "bg": "Bg.Panel",
  "children": [
    { "type": "text", "text": "제목" },
    { "type": "scroll", "h": "expand", "children": [...] },
    { "type": "button", "label": "확인", "h": "sh:0.04" }
  ]
}
속성설명기본값
gap자식 간 간격0
padding내부 여백0
mainAxisAlign세로 정렬 (start, center, end, spaceBetween)start
crossAxisAlign가로 정렬 (start, center, end, stretch)center
bg배경색 (선택)없음
children자식 요소들 (위→아래 자동 배치)

특수값: 자식의 h: "expand" → 나머지 공간을 채움 (Flutter의 Expanded)

row — 가로 배치

{
  "type": "row",
  "gap": "sw:0.01",
  "mainAxisAlign": "center",
  "children": [
    { "type": "button", "label": "A", "w": "rw:1" },
    { "type": "button", "label": "B", "w": "rw:2" },
    { "type": "button", "label": "C", "w": "rw:1" }
  ]
}
  • 자식의 w: "rw:N" → 가중치 기반 폭 배분 (B는 A/C의 2배 폭)
  • 자식의 w: "pw:0.3" → 부모 폭의 30%

scroll — 스크롤 컨테이너

{
  "type": "scroll",
  "direction": "vertical",
  "h": "expand",
  "bg": "#000000",
  "gap": "sh:0.008",
  "padding": "sh:0.01",
  "children": [
    { "type": "panel", "h": "sh:0.15" },
    { "type": "panel", "h": "sh:0.15" },
    { "type": "panel", "h": "sh:0.15" }
  ]
}
  • 자식 높이 합 + gap + padding → 자동 스크롤 영역 계산
  • direction: "vertical" (기본) 또는 "horizontal"

image — 이미지

{
  "type": "image",
  "sprite": "Sprites/Heroes/knight",
  "fit": "contain",
  "color": "#FFFFFF",
  "preserveAspect": true
}

foreach — 데이터 반복 (리스트/그리드)

{
  "type": "foreach",
  "dataSource": "$menuItems",
  "layout": "grid",
  "cols": 2,
  "cellH": "sh:0.12",
  "gap": "sh:0.008",
  "onItemClick": "onMenuSelect",
  "template": [
    {
      "type": "panel",
      "bg": "$item.bgColor",
      "children": [
        { "type": "image", "sprite": "$item.icon", "fit": "contain" },
        { "type": "text", "text": "$item.name", "font": "Headline.Small" },
        { "type": "text", "text": "$item.desc", "color": "Text.Secondary" }
      ]
    }
  ]
}
속성설명
dataSource바인딩 키 ($keyList<Dictionary<string,object>>)
layout"list" (세로), "grid" (격자), "row" (가로)
cols격자 열 수 (layout: "grid" 전용)
cellH셀 높이
cellW셀 폭 (기본: 자동 계산)
gap셀 간격
onItemClick아이템 클릭 콜백 (인덱스 전달)
template셀 템플릿 (아이템마다 반복)

listview — 가상화 리스트 (대량 데이터)

{
  "type": "listview",
  "dataSource": "$heroes",
  "cellH": "sh:0.08",
  "cols": 1,
  "gap": "sh:0.005",
  "template": [
    { "type": "text", "text": "$item.name" }
  ]
}
  • 가상화: 화면에 보이는 셀만 렌더링 (1000개+ 아이템도 성능 유지)
  • template 또는 C# RegisterListView() 콜백 방식

progressbar — 진행 바

{
  "type": "progressbar",
  "valueBinding": "$expPercent",
  "trackColor": "#1E1E2E",
  "fillColor": "Accent.Primary",
  "text": "EXP: {$currentExp}/{$maxExp}"
}

slider — 슬라이더

{
  "type": "slider",
  "label": "배경음 (BGM)",
  "valueBinding": "$bgmVolume",
  "trackColor": "#33334D",
  "fillColor": "#4DA6E6",
  "action": "onBgmChange"
}

7. 레이아웃 시스템

Column + Row = 모든 레이아웃

Flutter처럼 columnrow의 조합으로 모든 레이아웃을 구성합니다.

기본 패턴: 3존 화면

{
  "id": "rootColumn",
  "type": "column",
  "w": "sw:1.0",
  "h": "sh:1.0",
  "children": [
    {
      "id": "topBar",
      "type": "row",
      "h": "sh:0.06",
      "children": [...]
    },
    {
      "id": "content",
      "type": "scroll",
      "h": "expand",
      "children": [...]
    },
    {
      "id": "bottomBar",
      "type": "row",
      "h": "sh:0.05",
      "children": [...]
    }
  ]
}
┌─────────────────────┐
│    topBar (고정)     │  ← h: "sh:0.06"
├─────────────────────┤
│                     │
│   content (가변)    │  ← h: "expand" (남은 공간 전부)
│                     │
├─────────────────────┤
│   bottomBar (고정)  │  ← h: "sh:0.05"
└─────────────────────┘

expand — 남은 공간 채우기

Column 안에서 h: "expand" 를 가진 자식은 나머지 높이를 전부 차지합니다:

{
  "type": "column",
  "h": "sh:1.0",
  "children": [
    { "type": "text", "h": "sh:0.05" },
    { "type": "scroll", "h": "expand" },
    { "type": "button", "h": "sh:0.04" }
  ]
}

계산: expand 높이 = 전체(sh:1.0) - 텍스트(sh:0.05) - 버튼(sh:0.04) - gap

탭 시스템 (visible 조건)

{
  "type": "column",
  "visible": "activeTab=0",
  "children": [...]
},
{
  "type": "column",
  "visible": "activeTab=1",
  "children": [...]
}

C#에서 builder.SetBinding("activeTab", "1") → 두 번째 탭만 표시

네비게이션 바 (row + 균등 분배)

{
  "type": "row",
  "gap": "sw:0.01",
  "children": [
    { "type": "button", "label": "마을", "w": "rw:1", "action": "onTab0" },
    { "type": "button", "label": "탐험", "w": "rw:1", "action": "onTab1" },
    { "type": "button", "label": "도전", "w": "rw:1", "action": "onTab2" },
    { "type": "button", "label": "설정", "w": "rw:1", "action": "onTab3" }
  ]
}

rw:1 = 모든 버튼이 동일한 가중치 → 균등 분배

재화 바 (row + image + text)

{
  "type": "row",
  "mainAxisAlign": "center",
  "children": [
    { "type": "image", "sprite": "Sprites/UI/icon_gold", "w": "ph:0.65", "h": "ph:0.65" },
    { "type": "text", "text": "$goldText", "w": "pw:0.14" },
    { "type": "image", "sprite": "Sprites/UI/icon_gem", "w": "ph:0.65", "h": "ph:0.65" },
    { "type": "text", "text": "$gemText", "w": "pw:0.14" }
  ]
}

8. 데이터 바인딩

개념

JSON은 구조를 선언하고, C#은 데이터를 연결합니다. 이 연결을 바인딩이라 합니다.

스칼라 바인딩

// C# — 데이터 연결
builder.SetBinding("playerName", "용사")
       .SetBinding("currentHP", 75)
       .SetBinding("maxHP", 100)
       .SetBinding("isVIP", true);
// JSON — 데이터 사용
{ "type": "text", "text": "$playerName" }
{ "type": "text", "text": "HP: {$currentHP}/{$maxHP}" }
{ "type": "panel", "visible": "isVIP" }
표현식설명
$key바인딩 값 전체를 텍스트로 표시
{$key}문자열 보간 (문장 안에 삽입)
visible: "key"key가 truthy면 표시
visible: "key=value"key의 값이 value와 같으면 표시

컬렉션 바인딩 (foreach/listview)

// C# — 리스트 데이터 연결
var items = new List<Dictionary<string, object>>
{
    new() { { "name", "불검" }, { "price", "1000G" }, { "icon", "Sprites/sword_fire" } },
    new() { { "name", "얼음방패" }, { "price", "800G" }, { "icon", "Sprites/shield_ice" } }
};
builder.SetBinding("shopItems", items);
// JSON — 템플릿에서 $item.xxx로 접근
{
  "type": "foreach",
  "dataSource": "$shopItems",
  "template": [
    {
      "type": "row",
      "children": [
        { "type": "image", "sprite": "$item.icon" },
        { "type": "text", "text": "$item.name" },
        { "type": "text", "text": "$item.price" }
      ]
    }
  ]
}

조건부 표시 (visible)

// 불리언 체크
{ "visible": "hasNotification" }

// 값 비교
{ "visible": "activeTab=2" }

// foreach 안에서 아이템 필드 체크
{ "visible": "$item.isUnlocked" }
{ "visible": "$item.rarity=Legendary" }

9. 액션 시스템

기본 액션 (void 콜백)

builder.RegisterAction("onStartGame", () => {
    GameManager.Instance.StartExpedition();
});
{ "type": "button", "label": "출발!", "onClick": "onStartGame" }

인덱스 액션 (foreach 아이템 클릭)

builder.RegisterIndexedAction("onHeroSelect", (int index) => {
    var hero = heroes[index];
    ShowHeroDetail(hero);
});
{
  "type": "foreach",
  "dataSource": "$heroes",
  "onItemClick": "onHeroSelect",
  "template": [...]
}

값 액션 (슬라이더)

builder.RegisterFloatAction("onVolumeChange", (float vol) => {
    AudioListener.volume = vol;
});
{ "type": "slider", "action": "onVolumeChange", "valueBinding": "$volume" }

빌드 콜백 (onBuild)

Unity 컴포넌트를 직접 부착해야 할 때 사용:

builder.RegisterBuildCallback("attachAnimator", (GameObject go) => {
    var anim = go.AddComponent<SpriteAnimator>();
    anim.PlayIdle();
});
{ "type": "image", "onBuild": "attachAnimator" }

onBuild는 최후의 수단입니다. JSON으로 표현할 수 없는 커스텀 컴포넌트(SpriteAnimator, RectMask2D 등)에만 사용합니다.


10. 디자인 토큰 (UITheme)

개념

디자인 토큰은 이름이 붙은 디자인 값입니다. HEX 코드 대신 의미 있는 이름을 사용합니다.

// 나쁜 예 — 의미 불분명
{ "color": "#E5E5F2" }

// 좋은 예 — 의미 명확
{ "color": "Text.Primary" }

색상 토큰

Colors.Bg.Dark          — 화면 배경 (짙은 남색)
Colors.Bg.Panel         — 패널 배경
Colors.Bg.PanelLight    — 밝은 패널

Colors.Text.Primary     — 메인 텍스트 (거의 흰색)
Colors.Text.Secondary   — 보조 텍스트 (회색빛)
Colors.Text.Muted       — 비활성 텍스트

Colors.Accent.Primary   — 강조색 (파랑)
Colors.Accent.Light     — 밝은 강조

Colors.Status.Success   — 성공 (녹색)
Colors.Status.Warning   — 경고 (주황)
Colors.Status.Danger    — 위험 (빨강)
Colors.Status.Gold      — 골드/희귀

Colors.Btn.Confirm      — 확인 버튼
Colors.Btn.Close        — 닫기 버튼
Colors.Btn.Danger       — 위험 버튼

Colors.Bar.Bg           — 프로그레스바 배경
Colors.Bar.Info         — 프로그레스바 채움

폰트 토큰

Typography.Display.Large   — 7.4% (대형 제목)
Typography.Display.Small   — 5.0% (소형 제목)
Typography.Headline.Large  — 4.4% (섹션 헤더)
Typography.Headline.Medium — 4.0%
Typography.Headline.Small  — 3.5%
Typography.Body.Medium     — 2.6% (본문)
Typography.Body.Small      — 2.2%
Typography.Label.Medium    — 2.0% (라벨)
Typography.Caption.Small   — 1.5% (캡션)

JSON에서의 사용

{
  "type": "text",
  "text": "환영합니다",
  "font": "Display.Small",
  "color": "Text.Primary"
}
{
  "type": "button",
  "label": "삭제",
  "bg": "Btn.Danger",
  "labelFont": "Headline.Small"
}

테마 변경

토큰 값은 theme.json에 저장되어 전체 앱의 색상/폰트를 한 번에 변경할 수 있습니다.


11. Runtime UI Editor

개요

Play Mode에서 게임 화면의 UI를 실시간으로 선택, 편집, 저장하는 도구입니다.

실행

메뉴: MDC → Runtime UI Editor (단축키: Cmd+Shift+U)

화면 구성

┌─ 툴바 ──────────────────────────────────────────────────┐
│ [상태] [+Image] [+Text] [+Panel] [Copy] [Save JSON] [QS]│
├──────────────────────┬──────────────────────────────────┤
│  Hierarchy 패널      │  Property 패널                    │
│                      │                                   │
│  ▼ MainCanvas        │  [JSON] id: startBtn              │
│    ▼ Layer_Screen    │  type: button                     │
│      ▼ VillageScreen │                                   │
│        ▶ rootColumn  │  ┌─ Position ─────────────┐       │
│          currencyBar │  │ x: 0     y: -sh:0.05  │       │
│          navBar      │  │ w: sw:0.4  h: sh:0.04 │       │
│  ▶ Layer_Popup       │  └────────────────────────┘       │
│  ▶ Layer_Toast       │                                   │
│                      │  label: "시작하기"                  │
│  [검색...]           │  bg: Accent.Primary               │
│                      │  onClick: onStartGame             │
├──────────────────────┴──────────────────────────────────┤
│ 상태바: VillageScreen > rootColumn > startBtn    [Cmd+Z] │
└─────────────────────────────────────────────────────────┘

핵심 기능

1. Hierarchy 패널

  • 모든 Canvas 아래의 UI 요소를 트리로 표시
  • 펼침/접힘, 검색 필터
  • 클릭하면 Game View에서 선택 하이라이트 (시안색 테두리)
  • Cmd+Click으로 다중 선택 → Wrap 기능

2. Property 패널 — JSON 모드

JSON 선언 요소를 선택하면 원본 표현식을 직접 편집:

  • 위치/크기: sw:0.9, sh:0.08, expand 등 표현식 직접 입력
  • 텍스트: 내용, 폰트 토큰, 정렬
  • 색상: 테마 토큰 선택기 또는 HEX 직접 입력
  • 버튼: 라벨, 배경색, 액션명
  • 보이기 조건: visible 표현식

3. Save JSON

가장 중요한 기능. 편집한 내용을 원본 JSON 파일에 저장합니다.

편집 → Save JSON 클릭 → Resources/UI/Screens/xxx_screen.json 덮어쓰기

다음 Play Mode에서 변경사항이 반영됩니다.

4. Quick Save

빠른 프리셋 저장. Play Mode를 다시 시작해도 자동 로드됩니다.

5. Undo/Redo

Cmd+Z / Cmd+Shift+Z — JSON 스냅샷 기반, 최대 50단계

6. 요소 추가/삭제

  • 화면 루트에 새 요소 추가 (text, button, panel, column, row, scroll 등)
  • 선택한 컨테이너에 자식 추가
  • 삭제, 순서 이동 (Z-Order 위/아래)

7. Wrap 기능

여러 요소를 선택한 후 Column/Row/Panel로 감싸기 → 레이아웃 구조화

8. Dirty 표시

미저장 변경이 있으면:

  • 윈도우 타이틀에 * 표시
  • Save JSON 버튼이 빨간색으로 변경
  • Play Mode 종료 시 경고 다이얼로그

12. ValueResolver 표현식 언어

모든 크기/위치/색상 필드에서 사용하는 표현식 시스템입니다.

크기 접두사

접두사의미예시
sw:화면 안전영역 폭 비율sw:0.9 → 폭의 90%
sh:캔버스 높이 비율sh:0.08 → 높이의 8%
cu:Canvas Unit 직접값cu:10 → 10 cu
pw:부모 폭 비율pw:0.5 → 부모의 50%
ph:부모 높이 비율ph:1.0 → 부모 높이 전체
cw:셀 폭 비율 (foreach)cw:0.3 → 셀 폭의 30%
ch:셀 높이 비율 (foreach)ch:0.8 → 셀 높이의 80%
rw:Row 가중치 (row 전용)rw:2 → 2배 비율
font:폰트 크기font:0.044

키워드

키워드의미
HalfH화면 상단 Y (동적)
HalfW화면 우측 X (고정: 540)
SafeW안전영역 폭
expand남은 공간 전부
auto콘텐츠 기반 크기

산술 연산

"sh:0.1 + cu:5"        → SH(0.1) + 5
"sw:0.8 - sh:0.02"     → SW(0.8) - SH(0.02)
"HalfH - sh:0.1"       → 상단에서 10% 아래

패딩 축약

"sh:0.01"                                     → 4면 모두 동일
"sh:0.01 sw:0.02"                              → 상하 / 좌우
"sh:0.02 sw:0.01 sh:0.03"                      → 상 / 좌우 / 하
"sh:0.02 sw:0.01 sh:0.03 sw:0.015"             → 상 / 우 / 하 / 좌

색상 해석

"Text.Primary"    → 테마 토큰
"#FF6644"         → HEX RGB
"#FF6644CC"       → HEX RGBA
"white"           → Unity 내장 색상

13. 실전 튜토리얼

튜토리얼 1: 첫 번째 화면 만들기

Step 1: JSON 파일 작성

Assets/Resources/UI/Screens/hello_screen.json:

{
  "screen": "HelloScreen",
  "bg": "Bg.Dark",
  "elements": [
    {
      "id": "rootColumn",
      "type": "column",
      "w": "sw:1.0",
      "h": "sh:1.0",
      "mainAxisAlign": "center",
      "gap": "sh:0.02",
      "children": [
        {
          "id": "greeting",
          "type": "text",
          "text": "$userName",
          "font": "Display.Small",
          "color": "Accent.Light",
          "w": "pw:0.8",
          "h": "sh:0.06"
        },
        {
          "id": "message",
          "type": "text",
          "text": "반갑습니다!",
          "font": "Headline.Medium",
          "color": "Text.Secondary",
          "w": "pw:0.6",
          "h": "sh:0.04"
        },
        {
          "id": "startBtn",
          "type": "button",
          "label": "시작하기",
          "bg": "Accent.Primary",
          "w": "pw:0.5",
          "h": "sh:0.045",
          "onClick": "onStart"
        }
      ]
    }
  ]
}

Step 2: C# 스크린 클래스

public static class HelloScreen
{
    public static GameObject Create(Transform parent)
    {
        var builder = UIScreenBuilder.Load("UI/Screens/hello_screen", parent);

        // 데이터 바인딩
        builder.SetBinding("userName", "용사님");

        // 액션 등록
        builder.RegisterAction("onStart", () => {
            Debug.Log("게임 시작!");
        });

        return builder.Build();
    }
}

Step 3: Runtime Editor로 미세 조정

  1. Play Mode 진입
  2. Runtime UI Editor 열기 (Cmd+Shift+U)
  3. Hierarchy에서 greeting 선택
  4. Property에서 font 크기 조정, 색상 변경
  5. Save JSON 클릭 → 영구 반영

튜토리얼 2: 설정 화면 (슬라이더 + 토글)

{
  "screen": "SettingsScreen",
  "bg": "Bg.Dark",
  "elements": [
    {
      "id": "rootColumn",
      "type": "column",
      "w": "sw:0.92",
      "h": "sh:0.9",
      "gap": "sh:0.006",
      "padding": "sh:0.021 0 0 0",
      "children": [
        { "type": "text", "text": "설정", "font": "Display.Small", "color": "Accent.Light" },
        {
          "type": "scroll",
          "h": "expand",
          "children": [
            {
              "type": "column",
              "gap": "sh:0.01",
              "children": [
                { "type": "text", "text": "사운드", "font": "Headline.Small", "color": "Text.Warm" },
                { "type": "slider", "label": "배경음", "valueBinding": "$bgmVol", "action": "onBgm" },
                { "type": "slider", "label": "효과음", "valueBinding": "$sfxVol", "action": "onSfx" },
                { "type": "text", "text": "일반", "font": "Headline.Small", "color": "Text.Warm" },
                { "type": "button", "label": "$vibrationLabel", "bg": "$vibrationBg", "action": "onVibration" }
              ]
            }
          ]
        }
      ]
    }
  ]
}
public static class SettingsScreen
{
    public static GameObject Create(Transform parent)
    {
        var builder = UIScreenBuilder.Load("UI/Screens/settings_screen", parent);

        builder.SetBinding("bgmVol", AudioManager.BGMVolume)
               .SetBinding("sfxVol", AudioManager.SFXVolume)
               .SetBinding("vibrationLabel", Settings.Vibration ? "진동: ON" : "진동: OFF")
               .SetBinding("vibrationBg", Settings.Vibration ? "Btn.Confirm" : "#555555");

        builder.RegisterFloatAction("onBgm", vol => AudioManager.SetBGM(vol))
               .RegisterFloatAction("onSfx", vol => AudioManager.SetSFX(vol))
               .RegisterAction("onVibration", () => {
                   Settings.Vibration = !Settings.Vibration;
                   builder.SetBinding("vibrationLabel", Settings.Vibration ? "진동: ON" : "진동: OFF");
                   builder.SetBinding("vibrationBg", Settings.Vibration ? "Btn.Confirm" : "#555555");
                   builder.RebuildScreen();
               });

        return builder.Build();
    }
}

튜토리얼 3: 상점 화면 (foreach 그리드)

{
  "screen": "ShopScreen",
  "bg": "Bg.Dark",
  "elements": [
    {
      "type": "column",
      "w": "sw:1.0",
      "h": "sh:1.0",
      "children": [
        {
          "type": "row",
          "h": "sh:0.04",
          "children": [
            { "type": "button", "label": "추천", "w": "rw:1", "action": "onTab0" },
            { "type": "button", "label": "보석", "w": "rw:1", "action": "onTab1" },
            { "type": "button", "label": "교환소", "w": "rw:1", "action": "onTab2" }
          ]
        },
        {
          "type": "foreach",
          "dataSource": "$shopItems",
          "layout": "grid",
          "cols": 2,
          "h": "expand",
          "cellH": "sh:0.15",
          "gap": "sh:0.008",
          "onItemClick": "onBuyItem",
          "template": [
            {
              "type": "panel",
              "bg": "$item.cardBg",
              "padding": "sh:0.01",
              "children": [
                { "type": "image", "sprite": "$item.icon", "fit": "contain",
                  "w": "pw:0.4", "h": "ph:0.6", "x": "pw:-0.2", "y": "ph:0.1" },
                { "type": "text", "text": "$item.name", "font": "Headline.Small",
                  "x": "pw:0.1", "y": "ph:0.15" },
                { "type": "text", "text": "$item.price", "color": "Status.Gold",
                  "x": "pw:0.1", "y": "-ph:0.2" },
                { "type": "button", "label": "구매", "bg": "$item.btnColor",
                  "w": "pw:0.35", "h": "ph:0.22", "x": "pw:0.2", "y": "-ph:0.3" }
              ]
            }
          ]
        }
      ]
    }
  ]
}

14. 프로그레시브 개발 워크플로우

핵심 개념: AI 생성 → JSON 선언 → Runtime 폴리싱

이 프레임워크의 가장 강력한 점은 단계적 품질 향상 파이프라인입니다.

Phase 1: AI 생성 (10분)
  "설정 화면 만들어줘. 사운드 슬라이더 2개, 진동 토글, 로그아웃 버튼"
  → AI가 JSON + C# 바인딩 코드 즉시 생성
  → 컴파일 → Play Mode에서 바로 동작

Phase 2: Runtime 폴리싱 (5분)
  → Runtime UI Editor에서 요소 선택
  → 위치/크기/색상/간격 미세 조정 (실시간 Game View 반영)
  → Save JSON → 영구 반영

Phase 3: 데이터 연동 (10분)
  → C#에서 실제 게임 데이터 바인딩
  → 콜백에서 실제 로직 연결
  → 필요 시 foreach/listview로 동적 데이터 표시

Phase 4: 반복 개선 (필요 시)
  → 유저 피드백 → Runtime Editor로 즉시 수정 → Save
  → 재컴파일 없이 UI 변경 완료

왜 이게 빠른가

기존 방식 (Prefab)

의도 → Editor에서 마우스 작업 (20분) → 스크립트 작성 (15분)
→ 컴파일 (30초) → 테스트 → 수정 → 반복...

총 시간: 화면 1개 ≈ 2-4시간

MDC-UI 방식

의도 → AI에게 말하기 (1분) → AI가 JSON+C# 생성 (2분)
→ Play Mode (1초) → Runtime Editor 조정 (5분) → Save

총 시간: 화면 1개 ≈ 15-30분

핵심 차이: 컴파일 제거

Unity 개발에서 가장 큰 시간 낭비는 코드 수정 → 컴파일 대기 → Play Mode 진입 루프입니다.

MDC-UI는 이 루프를 끊습니다:

  • JSON은 코드가 아닙니다 → 수정해도 컴파일 불필요
  • Runtime Editor → Play Mode 안에서 직접 수정
  • Save JSON → 다음 Play Mode에서 자동 반영
기존: 코드 수정 → 컴파일(30s) → Play(5s) → 확인 → 중단 → 반복
MDC: Play Mode 유지 → Editor에서 수정 → 실시간 확인 → Save

유지보수성

JSON = Git-Friendly

- "gap": "sh:0.008"
+ "gap": "sh:0.012"

Prefab diff는 읽을 수 없지만, JSON diff는 한 눈에 뭐가 변했는지 보입니다.

관심사 분리

JSON:  레이아웃, 색상, 폰트, 크기      ← 디자이너가 건드림
C#:    데이터 바인딩, 게임 로직          ← 프로그래머가 건드림
Theme: 전역 디자인 토큰                  ← 공통

같은 파일을 수정할 일이 줄어들어 merge conflict가 감소합니다.

단일 진실 원천

UI 구조가 JSON 한 파일에 있으므로:

  • "이 버튼 어디에 정의되어 있지?" → grep "onStartGame" → 즉시 찾음
  • "이 화면 전체 구조가 뭐지?" → JSON 파일 열면 트리 구조가 보임
  • "이 색상 어디서 쓰이지?" → grep "Accent.Primary" → 전체 사용처

15. AI와 함께 쓰는 법 (바이브코딩)

바이브코딩이란?

바이브코딩(Vibe Coding) 은 개발자가 자연어로 의도를 설명하면 AI가 코드를 생성하는 개발 방식입니다. 이 프레임워크는 바이브코딩에 최적화되어 있습니다.

왜 이 프레임워크가 바이브코딩에 적합한가

1. JSON은 AI의 모국어

AI는 JSON을 완벽하게 이해합니다. 이런 대화가 가능합니다:

개발자: "상점 화면에 2열 그리드로 아이템 목록을 만들어줘. 각 카드에 아이콘, 이름, 가격, 구매 버튼이 있어야 해."

AI: (즉시 foreach + grid + template JSON을 생성)

Prefab이었다면 AI가 할 수 없는 작업입니다. JSON이니까 가능합니다.

2. 구조와 데이터의 분리

AI가 JSON 구조를 만들고, C# 바인딩 코드를 만들면 끝입니다:

AI가 하는 일:
1. JSON 파일 작성 (화면 구조 전체)
2. C# Create() 메서드 작성 (바인딩 + 액션)

사람이 하는 일:
3. Play Mode에서 확인
4. Runtime Editor로 미세 조정
5. Save JSON으로 저장

3. Runtime Editor = AI + 사람의 협업 도구

AI가 만든 UI를 사람이 실시간으로 조정합니다:

AI: JSON으로 화면 구조 생성 (레이아웃, 요소, 바인딩)
     ↓
사람: Play Mode에서 확인 → 위치/크기 미세 조정
     ↓
사람: Save JSON → AI가 생성한 파일에 수정사항 병합
     ↓
AI: 다음 요청에서 수정된 JSON 기반으로 작업

4. 반복 속도

기존 Unity 워크플로우:
코드 수정 → 컴파일 (30초) → Play Mode → 확인 → 반복

MDC-UI 워크플로우:
AI가 JSON 생성 → Play Mode (1초) → Runtime Editor로 조정 → Save → 끝

AI 프롬프트 예시

새 화면 만들기

"인벤토리 화면을 만들어줘.
상단: 필터 탭 (전체/무기/방어구/악세서리)
중앙: 3열 그리드로 아이템 카드 (아이콘+이름+등급색 테두리)
하단: 장착 버튼

visible 탭 전환이고, foreach 그리드야.
파일: inventory_screen.json + InventoryScreen.cs"

기존 화면 수정

"village_screen.json에서 navBar의 버튼 간격을 좁혀줘.
gap을 sw:0.005로 변경하고, 버튼 높이를 sh:0.035로 줄여."

디자인 토큰 변경

"전체 앱의 강조색을 보라색(#8B5CF6)으로 바꿔줘.
UITheme의 Accent.Primary를 변경하면 돼."

16. 기존 방식과의 비교

Unity 표준 Prefab vs MDC-UI

항목Prefab 방식MDC-UI
UI 정의Unity Editor GUI (마우스)JSON 텍스트
버전 관리바이너리 Prefab (diff 불가)JSON (git diff 가능)
AI 생성불가능완벽 지원
반응형수동 anchor 설정sw:, sh:, expand 자동
런타임 편집불가Runtime UI Editor
테마 변경개별 요소 수동 수정토큰 1곳 변경 → 전체 반영
학습 곡선RectTransform 이해 필요Flutter 경험 있으면 즉시
협업Prefab merge conflictJSON merge 용이

UGUI 코드 직접 생성 vs MDC-UI

항목C# 직접 생성MDC-UI
코드량버튼 1개 = ~15줄JSON 5줄 + C# 2줄
수정재컴파일 필수JSON 수정 → Play Mode 반영
레이아웃Y좌표 수동 계산column/row/expand 자동
재사용함수 추출JSON 복사
디자이너 참여코드 이해 필요JSON 읽기만

다른 UI 프레임워크 vs MDC-UI

항목UI ToolkitNGUIMDC-UI
정의 형식UXML + USSPrefabJSON
런타임 지원제한적완전완전
AI 친화낮음 (UXML 복잡)불가높음
모바일 최적화보통좋음좋음
학습 비용CSS 유사Unity 유사Flutter 유사
런타임 편집기없음없음내장

17. 솔직한 한계와 단점

이 프레임워크는 만능이 아닙니다. 정직하게 한계를 인정해야 개선할 수 있습니다.

한계 1: 애니메이션을 선언할 수 없다

현상: JSON으로 페이드인, 슬라이드, 바운스 같은 애니메이션을 정의할 수 없습니다. 모든 애니메이션은 C# 코드로 작성해야 합니다.

영향: UI 전환 효과, 알림 등장 애니메이션, 카드 뒤집기 등은 여전히 C#. 바이브코딩으로 "버튼 누르면 패널이 슬라이드 업"을 말하면 AI가 C#을 생성해야 합니다.

심각도: ★★★☆☆ (기능적 한계, 워크플로우 중단은 아님)

한계 2: Play Mode에서만 편집 가능

현상: Runtime UI Editor는 Play Mode가 아니면 동작하지 않습니다. Edit Mode에서는 JSON을 텍스트로만 수정 가능합니다.

영향: UI 확인하려면 항상 Play Mode에 진입해야 합니다 (1-5초). 컴파일이 필요한 수정 후에는 Play Mode를 다시 시작해야 합니다.

심각도: ★★☆☆☆ (Play Mode 진입은 빠르고, JSON 수정은 컴파일 불필요)

한계 3: 전체 리빌드 (Incremental Update 없음)

현상: 바인딩 변경이나 visible 토글 시 전체 화면 리빌드(RebuildScreen())가 필요한 경우가 있습니다. 개별 요소만 갱신하는 기능이 제한적입니다.

영향: 복잡한 화면(100+ 요소)에서 탭 전환 시 순간적인 프레임 드롭. 대부분의 화면에서는 체감되지 않음.

심각도: ★★☆☆☆ (체감 빈도 낮음, listview 가상화로 대부분 해결)

한계 4: 복잡한 레이아웃의 한계

현상: CSS Grid, Constraint Layout 같은 고급 레이아웃이 없습니다. 모든 레이아웃은 column/row 중첩으로 구성해야 합니다.

영향: 오버래핑 레이아웃, 비정형 배치(원형 메뉴, 방사형 배치)는 onBuild + C#으로 처리해야 합니다.

심각도: ★★☆☆☆ (모바일 게임 UI의 95%는 column/row로 해결 가능)

한계 5: 타입 안전성 부재

현상: JSON은 문자열 기반이라 오타를 컴파일 타임에 잡을 수 없습니다. "onClikc" 같은 오타는 런타임에서야 발견됩니다.

영향: 바인딩 키 오타, 액션 이름 오타, 색상 토큰 오타 → 런타임에 null/기본값으로 표시.

심각도: ★★★☆☆ (빈번한 실수, 에디터에서 경고 표시가 필요)

한계 6: UGUI 종속

현상: Unity의 legacy UGUI (Canvas + RectTransform + Image/Text) 위에 구축되었습니다. UI Toolkit(UIElements)이나 다른 렌더링 백엔드로의 전환이 어렵습니다.

영향: UGUI의 성능 한계 (대량 요소 시 배칭 이슈)를 그대로 물려받습니다. Unity가 UGUI를 deprecated하면 마이그레이션 비용 발생.

심각도: ★★☆☆☆ (UGUI는 당분간 유지됨, 모바일 게임 규모에서는 문제없음)

한계 7: 스크린 간 공유 컴포넌트

현상: 재화 바(CurrencyBar), 네비게이션 바(NavBar) 같은 공통 UI를 여러 화면에서 중복 선언해야 합니다. 컴포넌트 import/include 기능이 없습니다.

영향: 공통 UI 변경 시 여러 JSON 파일을 동시 수정해야 합니다. DRY 원칙 위반.

심각도: ★★★☆☆ (실무에서 자주 부딪히는 문제)

한계 8: 디버깅 가시성

현상: "왜 이 요소가 안 보이지?" 라는 문제를 진단하기 어렵습니다. visible 조건이 false인지, 크기가 0인지, 부모 밖에 있는지, alpha가 0인지 등을 하나하나 확인해야 합니다.

영향: UI 디버깅 시간이 늘어남. 특히 visible 바인딩이 복잡한 탭 시스템에서.

심각도: ★★☆☆☆ (Runtime Editor에서 어느 정도 해결)

한계 9: 에디터 드래그 앤 드롭 없음

현상: Runtime UI Editor에서 요소 위치를 변경하려면 수치를 직접 입력해야 합니다. 마우스로 드래그해서 이동하는 기능이 없습니다.

영향: 비주얼 디자이너에게 직관적이지 않음. 위치 조정에 시행착오가 필요.

심각도: ★★★☆☆ (UX 개선이 필요한 주요 항목)


18. 극복 로드맵

각 한계에 대한 구체적 해결 방안입니다.

로드맵 1: JSON 애니메이션 선언 (한계 1)

목표

{
  "type": "panel",
  "animation": {
    "enter": { "type": "slideUp", "duration": 0.3, "ease": "easeOut" },
    "exit": { "type": "fadeOut", "duration": 0.2 }
  }
}

구현 방안

  1. ElementDefanimation 필드 추가
  2. AnimationDef 클래스: type, duration, ease, delay
  3. 빌드 시 CanvasGroup + DOTween 시퀀스 자동 생성
  4. 지원 타입: fadeIn, fadeOut, slideUp/Down/Left/Right, scaleIn, bounce

난이도: 중 (2-3일)

DOTween 의존성 추가 필요. 트리거 타이밍(visible 변경 시, 생성 시) 설계가 핵심.

로드맵 2: Edit Mode 프리뷰 (한계 2)

목표

Play Mode 없이 JSON을 수정하면 Scene View에 프리뷰를 표시.

구현 방안

  1. [ExecuteInEditMode] 컴포넌트로 JSON을 Scene View에 렌더링
  2. JSON FileWatcher → 변경 감지 시 자동 리빌드
  3. SceneView.duringSceneGui에서 오버레이 렌더링

난이도: 상 (1주)

Edit Mode에서 UGUI를 렌더링하는 것 자체가 까다로움. Canvas 생성/파괴 관리 필요.

로드맵 3: Incremental Update (한계 3)

목표

바인딩 변경 시 해당 요소만 업데이트. 전체 리빌드 불필요.

구현 방안

  1. 각 바인딩 키에 연결된 요소 목록 관리 (Dictionary<string, List<GameObject>>)
  2. SetBinding() 호출 시 관련 요소만 텍스트/visible/color 갱신
  3. 레이아웃 영향 없는 변경(텍스트, 색상)은 리빌드 없이 직접 수정

난이도: 중 (2-3일)

바인딩 의존성 그래프 구축이 핵심. visible 변경은 레이아웃에 영향을 줘서 부분 리빌드 필요.

로드맵 4: JSON Component Include (한계 7)

목표

{
  "type": "include",
  "src": "UI/Components/currency_bar",
  "bindings": { "gold": "$goldText", "gem": "$gemText" }
}

구현 방안

  1. include 타입 추가 → BuildElement에서 외부 JSON 로드
  2. 바인딩 매핑으로 부모 컨텍스트 연결
  3. 재귀 빌드 (include 안의 include 지원)

난이도: 중 (2일)

JSON 로딩 캐시, 순환 참조 방지, 네임스페이스 충돌 해결 필요.

로드맵 5: 드래그 앤 드롭 에디터 (한계 9)

목표

Game View에서 요소를 마우스로 드래그하여 위치 이동.

구현 방안

  1. SceneView.duringSceneGui에서 마우스 이벤트 감지
  2. 선택된 요소의 RectTransform을 마우스 델타만큼 이동
  3. 이동 완료 시 JSON의 x/y 값을 역계산하여 갱신 (cu → sw:/sh: 역변환)

난이도: 중-상 (3-4일)

Screen Space → Canvas Unit 좌표 변환, 스냅 기능, 가이드라인 표시.

로드맵 6: JSON Lint / Validator (한계 5)

목표

JSON 오타를 빌드 시점에 경고.

구현 방안

  1. UIScreenBuilder.Build() 에서 검증 패스 추가
  2. 등록되지 않은 액션 이름 → Warning 로그
  3. 존재하지 않은 바인딩 키 → Warning 로그
  4. 잘못된 색상 토큰 → Warning 로그 + 빨간색 fallback
  5. Editor 콘솔에 종합 리포트 출력

난이도: 하 (1일)

기존 코드에 검증 로직만 추가하면 됨.

우선순위

순위항목이유
1JSON Lint/Validator낮은 난이도, 높은 효과 (매일 쓰는 기능)
2Component IncludeDRY 원칙 회복, 생산성 직결
3Incremental Update대형 화면 성능 개선
4JSON 애니메이션AI 바이브코딩 범위 확대
5드래그 앤 드롭UX 개선 (비개발자 접근성)
6Edit Mode 프리뷰개발 루프 단축 (하지만 Play Mode가 빠르므로 낮은 우선순위)

19. 발견된 개선점과 기술 부채

프레임워크를 실제 프로덕션에서 사용하면서 발견된 구체적 개선사항들입니다.

개선점 1: 바인딩 갱신 후 자동 리프레시 (심각도: 높음)

현상: SetBinding() 후 화면에 반영하려면 RebuildScreen() 또는 RefreshForeach()를 수동 호출해야 합니다.

개선안: Reactive Binding — SetBinding 시 자동으로 관련 요소만 갱신

// 현재 (번거로움)
builder.SetBinding("gold", newValue);
builder.RebuildScreen(); // 전체 리빌드 😰

// 개선 후 (자동 갱신)
builder.SetBinding("gold", newValue); // 관련 Text만 자동 업데이트

개선점 2: Error Boundary (심각도: 높음)

현상: JSON에 오타가 있으면 (존재하지 않는 타입, 잘못된 표현식) Build()가 NullReferenceException을 던지고 화면 전체가 안 나옵니다.

개선안: 오류 요소를 빨간 박스 + 에러 메시지로 대체하고 나머지는 정상 렌더링

[ERROR] Element "brokenBtn" (line 45): Unknown type "btton" — did you mean "button"?

개선점 3: $item 바인딩 확장 (심각도: 중)

현상: foreach 템플릿에서 $item.xxx 바인딩이 text와 visible에만 적용됩니다. bg, color, sprite 필드는 $item.을 인식하지 못합니다.

개선안: 모든 문자열 필드에서 $item.xxx 해석. 특히:

  • bg: "$item.cardColor" → 아이템별 배경색
  • sprite: "$item.iconPath" → 아이템별 아이콘
  • color: "$item.textColor" → 아이템별 텍스트색

참고: 이 개선은 Phase 0-A로 이미 플랜에 포함되어 있습니다.

개선점 4: listview JSON cellTemplate (심각도: 중)

현상: listview는 C# createCell/bindCell 콜백이 필수입니다. JSON template만으로는 셀을 정의할 수 없습니다.

개선안: foreach처럼 template + dataSource 방식 지원

{
  "type": "listview",
  "dataSource": "$heroes",
  "template": [
    { "type": "text", "text": "$item.name" }
  ]
}

참고: 이 개선은 Phase 0-B로 이미 플랜에 포함되어 있습니다.

개선점 5: 화면 전환 시 정적 참조 누수 (심각도: 높음)

현상: VillageScreen 등의 static 필드(_builder, _screenRoot, _overlayPanel)가 화면 Destroy 후에도 참조를 유지합니다.

개선안:

  1. UIScreenBuilder.Cleanup()을 화면 Destroy 시 자동 호출
  2. 화면별 OnScreenDestroy() 콜백에서 static 참조 정리
  3. 또는 static 사용을 Instance 패턴으로 전환

참고: Layer_Screen 스택 버그의 근본 원인. SetActive(false) + Destroy()로 1차 대응 완료.

개선점 6: Runtime Editor 단위 표시 (심각도: 낮음)

현상: stretch 모드 요소(sizeDelta=0,0)에서 크기 모드 토글(cu / % screen / % parent)이 모두 0을 표시합니다. 실제 렌더링된 크기를 보여줘야 합니다.

개선안: stretch 요소는 rect.width/height를 읽어서 "실제 크기: 1080 × 1920 cu" 표시

개선점 7: C# UI → JSON 전환 미완료 (심각도: 중)

현상: 일부 화면(ExplorationScreen, AchievementScreen, ResultScreen 등)이 onBuild 안에 수백 줄의 C# UI 생성 코드를 포함합니다. Runtime Editor에서 이 영역은 편집/저장이 불가능합니다.

현재 상태:

상태화면 수설명
JSON 100%4Title, Settings, Augment, BuffSelect
JSON 90%+4Shop, Ascension, Engrave, MonsterEnhance
onBuild 50%+6Achievement, Result, Gacha, Equipment, SkillTree, Party
스텁2Village, Exploration (구조만 JSON, 내부 C#)

개선안: Phase 1~7 플랜에 따라 점진적 전환. 각 화면의 C# UI 생성 코드를 JSON으로 이관.

개선점 8: JSON 스키마 문서 자동 생성 (심각도: 낮음)

현상: JSON에서 사용 가능한 필드가 코드에만 정의되어 있고, 별도 문서가 없습니다. 새 개발자가 어떤 필드를 쓸 수 있는지 알려면 UIScreenBuilder.cs를 읽어야 합니다.

개선안:

  1. ElementDef 클래스에서 JSON Schema 자동 추출
  2. 에디터에서 "?" 버튼 → 현재 타입의 사용 가능 필드 팝업
  3. 자동완성 (typing "col" → "color", "column" 제안)

기술 부채 요약

항목심각도난이도ROI
Reactive Binding★★★★★
Error Boundary★★★★★
정적 참조 누수★★★★☆
$item 바인딩 확장★★★★☆
listview cellTemplate★★★★☆
JSON Lint★★★★☆
Component Include★★★☆☆
C# → JSON 전환★★★☆☆
에디터 단위 표시★★☆☆☆
스키마 자동 생성★★☆☆☆

20. FAQ

Q: Unity UI Toolkit을 쓰면 되지 않나?

A: UI Toolkit은 에디터 도구용으로 설계되었고, 런타임 지원이 제한적입니다. UXML/USS의 복잡한 문법은 AI가 생성하기 어렵습니다. MDC-UI의 JSON은 의도적으로 단순합니다.

Q: 성능은 괜찮은가?

A: 예. listview는 가상화되어 1000개+ 아이템도 60fps. foreach는 실제 아이템 수만큼만 GameObject 생성. UIScreenBuilder의 Build()는 한 번 호출되면 네이티브 Unity UI가 됩니다 — 매 프레임 JSON을 파싱하지 않습니다.

Q: 기존 Prefab 프로젝트에 점진적으로 도입 가능한가?

A: 가능합니다. 새 화면부터 JSON으로 만들고, 기존 화면은 그대로 둬도 됩니다. UIScreenBuilder는 독립적으로 동작합니다.

Q: onBuild는 언제 사용하나?

A: JSON으로 표현할 수 없는 경우에만:

  • 커스텀 Unity 컴포넌트 부착 (SpriteAnimator, RectMask2D)
  • 절차적 레이아웃 (스킬트리 노드 그래프의 Atan2 좌표 계산)
  • 렌더링 영역 초기화 (전투 뷰포트)

일반적인 텍스트, 버튼, 레이아웃, 스크롤, 리스트는 전부 JSON으로 해결합니다.

Q: Hot Reload가 되나?

A: Runtime UI Editor에서 수정 → Save JSON → Play Mode 재시작 → 반영. 컴파일 없이 UI 변경이 가능합니다. Quick Save 기능을 쓰면 같은 Play Mode 세션에서 다른 화면으로 전환 시에도 자동 적용됩니다.

Q: 디자이너(비개발자)도 사용할 수 있나?

A: Runtime UI Editor를 통해 가능합니다. Play Mode에서 요소를 클릭하고, 위치/크기/색상을 조정하고, Save JSON만 누르면 됩니다. JSON을 직접 편집할 필요 없습니다.

Q: 다국어(i18n)는 어떻게?

A: 텍스트 바인딩으로 해결합니다. "text": "$welcomeMessage" 에 대해 C#에서 언어별 문자열을 바인딩하면 됩니다.


부록: 프로젝트 구조

Assets/
├── Resources/
│   └── UI/
│       ├── Screens/           ← JSON 스크린 파일들
│       │   ├── village_screen.json
│       │   ├── settings_screen.json
│       │   ├── shop_screen.json
│       │   └── ...
│       └── theme.json         ← 디자인 토큰 정의
├── Scripts/
│   └── UI/
│       ├── UIHelper.cs        ← cu 좌표계 + 요소 팩토리
│       ├── UIComponents.cs    ← ScrollContainer, FlexRow 등
│       ├── UIScreenBuilder.cs ← JSON→UI 엔진 (핵심)
│       ├── UITheme.cs         ← 디자인 토큰
│       ├── UILayerManager.cs  ← Z-order 레이어
│       ├── ScreenLayout.cs    ← 3존 화면 템플릿
│       ├── ListView.cs        ← 가상화 리스트
│       ├── VillageScreen.cs   ← 화면별 C# (바인딩+액션)
│       └── ...
└── Editor/
    └── RuntimeUIEditor/
        ├── RuntimeUIEditorWindow.cs  ← 에디터 메인 윈도우
        └── PropertyPanel.cs          ← 속성 편집 패널

MDC-UI는 바이브코딩 시대에 맞춘 게임 UI 개발 프레임워크입니다. JSON으로 선언하고, AI로 생성하고, Runtime Editor로 조정하고, Save로 확정합니다.

Prefab을 버리세요. 텍스트가 미래입니다.