Initial Dansori character workspace

This commit is contained in:
eKeerar
2026-07-04 10:34:46 +09:00
commit 5a419816ff
2480 changed files with 38692 additions and 0 deletions
+66
View File
@@ -0,0 +1,66 @@
# 반응 시퀀서 & 트리거 (Reactions)
상황 → 반응을 정의하는 레이어. **트리거 매퍼**(`reactions.json`) + **반응 클립**(`clips/*.json`)으로 구성.
> ✅ **Phase 2 런타임 구현됨**: `../07_Viewer/reactions.html`(더블클릭). 트리거(idle/error/success). baked 바디 + 표정 머리(목 정합·회전)는 `_layout.json` 사용. head base=`haruka_head_twin`. **리그 파츠 완성 → idle=배경춤도 동작**(허리봉인 튜닝). idle 배경춤 단독은 `../07_Viewer/index.html`.
## 트리거 매퍼 (`reactions.json`)
상황키 → 반응 클립 이름. 앱은 상황키만 던지면 된다.
```json
{ "error": "gesture_no", "success": "gesture_heart", "idle": "dance_idle" }
```
## 반응 클립 스키마 (`clips/<name>.json`)
하나의 반응 = 레이어드 타임라인.
```jsonc
{
"name": "gesture_no",
"duration": 2.4, // 초
"return": "idle", // 종료 후 복귀 클립(보통 배경 idle)
"layers": {
"body": [ // Body 트랙: 시간에 따라 rig 클립 or baked 포즈
{ "t":0.0, "mode":"rig", "clip":"idle" },
{ "t":0.15,"mode":"baked", "image":"haruka_body_sailor_armscross", "fade":0.2 }
],
"face": [ // 표정 프레임 트랙
{ "t":0.0, "expr":"neutral" },
{ "t":0.3, "expr":"negative" }
],
"mouth": [ // 말하기(유사 립싱크): expr↔talk 순환
{ "t":0.5, "say":"안돼요", "dur":1.2, "pattern":"talk" }
],
"transform": { // 리그 위 잔모션(본별 delta) — Animation.md 스키마와 동일
"head": { "rot":[ {"t":0.5,"v":0},{"t":0.8,"v":9},{"t":1.1,"v":-9},{"t":1.4,"v":9},{"t":1.7,"v":0} ] },
"chest":{ "ty":[ {"t":0.0,"v":0},{"t":0.2,"v":-4},{"t":0.5,"v":0} ] }
},
"caption": [ { "t":0.5, "text":"안돼요", "dur":1.6 } ], // 옵션 말풍선
"sfx": [ { "t":0.5, "id":"nope" } ] // 옵션 효과음
}
}
```
### 필드 규칙
- `body[]`: 시간순. `mode:"rig"`+`clip` 또는 `mode:"baked"`+`image`(파일명, 확장자 생략 가능). `fade`=크로스페이드 초.
- `face[]`: `expr` = 표정 프레임 키(20종 중). 시간에 스냅.
- `mouth[]`: `say` 대사, `dur` 길이, `pattern:"talk"`(talk/talk_wide/현재 감정 프레임 순환). 립싱크는 근사.
- `transform`: 본별 키프레임(리그 delta). Body가 baked여도 head/chest 등 트랜스폼은 적용(단 baked는 통짜라 파츠 분리 트랜스폼은 제한적 → 주로 전체/머리에 적용).
- `caption`/`sfx`: 옵션. 앱 설정(말풍선/TTS)에 따라 사용.
## 상황 → 반응 카탈로그
| 상황키 | 클립 | Body | Face | Mouth | 잔모션 |
|---|---|---|---|---|---|
| `error` | `gesture_no` | baked armscross | negative | "안돼요" | 고개 젓기 |
| `success` | `gesture_heart` | baked heart | love/positive | "잘됐어요" | 통통 바운스 |
| `idle` | `dance_idle` | rig | smile/neutral | — | 그루브 루프 |
| *(확장)* `greet` | `gesture_wave` | rig wave | smile | "안녕하세요" | 손 흔들기 |
| *(확장)* `explain` | `gesture_present` | rig present | neutral | 안내 대사 | 제시 |
| *(확장)* `thinking` | `gesture_think` | rig idle_upper | thinking | — | 갸웃 |
## 트리거 API(개념)
```
Mascot.React("success") // reactions.json으로 클립 결정 → 시퀀서 재생 → 종료 후 return 클립
Mascot.SetIdle("dance_idle")// 배경 기본 루프
Mascot.Say("...", expr) // 임시 대사(mouth+face)만
```
상세 앱 연동: `../08_Roadmap/App_Integration.md`.
+435
View File
@@ -0,0 +1,435 @@
{
"stage": {
"w": 520,
"h": 900
},
"neck": [
260,
250
],
"overlap": 6,
"headTargetW": 150,
"bodies": {
"haruka_body_idol_armscross": {
"scale": 0.329,
"ox": 28.5,
"oy": 228.9
},
"haruka_body_idol_cheer": {
"scale": 0.2516,
"ox": 194.7,
"oy": 242.7
},
"haruka_body_idol_clap": {
"scale": 0.319,
"ox": 36.9,
"oy": 227.0
},
"haruka_body_idol_control": {
"scale": 0.2725,
"ox": 41.4,
"oy": 218.4
},
"haruka_body_idol_dj": {
"scale": 0.3042,
"ox": 2.0,
"oy": 237.2
},
"haruka_body_idol_handwave": {
"scale": 0.3329,
"ox": 150.8,
"oy": 212.4
},
"haruka_body_idol_heart": {
"scale": 0.3464,
"ox": -5.3,
"oy": 225.8
},
"haruka_body_idol_idle_full": {
"scale": 0.5361,
"ox": -24.7,
"oy": 217.3
},
"haruka_body_idol_idle_upper": {
"scale": 0.3392,
"ox": -10.4,
"oy": 216.1
},
"haruka_body_idol_joy": {
"scale": 0.2641,
"ox": 218.4,
"oy": 244.2
},
"haruka_body_idol_listen": {
"scale": 0.3464,
"ox": 72.6,
"oy": 230.9
},
"haruka_body_idol_peace": {
"scale": 0.3686,
"ox": 82.7,
"oy": 226.8
},
"haruka_body_idol_piano": {
"scale": 0.3397,
"ox": 35.3,
"oy": 229.6
},
"haruka_body_idol_point": {
"scale": 0.3142,
"ox": 172.0,
"oy": 233.0
},
"haruka_body_idol_present": {
"scale": 0.2593,
"ox": 82.5,
"oy": 222.3
},
"haruka_body_idol_shrug": {
"scale": 0.1738,
"ox": 126.1,
"oy": 232.3
},
"haruka_body_idol_thumbsup": {
"scale": 0.356,
"ox": 13.8,
"oy": 216.2
},
"haruka_body_idol_wave": {
"scale": 0.3397,
"ox": 151.3,
"oy": 234.4
},
"haruka_body_sailor_armscross": {
"scale": 0.3199,
"ox": 64.4,
"oy": 221.8
},
"haruka_body_sailor_cheer": {
"scale": 0.213,
"ox": 210.3,
"oy": 233.6
},
"haruka_body_sailor_clap": {
"scale": 0.3423,
"ox": 5.2,
"oy": 214.7
},
"haruka_body_sailor_control": {
"scale": 0.2369,
"ox": 100.7,
"oy": 229.4
},
"haruka_body_sailor_dj": {
"scale": 0.293,
"ox": 17.0,
"oy": 234.5
},
"haruka_body_sailor_handwave": {
"scale": 0.29,
"ox": 68.9,
"oy": 219.8
},
"haruka_body_sailor_heart": {
"scale": 0.3662,
"ox": 4.0,
"oy": 218.9
},
"haruka_body_sailor_idle_full": {
"scale": 0.5263,
"ox": -36.6,
"oy": 196.8
},
"haruka_body_sailor_idle_upper": {
"scale": 0.2879,
"ox": 83.5,
"oy": 222.1
},
"haruka_body_sailor_joy": {
"scale": 0.2712,
"ox": 25.5,
"oy": 242.7
},
"haruka_body_sailor_listen": {
"scale": 0.3722,
"ox": -58.6,
"oy": 232.1
},
"haruka_body_sailor_peace": {
"scale": 0.3258,
"ox": 107.4,
"oy": 222.3
},
"haruka_body_sailor_piano": {
"scale": 0.3083,
"ox": 68.4,
"oy": 219.5
},
"haruka_body_sailor_point": {
"scale": 0.3046,
"ox": 173.9,
"oy": 222.6
},
"haruka_body_sailor_present": {
"scale": 0.2486,
"ox": 43.2,
"oy": 220.7
},
"haruka_body_sailor_shrug": {
"scale": 0.1697,
"ox": 150.2,
"oy": 234.0
},
"haruka_body_sailor_thumbsup": {
"scale": 0.3506,
"ox": 27.4,
"oy": 218.4
},
"haruka_body_sailor_wave": {
"scale": 0.319,
"ox": 186.6,
"oy": 231.8
},
"haruka_body_witch_armscross": {
"scale": 0.3925,
"ox": -49.1,
"oy": 233.1
},
"haruka_body_witch_cheer": {
"scale": 0.2255,
"ox": 188.5,
"oy": 240.3
},
"haruka_body_witch_clap": {
"scale": 0.4236,
"ox": -62.8,
"oy": 230.5
},
"haruka_body_witch_control": {
"scale": 0.2897,
"ox": 12.8,
"oy": 239.0
},
"haruka_body_witch_dj": {
"scale": 0.3199,
"ox": 47.6,
"oy": 238.8
},
"haruka_body_witch_handwave": {
"scale": 0.3464,
"ox": 9.6,
"oy": 225.8
},
"haruka_body_witch_heart": {
"scale": 0.3945,
"ox": -16.7,
"oy": 231.1
},
"haruka_body_witch_idle_full": {
"scale": 0.4684,
"ox": 16.2,
"oy": 191.4
},
"haruka_body_witch_idle_upper": {
"scale": 0.3814,
"ox": -61.9,
"oy": 222.5
},
"haruka_body_witch_joy": {
"scale": 0.2678,
"ox": 216.6,
"oy": 240.4
},
"haruka_body_witch_listen": {
"scale": 0.3918,
"ox": 26.1,
"oy": 245.3
},
"haruka_body_witch_peace": {
"scale": 0.371,
"ox": 92.9,
"oy": 234.8
},
"haruka_body_witch_piano": {
"scale": 0.3758,
"ox": -50.8,
"oy": 229.3
},
"haruka_body_witch_point": {
"scale": 0.29,
"ox": 54.4,
"oy": 237.8
},
"haruka_body_witch_present": {
"scale": 0.2671,
"ox": 37.2,
"oy": 237.7
},
"haruka_body_witch_shrug": {
"scale": 0.1896,
"ox": 125.0,
"oy": 239.4
},
"haruka_body_witch_thumbsup": {
"scale": 0.3674,
"ox": 2.6,
"oy": 236.0
},
"haruka_body_witch_wave": {
"scale": 0.3272,
"ox": 186.7,
"oy": 235.3
}
},
"heads": {
"haruka_head_twin": {
"w": 1046,
"neckNorm": [
0.4972,
0.9537
]
},
"haruka_head_twin_blink": {
"w": 1044,
"neckNorm": [
0.4972,
0.9537
]
},
"haruka_head_twin_confused": {
"w": 1046,
"neckNorm": [
0.4972,
0.9553
]
},
"haruka_head_twin_cool": {
"w": 1043,
"neckNorm": [
0.4976,
0.9553
]
},
"haruka_head_twin_laugh": {
"w": 1059,
"neckNorm": [
0.5,
0.9641
]
},
"haruka_head_twin_love": {
"w": 1044,
"neckNorm": [
0.4972,
0.9553
]
},
"haruka_head_twin_negative": {
"w": 1044,
"neckNorm": [
0.4964,
0.953
]
},
"haruka_head_twin_neutral": {
"w": 1045,
"neckNorm": [
0.4976,
0.9545
]
},
"haruka_head_twin_playful": {
"w": 1042,
"neckNorm": [
0.4972,
0.9553
]
},
"haruka_head_twin_positive": {
"w": 1040,
"neckNorm": [
0.4972,
0.9537
]
},
"haruka_head_twin_pout": {
"w": 1044,
"neckNorm": [
0.4972,
0.9553
]
},
"haruka_head_twin_proud": {
"w": 1044,
"neckNorm": [
0.4972,
0.9553
]
},
"haruka_head_twin_sad": {
"w": 1044,
"neckNorm": [
0.4972,
0.9569
]
},
"haruka_head_twin_shy": {
"w": 1043,
"neckNorm": [
0.4968,
0.9553
]
},
"haruka_head_twin_sleepy": {
"w": 1045,
"neckNorm": [
0.4976,
0.9561
]
},
"haruka_head_twin_smile": {
"w": 1045,
"neckNorm": [
0.4976,
0.9537
]
},
"haruka_head_twin_surprised": {
"w": 1044,
"neckNorm": [
0.4972,
0.9545
]
},
"haruka_head_twin_talk": {
"w": 1044,
"neckNorm": [
0.4964,
0.953
]
},
"haruka_head_twin_talk_wide": {
"w": 1044,
"neckNorm": [
0.4972,
0.9537
]
},
"haruka_head_twin_thinking": {
"w": 1044,
"neckNorm": [
0.4972,
0.9537
]
},
"haruka_head_twin_wink": {
"w": 1044,
"neckNorm": [
0.4972,
0.9545
]
}
}
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 137 KiB

@@ -0,0 +1,28 @@
{
"name": "gesture_heart",
"desc": "손 하트를 그리며 밝게 '잘됐어요'",
"duration": 2.2,
"return": "idle",
"layers": {
"body": [
{ "t": 0.0, "mode": "rig", "clip": "idle" },
{ "t": 0.15, "mode": "baked", "image": "haruka_body_sailor_heart", "fade": 0.2 }
],
"face": [
{ "t": 0.0, "expr": "smile" },
{ "t": 0.25, "expr": "love" }
],
"mouth": [
{ "t": 0.5, "say": "잘됐어요", "dur": 1.1, "pattern": "talk" }
],
"transform": {
"pelvis": { "ty": [ {"t":0.4,"v":0}, {"t":0.7,"v":8}, {"t":1.0,"v":0}, {"t":1.3,"v":8}, {"t":1.6,"v":0} ] },
"head": { "rot": [ {"t":0.4,"v":0}, {"t":0.7,"v":4}, {"t":1.0,"v":-4}, {"t":1.3,"v":4}, {"t":1.6,"v":0} ] }
},
"caption": [ { "t": 0.5, "text": "잘됐어요", "dur": 1.5 } ],
"sfx": [ { "t": 0.45, "id": "success" } ]
}
}
@@ -0,0 +1,28 @@
{
"name": "gesture_no",
"desc": "서있다 → 팔짱 끼고 인상 쓰며 고개 저으며 '안돼요'",
"duration": 2.4,
"return": "idle",
"layers": {
"body": [
{ "t": 0.0, "mode": "rig", "clip": "idle" },
{ "t": 0.15, "mode": "baked", "image": "haruka_body_sailor_armscross", "fade": 0.2 }
],
"face": [
{ "t": 0.0, "expr": "neutral" },
{ "t": 0.3, "expr": "negative" }
],
"mouth": [
{ "t": 0.55, "say": "안돼요", "dur": 1.2, "pattern": "talk" }
],
"transform": {
"chest": { "ty": [ {"t":0.0,"v":0}, {"t":0.2,"v":-4}, {"t":0.5,"v":0} ] },
"head": { "rot": [ {"t":0.5,"v":0}, {"t":0.8,"v":9}, {"t":1.1,"v":-9}, {"t":1.4,"v":9}, {"t":1.7,"v":-9}, {"t":2.0,"v":0} ] }
},
"caption": [ { "t": 0.55, "text": "안돼요", "dur": 1.6 } ],
"sfx": [ { "t": 0.5, "id": "nope" } ]
}
}
+18
View File
@@ -0,0 +1,18 @@
{
"name": "Haruka reactions map",
"note": "상황키(app event) → 반응 클립 이름(clips/<name>.json). idle은 배경 기본 루프.",
"idleDefault": "dance_idle",
"map": {
"idle": "dance_idle",
"error": "gesture_no",
"success": "gesture_heart"
},
"plannedExpansion": {
"greet": "gesture_wave",
"explain": "gesture_present",
"thinking": "gesture_think"
}
}