MDC-UI: AI-First 선언적 게임 UI 프레임워크
Unity에서 Prefab 없이, JSON 선언만으로 모바일 게임 UI를 만드는 프레임워크
바이브코딩(Vibe Coding) 시대의 게임 UI 개발 패러다임
목차
Part I: 왜, 그리고 무엇인가
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 기반입니다. 버튼 하나를 만들려면:
- Hierarchy에서 우클릭 → UI → Button
- Inspector에서 속성 하나하나 클릭해서 설정
- 위치/크기를 마우스로 드래그
- 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에 이식한 것입니다.
| Flutter | MDC-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" | 슬라이더 |
원칙
- JSON이 구조의 Single Source of Truth — UI의 레이아웃, 색상, 폰트, 크기는 전부 JSON
- C#은 데이터 바인딩만 — SetBinding으로 텍스트 값 연결, RegisterAction으로 콜백 연결
- 절대 좌표 나열 금지 — 반드시 column/row/expand로 선언
- 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] │
└─────────────────────────────────────────────────────────┘
핵심 컴포넌트
| 컴포넌트 | 파일 | 역할 |
|---|---|---|
| UIScreenBuilder | UIScreenBuilder.cs | JSON → Unity UI 변환 엔진 |
| ValueResolver | UIScreenBuilder.cs 내부 | sw:0.9, sh:0.08 등 표현식 파싱 |
| UITheme | UITheme.cs | 디자인 토큰 (색상/폰트/간격) |
| UIHelper | UIHelper.cs | UI 요소 팩토리 + cu 좌표계 |
| UIComponents | UIComponents.cs | ScrollContainer, FlexRow 등 복합 컴포넌트 |
| UILayerManager | UILayerManager.cs | Z-order 레이어 관리 (Screen/Popup/Toast) |
| RuntimeUIEditor | RuntimeUIEditorWindow.cs | Play Mode 비주얼 에디터 |
| PropertyPanel | PropertyPanel.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" }
]
}
]
}
최상위 필드
| 필드 | 타입 | 설명 |
|---|---|---|
screen | string | C# 스크린 클래스명 |
bg | string | 배경색 (테마 토큰 또는 #RRGGBB) |
elements | array | 최상위 요소 배열 |
요소 공통 필드
모든 요소가 사용할 수 있는 필드:
| 필드 | 타입 | 예시 | 설명 |
|---|---|---|---|
id | string | "titleText" | 고유 식별자 (바인딩/에디터용) |
type | string | "text" | 요소 타입 (필수) |
x | string | "0", "sw:0.1" | X 위치 |
y | string | "-sh:0.05" | Y 위치 |
w | string | "sw:0.8", "pw:1.0" | 폭 |
h | string | "sh:0.04", "expand" | 높이 |
visible | string | "isLoggedIn", "tab=1" | 조건부 표시 |
anchor | string | "TC", "BL" | 앵커 프리셋 |
margin | string | "sh:0.01" | 외부 여백 |
alpha | float | 0.8 | 투명도 |
platform | string | "ios", "android" | 플랫폼 필터 |
onBuild | string | "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 | 바인딩 키 ($key → List<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처럼 column과 row의 조합으로 모든 레이아웃을 구성합니다.
기본 패턴: 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로 미세 조정
- Play Mode 진입
- Runtime UI Editor 열기 (Cmd+Shift+U)
- Hierarchy에서
greeting선택 - Property에서 font 크기 조정, 색상 변경
- 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 conflict | JSON 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 Toolkit | NGUI | MDC-UI |
|---|---|---|---|
| 정의 형식 | UXML + USS | Prefab | JSON |
| 런타임 지원 | 제한적 | 완전 | 완전 |
| 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 }
}
}
구현 방안
ElementDef에animation필드 추가AnimationDef클래스: type, duration, ease, delay- 빌드 시
CanvasGroup+DOTween시퀀스 자동 생성 - 지원 타입: fadeIn, fadeOut, slideUp/Down/Left/Right, scaleIn, bounce
난이도: 중 (2-3일)
DOTween 의존성 추가 필요. 트리거 타이밍(visible 변경 시, 생성 시) 설계가 핵심.
로드맵 2: Edit Mode 프리뷰 (한계 2)
목표
Play Mode 없이 JSON을 수정하면 Scene View에 프리뷰를 표시.
구현 방안
[ExecuteInEditMode]컴포넌트로 JSON을 Scene View에 렌더링- JSON FileWatcher → 변경 감지 시 자동 리빌드
- SceneView.duringSceneGui에서 오버레이 렌더링
난이도: 상 (1주)
Edit Mode에서 UGUI를 렌더링하는 것 자체가 까다로움. Canvas 생성/파괴 관리 필요.
로드맵 3: Incremental Update (한계 3)
목표
바인딩 변경 시 해당 요소만 업데이트. 전체 리빌드 불필요.
구현 방안
- 각 바인딩 키에 연결된 요소 목록 관리 (
Dictionary<string, List<GameObject>>) SetBinding()호출 시 관련 요소만 텍스트/visible/color 갱신- 레이아웃 영향 없는 변경(텍스트, 색상)은 리빌드 없이 직접 수정
난이도: 중 (2-3일)
바인딩 의존성 그래프 구축이 핵심. visible 변경은 레이아웃에 영향을 줘서 부분 리빌드 필요.
로드맵 4: JSON Component Include (한계 7)
목표
{
"type": "include",
"src": "UI/Components/currency_bar",
"bindings": { "gold": "$goldText", "gem": "$gemText" }
}
구현 방안
include타입 추가 → BuildElement에서 외부 JSON 로드- 바인딩 매핑으로 부모 컨텍스트 연결
- 재귀 빌드 (include 안의 include 지원)
난이도: 중 (2일)
JSON 로딩 캐시, 순환 참조 방지, 네임스페이스 충돌 해결 필요.
로드맵 5: 드래그 앤 드롭 에디터 (한계 9)
목표
Game View에서 요소를 마우스로 드래그하여 위치 이동.
구현 방안
SceneView.duringSceneGui에서 마우스 이벤트 감지- 선택된 요소의 RectTransform을 마우스 델타만큼 이동
- 이동 완료 시 JSON의 x/y 값을 역계산하여 갱신 (cu → sw:/sh: 역변환)
난이도: 중-상 (3-4일)
Screen Space → Canvas Unit 좌표 변환, 스냅 기능, 가이드라인 표시.
로드맵 6: JSON Lint / Validator (한계 5)
목표
JSON 오타를 빌드 시점에 경고.
구현 방안
UIScreenBuilder.Build()에서 검증 패스 추가- 등록되지 않은 액션 이름 → Warning 로그
- 존재하지 않은 바인딩 키 → Warning 로그
- 잘못된 색상 토큰 → Warning 로그 + 빨간색 fallback
- Editor 콘솔에 종합 리포트 출력
난이도: 하 (1일)
기존 코드에 검증 로직만 추가하면 됨.
우선순위
| 순위 | 항목 | 이유 |
|---|---|---|
| 1 | JSON Lint/Validator | 낮은 난이도, 높은 효과 (매일 쓰는 기능) |
| 2 | Component Include | DRY 원칙 회복, 생산성 직결 |
| 3 | Incremental Update | 대형 화면 성능 개선 |
| 4 | JSON 애니메이션 | AI 바이브코딩 범위 확대 |
| 5 | 드래그 앤 드롭 | UX 개선 (비개발자 접근성) |
| 6 | Edit 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 후에도 참조를 유지합니다.
개선안:
UIScreenBuilder.Cleanup()을 화면 Destroy 시 자동 호출- 화면별
OnScreenDestroy()콜백에서 static 참조 정리 - 또는 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% | 4 | Title, Settings, Augment, BuffSelect |
| JSON 90%+ | 4 | Shop, Ascension, Engrave, MonsterEnhance |
| onBuild 50%+ | 6 | Achievement, Result, Gacha, Equipment, SkillTree, Party |
| 스텁 | 2 | Village, Exploration (구조만 JSON, 내부 C#) |
개선안: Phase 1~7 플랜에 따라 점진적 전환. 각 화면의 C# UI 생성 코드를 JSON으로 이관.
개선점 8: JSON 스키마 문서 자동 생성 (심각도: 낮음)
현상: JSON에서 사용 가능한 필드가 코드에만 정의되어 있고, 별도 문서가 없습니다. 새 개발자가 어떤 필드를 쓸 수 있는지 알려면 UIScreenBuilder.cs를 읽어야 합니다.
개선안:
ElementDef클래스에서 JSON Schema 자동 추출- 에디터에서 "?" 버튼 → 현재 타입의 사용 가능 필드 팝업
- 자동완성 (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을 버리세요. 텍스트가 미래입니다.