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
+58
View File
@@ -0,0 +1,58 @@
# Live2D 모션 설계
최종 모션은 Cubism Editor에서 `.motion3.json`으로 export한다.
## 파일 역할
| 파일 | 역할 |
|---|---|
| `live2d_motion_plan.json` | Live2D 파라미터 곡선 설계 |
| `dance_idle.json` | 대기춤 곡선 데이터 |
## MVP 모션
| Motion ID | 용도 | 루프 | 우선순위 |
|---|---|---|---|
| `motion_idle_breath` | 기본 대기 호흡 | yes | 필수 |
| `motion_idle_dance` | 가벼운 배경춤 | yes | 필수 |
| `motion_no` | 오류/거절 | no | 필수 |
| `motion_heart` | 성공/칭찬 | no | 필수 |
| `motion_greet` | 인사 | no | 확장 |
| `motion_present` | 안내 | no | 확장 |
| `motion_thinking` | 생각중 | loop/short | 확장 |
## 모션 제작 원칙
- `ParamBodyAngle*`, `ParamAngle*`, `ParamBreath`를 중심으로 자연스러운 움직임을 만든다.
- 대사는 mouth/caption/TTS 레이어에서 처리한다.
- 표정은 `.exp3.json`으로 분리한다.
- idle 계열은 첫 키와 마지막 키를 맞춘다.
- 머리카락과 펜던트는 physics에 맡긴다.
## Cubism export 산출물 이름
```text
motions/
idle_breath.motion3.json
idle_dance.motion3.json
no.motion3.json
heart.motion3.json
greet.motion3.json
present.motion3.json
thinking.motion3.json
expressions/
neutral.exp3.json
smile.exp3.json
love.exp3.json
negative.exp3.json
thinking.exp3.json
```
## WPF 런타임 호출
```text
React("success") -> motion_heart + exp_love + caption "잘됐어요"
React("error") -> motion_no + exp_negative + caption "안돼요"
SetIdle("idle") -> motion_idle_dance loop
```
@@ -0,0 +1,42 @@
{
"name": "dance_idle",
"status": "legacy_canvas_reference",
"live2dNote": "Use this as motion rhythm reference only. Final Cubism motion plan is live2d_motion_plan.json.",
"duration": 2.0,
"loop": true,
"fpsHint": 60,
"defaultEase": "sine",
"note": "tracks[bone].{rot|tx|ty|sx|sy} = keyframe arrays [{t(sec), v}]. rot=deg delta, tx/ty=px delta in parent frame, sx/sy=scale delta (0=none). Values are ADDED on top of rig rest pose. First and last key match for seamless loop. Light 2-beat groove: hip bounce+sway, counter chest, head side-to-side, alternating arm pump, alternating knee bend.",
"tracks": {
"pelvis": {
"ty": [ {"t":0,"v":0}, {"t":0.5,"v":10}, {"t":1.0,"v":0}, {"t":1.5,"v":10}, {"t":2.0,"v":0} ],
"tx": [ {"t":0,"v":0}, {"t":0.5,"v":7}, {"t":1.0,"v":0}, {"t":1.5,"v":-7}, {"t":2.0,"v":0} ],
"rot": [ {"t":0,"v":0}, {"t":0.5,"v":2}, {"t":1.0,"v":0}, {"t":1.5,"v":-2}, {"t":2.0,"v":0} ]
},
"chest": {
"ty": [ {"t":0,"v":0}, {"t":0.5,"v":-3}, {"t":1.0,"v":0}, {"t":1.5,"v":-3}, {"t":2.0,"v":0} ],
"tx": [ {"t":0,"v":0}, {"t":0.5,"v":-3}, {"t":1.0,"v":0}, {"t":1.5,"v":3}, {"t":2.0,"v":0} ],
"rot": [ {"t":0,"v":0}, {"t":0.5,"v":-3}, {"t":1.0,"v":0}, {"t":1.5,"v":3}, {"t":2.0,"v":0} ]
},
"neck": {
"rot": [ {"t":0,"v":0}, {"t":0.5,"v":3}, {"t":1.0,"v":0}, {"t":1.5,"v":-3}, {"t":2.0,"v":0} ]
},
"head": {
"rot": [ {"t":0,"v":0}, {"t":0.5,"v":7}, {"t":1.0,"v":0}, {"t":1.5,"v":-7}, {"t":2.0,"v":0} ],
"ty": [ {"t":0,"v":0}, {"t":0.5,"v":-2}, {"t":1.0,"v":0}, {"t":1.5,"v":-2}, {"t":2.0,"v":0} ]
},
"upperarm_r": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":8}, {"t":1.0,"v":0}, {"t":1.5,"v":-4}, {"t":2.0,"v":0} ] },
"forearm_r": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":12}, {"t":1.0,"v":0}, {"t":1.5,"v":-6}, {"t":2.0,"v":0} ] },
"hand_r": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":6}, {"t":1.0,"v":0}, {"t":1.5,"v":-3}, {"t":2.0,"v":0} ] },
"upperarm_l": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":-4}, {"t":1.0,"v":0}, {"t":1.5,"v":8}, {"t":2.0,"v":0} ] },
"forearm_l": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":-6}, {"t":1.0,"v":0}, {"t":1.5,"v":12}, {"t":2.0,"v":0} ] },
"hand_l": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":-3}, {"t":1.0,"v":0}, {"t":1.5,"v":6}, {"t":2.0,"v":0} ] },
"thigh_r": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":3}, {"t":1.0,"v":0}, {"t":1.5,"v":-2}, {"t":2.0,"v":0} ] },
"shin_r": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":7}, {"t":1.0,"v":0}, {"t":1.5,"v":0}, {"t":2.0,"v":0} ] },
"thigh_l": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":-2}, {"t":1.0,"v":0}, {"t":1.5,"v":3}, {"t":2.0,"v":0} ] },
"shin_l": { "rot": [ {"t":0,"v":0}, {"t":0.5,"v":0}, {"t":1.0,"v":0}, {"t":1.5,"v":7}, {"t":2.0,"v":0} ] }
}
}
@@ -0,0 +1,114 @@
{
"name": "LeeSori Live2D Motion Plan",
"version": 1,
"units": {
"time": "seconds",
"parameterValues": "Live2D Cubism parameter values"
},
"motions": [
{
"id": "motion_idle_breath",
"file": "motions/idle_breath.motion3.json",
"duration": 3.0,
"loop": true,
"priority": "idle",
"curves": [
{ "param": "ParamBreath", "keys": [[0, 0], [1.5, 1], [3.0, 0]] },
{ "param": "ParamBodyAngleY", "keys": [[0, 0], [1.5, 1.5], [3.0, 0]] },
{ "param": "ParamAngleZ", "keys": [[0, 0], [1.5, 1.5], [3.0, 0]] }
]
},
{
"id": "motion_idle_dance",
"file": "motions/idle_dance.motion3.json",
"duration": 2.0,
"loop": true,
"priority": "idle",
"reference": "05_Animation/dance_idle.json",
"curves": [
{ "param": "ParamBodyAngleX", "keys": [[0, 0], [0.5, 3], [1.0, 0], [1.5, -3], [2.0, 0]] },
{ "param": "ParamBodyAngleZ", "keys": [[0, 0], [0.5, 4], [1.0, 0], [1.5, -4], [2.0, 0]] },
{ "param": "ParamAngleZ", "keys": [[0, 0], [0.5, 6], [1.0, 0], [1.5, -6], [2.0, 0]] },
{ "param": "ParamBreath", "keys": [[0, 0.2], [0.5, 0.8], [1.0, 0.2], [1.5, 0.8], [2.0, 0.2]] },
{ "param": "ParamArmRA", "keys": [[0, 0], [0.5, 8], [1.0, 0], [1.5, -4], [2.0, 0]] },
{ "param": "ParamArmLA", "keys": [[0, 0], [0.5, -4], [1.0, 0], [1.5, 8], [2.0, 0]] }
]
},
{
"id": "motion_no",
"file": "motions/no.motion3.json",
"duration": 2.4,
"loop": false,
"priority": "force",
"expressionDefault": "exp_negative",
"curves": [
{ "param": "ParamAngleZ", "keys": [[0, 0], [0.35, 0], [0.65, 10], [0.95, -10], [1.25, 10], [1.55, -8], [1.9, 0], [2.4, 0]] },
{ "param": "ParamBrowLAngle", "keys": [[0, 0], [0.4, -0.7], [2.0, -0.7], [2.4, 0]] },
{ "param": "ParamBrowRAngle", "keys": [[0, 0], [0.4, -0.7], [2.0, -0.7], [2.4, 0]] },
{ "param": "ParamMouthForm", "keys": [[0, 0], [0.4, -0.5], [2.0, -0.5], [2.4, 0]] },
{ "param": "ParamArmLA", "keys": [[0, 0], [0.25, 25], [2.1, 25], [2.4, 0]] },
{ "param": "ParamArmRA", "keys": [[0, 0], [0.25, 25], [2.1, 25], [2.4, 0]] }
],
"notes": "If crossed arms look poor with base arm layers, create swap_arm_cross_L/R and drive visibility by ParamArmLA/RA."
},
{
"id": "motion_heart",
"file": "motions/heart.motion3.json",
"duration": 2.2,
"loop": false,
"priority": "normal",
"expressionDefault": "exp_love",
"curves": [
{ "param": "ParamBodyAngleY", "keys": [[0, 0], [0.55, 3], [0.85, 0], [1.2, 3], [1.55, 0], [2.2, 0]] },
{ "param": "ParamAngleZ", "keys": [[0, 0], [0.55, 4], [0.85, -4], [1.2, 4], [1.55, 0], [2.2, 0]] },
{ "param": "ParamEyeLSmile", "keys": [[0, 0], [0.35, 1], [1.8, 1], [2.2, 0]] },
{ "param": "ParamEyeRSmile", "keys": [[0, 0], [0.35, 1], [1.8, 1], [2.2, 0]] },
{ "param": "ParamMouthForm", "keys": [[0, 0], [0.35, 0.7], [1.8, 0.7], [2.2, 0]] },
{ "param": "ParamArmLA", "keys": [[0, 0], [0.35, 35], [1.8, 35], [2.2, 0]] },
{ "param": "ParamArmRA", "keys": [[0, 0], [0.35, 35], [1.8, 35], [2.2, 0]] },
{ "param": "ParamHandL", "keys": [[0, 0], [0.35, 8], [1.8, 8], [2.2, 0]] },
{ "param": "ParamHandR", "keys": [[0, 0], [0.35, 8], [1.8, 8], [2.2, 0]] }
],
"notes": "Use swap_hand_heart_L/R if finger heart cannot be rigged cleanly."
},
{
"id": "motion_greet",
"file": "motions/greet.motion3.json",
"duration": 1.8,
"loop": false,
"priority": "normal",
"expressionDefault": "exp_smile",
"curves": [
{ "param": "ParamArmRA", "keys": [[0, 0], [0.25, 40], [1.5, 40], [1.8, 0]] },
{ "param": "ParamHandR", "keys": [[0, 0], [0.4, -8], [0.7, 8], [1.0, -8], [1.3, 8], [1.6, 0]] },
{ "param": "ParamAngleZ", "keys": [[0, 0], [0.5, 3], [1.2, -3], [1.8, 0]] }
]
},
{
"id": "motion_present",
"file": "motions/present.motion3.json",
"duration": 2.0,
"loop": false,
"priority": "normal",
"expressionDefault": "exp_neutral",
"curves": [
{ "param": "ParamArmLA", "keys": [[0, 0], [0.4, 28], [1.6, 28], [2.0, 0]] },
{ "param": "ParamHandL", "keys": [[0, 0], [0.4, 4], [1.6, 4], [2.0, 0]] },
{ "param": "ParamAngleX", "keys": [[0, 0], [0.4, -5], [1.6, -5], [2.0, 0]] }
]
},
{
"id": "motion_thinking",
"file": "motions/thinking.motion3.json",
"duration": 2.0,
"loop": true,
"priority": "normal",
"expressionDefault": "exp_thinking",
"curves": [
{ "param": "ParamAngleZ", "keys": [[0, 0], [0.8, -7], [1.6, -5], [2.0, 0]] },
{ "param": "ParamEyeBallX", "keys": [[0, 0], [0.8, -0.4], [1.6, -0.4], [2.0, 0]] },
{ "param": "ParamBrowLY", "keys": [[0, 0], [0.8, 0.5], [1.6, 0.5], [2.0, 0]] }
]
}
]
}