Everything about getting an anime-styled character into the game — import, stylization, the body/garment/hair separation, rigging, retargeting, cloth and hair physics, and how to measure any of it — collected into .claude/skills/character-pipeline/. Organised around the principle the Hoyoverse-class pipelines are built on and that every failure in this project traced back to: a character is not one object. It is a body, a set of garments and hair, authored and rigged separately and moved by different systems. The body is skinned and animated; the garments and hair are bone chains the animation never touches and physics moves. The skill's non-negotiables are the four ways that separation has been destroyed here before — joining meshes, keying cosmetic bones, skinning cloth to a leg, and running the auto-rig repair on authored weights. references/verification.md leads with the trap that invalidated every cloth measurement ever taken in this repo: Godot restores bone poses after the modifier pass, so a tool that reads them afterwards measures the animation and never sees what any modifier did. Also records what is known-unsolved, with numbers: peak cloth clipping in a run/slide/dash, no foot IK, no strafe clips. Co-Authored-By: Claude Opus 5 <[email protected]>
140 lines
7.0 KiB
Markdown
140 lines
7.0 KiB
Markdown
---
|
|
name: character-pipeline
|
|
description: Import, rig, stylize and animate an anime-styled character into Papaya-Shooter as a selectable skin — keeping the model's own skeleton, artist weights and separate body/cloth/hair meshes, with cloth and hair driven by the spring solver. Use when adding a new playable character, re-importing an existing one, debugging skinning/cloth/hair/animation problems on a character, or changing the cel-shaded look. Triggers on "add a character", "import a skin", "new playable model", "skirt clipping", "hair flailing", "T-posing", "character looks squashed".
|
|
---
|
|
|
|
# Character pipeline
|
|
|
|
Turns a source model into a playable, cel-shaded, cloth-simulated character skin.
|
|
|
|
The whole design follows one principle, which is also what the Hoyoverse-class
|
|
anime pipelines (Genshin / Star Rail / Zenless Zone Zero) are built on:
|
|
|
|
> **The character is not one object. It is a body, a set of garments, and hair —
|
|
> authored separately, rigged separately, and moved by different systems.**
|
|
> The body is skinned and animated. The garments and hair are bone chains that
|
|
> the animation never touches; physics moves them. Keeping those separate is
|
|
> what makes the result read as an anime character instead of a mannequin in a
|
|
> painted-on costume.
|
|
|
|
Everything below exists to protect that separation.
|
|
|
|
## The one rule
|
|
|
|
**If a model arrives with a skeleton, that skeleton ships.** Its bones, its
|
|
artist-painted weights, its per-part meshes and its skirt/hair chains all
|
|
survive. Only the ANIMATION is moved onto it.
|
|
|
|
The old route (`strip_rig.py` → `autorig.py` → `merge_animations.py`) solved a
|
|
bone-*naming* problem by destroying the asset — 18 meshes became 1, 21 skirt
|
|
bones and ~50 hair bones became 0, and 16% of vertices ended up pulled by both
|
|
legs. `tools/rig_map.py` solves naming properly now. **Never reach for
|
|
`strip_rig.py` or `--rebind`** unless the model genuinely has no skeleton at all.
|
|
|
|
## Doing it
|
|
|
|
```bash
|
|
# From an already-rigged local model (the normal case)
|
|
python tools/pipeline.py --input assets/characters/incoming/<name>.glb --name <name> --rigged
|
|
|
|
# From a Sketchfab UID (needs SKETCHFAB_API_TOKEN)
|
|
python tools/pipeline.py --uid <uid> --name <name>
|
|
|
|
# From an unrigged mesh — auto-rigs, and accepts the quality loss
|
|
python tools/pipeline.py --input <mesh.glb> --name <name>
|
|
```
|
|
|
|
Then, once, so Godot sees the new files:
|
|
|
|
```bash
|
|
godot --headless --path . --import
|
|
```
|
|
|
|
The result is `assets/characters/skins/<name>.glb` + `<name>.rig.json`, and a
|
|
registry entry in `skins.json` that `SkinManager` picks up with no code change.
|
|
|
|
Blender is required (`BLENDER_PATH`, or auto-found under
|
|
`C:\Program Files\Blender Foundation`). Godot lives at
|
|
`C:\Program Files\Godot\Godot_v4.7-stable_win64_console.exe`.
|
|
|
|
## The stages, and what each one protects
|
|
|
|
| Stage | Where | Protects |
|
|
|---|---|---|
|
|
| Fix unlit/emissive materials | `tools/gltf_fix.py` | Textures surviving import at all |
|
|
| Resolve bone ROLES, not names | `tools/rig_map.py` | The model's own skeleton |
|
|
| Rebuild parenting | `retarget.py::rebuild_hierarchy` | Limbs/cloth following the hips |
|
|
| Subdivide cloth panels | `retarget.py::subdivide_cloth_panels` | A skirt being able to bend at all |
|
|
| Retarget clips as rest-relative deltas | `retarget.py::retarget_clip` | Limbs not being twisted by foreign bone roll |
|
|
| Leave cosmetic bones unkeyed | `export_optimize_animation_keep_anim_armature=False` | Physics owning the cloth |
|
|
| Write the rig sidecar | `retarget.py::describe_rig` | The runtime never re-guessing anatomy |
|
|
| Cel look | `LevelMaterials.apply_character_look` | The model's own line-work not being re-lit |
|
|
| Cloth + hair | `characters/spring_bones.gd` | Clothes reading as clothes |
|
|
|
|
## Read before you touch anything
|
|
|
|
Load the reference that matches what you are doing. They are short and each one
|
|
is a list of things that cost a debugging cycle to learn.
|
|
|
|
- **`references/separation.md`** — body vs. garments vs. hair: what must stay
|
|
separate, how cloth chains are detected and classed, why cloth is never
|
|
skinned to a leg, and the ZZZ-convention mapping.
|
|
- **`references/rigging.md`** — role resolution, hierarchy rebuild, cloth panel
|
|
subdivision, twist bones, joint helpers, the retarget maths.
|
|
- **`references/cloth-and-hair.md`** — the position-based spring solver, its
|
|
colliders, per-class tuning, collision hulls, LOD and cost.
|
|
- **`references/stylization.md`** — cel shading, outlines, the imported
|
|
line-work trap, eyes, materials.
|
|
- **`references/verification.md`** — every measuring tool, what each one
|
|
actually measures, and the pose-reading trap that invalidated all of them
|
|
once. **Read this before trusting any measurement.**
|
|
|
|
## Non-negotiables
|
|
|
|
1. **Never join meshes.** Per-part meshes are how body, cloth and hair stay
|
|
separable — for materials, for the outline pass, and for the cloth solver's
|
|
hull extraction.
|
|
2. **Never key cosmetic bones.** If a clip has tracks on skirt/hair bones, the
|
|
AnimationPlayer overwrites the solver every frame and the cloth goes rigid.
|
|
3. **Never skin cloth to a leg.** A vertex weighted 0.9 to a thigh cannot be
|
|
moved by its own cloth bone, so the solver loses the authority to push it out
|
|
of that thigh — and the leg still overtakes it. There was a
|
|
`bind_cloth_to_legs()`; it is deleted, and the note above its grave in
|
|
`retarget.py` says why.
|
|
4. **Never run `SkinLegRepair` on authored weights.** It snaps weights and
|
|
deletes triangles. It exists only to undo auto-rigging. It is gated on
|
|
`weights_authored`, which is MEASURED, not assumed.
|
|
5. **Measure from inside the modifier pass.** See `references/verification.md`.
|
|
|
|
## After importing
|
|
|
|
```bash
|
|
godot --headless --path . -s res://debug/spawn_smoke_test.gd # 29 checks
|
|
godot --headless --path . -s res://debug/cloth_clip_check.gd # leg-through-cloth
|
|
godot --headless --path . -s res://debug/cloth_settle_check.gd # idle stability
|
|
godot --headless --path . -s res://debug/cloth_perf_check.gd # ms per character
|
|
godot --headless --path . -s res://debug/travel_dir_check.gd # legs face travel
|
|
```
|
|
|
|
What "good" looks like on Taila, for calibration:
|
|
|
|
| Measure | Good | Bad |
|
|
|---|---|---|
|
|
| Idle skirt movement | < 0.1 deg/frame | 0.5+, or never decaying |
|
|
| Leg inside cloth, idle/walk | < 25 mm | 100 mm |
|
|
| Leg inside cloth, run/slide/dash | ~95 mm *(current, unsolved)* | — |
|
|
| Solver cost | ~2.6 ms/character | 10 ms |
|
|
| Stride vs. travel direction | < 10° (except a capped sidestep) | 90° |
|
|
|
|
## Known-unsolved
|
|
|
|
- **Peak cloth clipping** in a run, slide and dash sits at ~95 mm of thigh
|
|
inside the skirt. Idle, walk and fall are clean. The solver sees the contact
|
|
and pushes on it every iteration; the remaining gap is a standing fight
|
|
between the collision and the garment's own shape constraints.
|
|
- **No foot IK.** Feet do not plant on ground height, so stairs and uneven
|
|
ground read as sliding.
|
|
- **No strafe or backpedal clips.** Direction is conveyed by yawing the hips
|
|
(`SkinnedPlayerModel._update_travel`), which is capped, so a pure sidestep
|
|
still runs its legs ~40° off the direction of travel.
|