Initial Dansori character workspace
This commit is contained in:
@@ -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]] }
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user