feat(characters): add modular woodland traveler kit

This commit is contained in:
Rijad Zuzo
2026-09-05 22:54:54 +02:00
parent 70ee6feddf
commit 4ff8858b19
39 changed files with 2037 additions and 50 deletions
+116
View File
@@ -0,0 +1,116 @@
# Woodland character kit
Original Blender geometry inspired by the supplied cat-traveler reference:
broad cheeks, relaxed almond eyes, whiskers, layered clothing, rolled cuffs,
boots, a scarf, and a travel pack with a bedroll. The reference image is not
embedded in the assets. Player and NPC visuals use this kit in the main game.
## Try and configure
Run `tools/CharacterWorkshop.tscn` in Godot 4.7 (F6), or:
```bash
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" tools/CharacterWorkshop.tscn
```
Choose one of six presets, change the parts and proportions, drag the model to
turn it, scroll over the model to zoom, and enable the walking preview. **Save
profile** writes a `CharacterAppearanceProfile` `.tres` to the chosen file.
Assign that resource to `PlayerVisual.appearance` or `NpcVisual.appearance` in
the Inspector. The player defaults to `profiles/reference_cat.tres`. NPCs with
no authored override derive their appearance from their stable simulation ID.
The workshop is a separate authoring scene; it does not modify a game save.
| Control | Available values |
| --- | --- |
| Head and tail | Cat, fox, rabbit, badger, otter |
| Ears | Species default, or any of six ear pairs including lop ears |
| Height | 0.801.15 times the base height |
| Body fullness | 01 Blender `Stout` shape key |
| Head size / leg length / ear length | 0.881.12 / 0.851.15 / 0.651.20 |
| Clothing | Open vest, long coat, work tunic with apron |
| Arms | Relaxed or holding pack straps |
| Accessories | Scarf, spectacles, beret, brimmed hat, satchel, travel pack |
| Palette | Fur, muzzle, outfit, shirt, scarf, trousers, leather |
The workshop exposes the four main palette colors. All seven channels are
editable on the profile resource. Profession tint remains a separate, subtle
overlay driven by the actual NPC profession.
## Blender source and rebuilding
Open `art/characters/woodland_character_kit.blend`. The visible collection is an
assembled traveler; the hidden library meshes contain the editable building
blocks. Unhide a library object to edit it. Torso and wearable meshes have
`Basis` and `Stout` shape keys. Preview objects are copies of library objects.
```bash
/Applications/Blender.app/Contents/MacOS/Blender --background \
--python tools/art/build_character_kit.py
/Applications/Godot.app/Contents/MacOS/Godot --headless --editor --path "$PWD" --import
```
The reproducible Blender 5.1 generator rebuilds the source, GLB, and triangle
manifest. Regenerating replaces manual edits to those generated files. Commit
changes to the generator when they should survive a rebuild. Godot only imports
the portable GLB; `.gdignore` excludes the Blender authoring directory.
## Mesh and animation contract
`woodland_parts.glb` contains 31 named parts, each with one opaque surface. The
reference cat uses 10 surfaces and 20,070 base triangles before Godot's generated
LODs. The GLB is approximately 4.6 MiB. `mesh_budget.json` records each part's
triangle count, origin and shape-key support. Accessories are optional; only
selected meshes enter the live scene.
- `AnimalAppearance` caches the imported mesh library once. Characters share
`ArrayMesh` resources and have their own shader material and blend-shape
weights. No per-frame mesh rebuilding or CPU deformation is performed.
- Godot Y is up, +Z is forward. The existing body, head, arm, leg and tail node
paths remain animation sockets. Ear/glasses attachments follow the head;
scarf and bag attachments follow the torso. `Hair` is the optional hat socket.
- Fullness deforms the torso and worn gear together. Shoulder/hip spacing follows
it. Leg length raises the upper body while keeping the soles on the ground.
Gait uses configured rest heights; the hiker pose has restrained arm swing.
- Animation remains the existing rigid-part procedural gait. The kit does not
add a skeletal animation library, facial rig, cloth simulation, or physics
bodies. Changes to these appearance controls are cosmetic: collision,
navigation, interaction reach, authoritative positions, and saves retain
their existing contracts. Very unusual mixed parts may need art adjustment.
- `UV2.x` stores a discrete palette role (0 fixed vertex paint, 1 fur, 2 muzzle,
3 outfit, 4 linen, 5 scarf, 6 trousers, 7 leather, 8 fur markings, 9 outfit seams).
Preserve both UV layers and vertex colors on export. Do not regenerate UV2
lightmap coordinates. `character_cel.gdshader` supplies soft cel bands with
no texture sampling, transparency, outline pass, or screen-space effect.
## Verification and review
`tests/character_appearance_test.gd` checks the real imported geometry, palette
roles, independent morph weights, shared meshes, attachment reuse, all six
presets, extreme proportions, stable IDs/RNG isolation, gait rest heights, and
NPC cue clearance. Existing lifecycle scenarios cover profession, inventory,
death, and save/load behavior. Run the full repository quality gate before a
commit.
```bash
./tools/quality.sh
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-driver metal --script tools/capture_character_kit.gd
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-method gl_compatibility --script tools/capture_character_kit.gd
/Applications/Godot.app/Contents/MacOS/Godot --path "$PWD" \
--rendering-driver metal --script tools/capture_character_kit.gd -- --world
```
The `docs/baselines/character_*.png` images are actual Godot renders. They show
the traveler, rear pack detail, preset lineup, workshop and main-world placement.
These local Apple M1 Max checks do not establish a frame-time budget for a
large crowd or a weaker GPU.
The workshop uses filmic tonemapping with a separately calibrated Compatibility
exposure to preserve cream-fabric highlights. This only stages the authoring
preview; the main world retains its existing day/night environment. Metal and
Compatibility images are separate visual reviews, not a pixel-parity claim.
The full Godot 4.7 gate passes 61 scenario runs (including 111 character checks)
and 182 GUT tests / 2,279 assertions. The native workshop controls and profile
export were also exercised directly.
+312
View File
@@ -0,0 +1,312 @@
{
"TorsoVest": {
"triangles": 2914,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"TorsoCoat": {
"triangles": 3074,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"TorsoTunic": {
"triangles": 1894,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"ArmRelaxedLeft": {
"triangles": 996,
"surfaces": 1,
"pivot": [
-0.44,
1.3,
0
],
"stout_shape": false
},
"ArmHikerLeft": {
"triangles": 2144,
"surfaces": 1,
"pivot": [
-0.44,
1.3,
0
],
"stout_shape": false
},
"LegLeft": {
"triangles": 1074,
"surfaces": 1,
"pivot": [
-0.2,
0.61,
0
],
"stout_shape": false
},
"ArmRelaxedRight": {
"triangles": 996,
"surfaces": 1,
"pivot": [
0.44,
1.3,
0
],
"stout_shape": false
},
"ArmHikerRight": {
"triangles": 2144,
"surfaces": 1,
"pivot": [
0.44,
1.3,
0
],
"stout_shape": false
},
"LegRight": {
"triangles": 1074,
"surfaces": 1,
"pivot": [
0.2,
0.61,
0
],
"stout_shape": false
},
"HeadCat": {
"triangles": 6798,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsCat": {
"triangles": 202,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailCat": {
"triangles": 584,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadFox": {
"triangles": 6798,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsFox": {
"triangles": 202,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailFox": {
"triangles": 636,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadRabbit": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsRabbit": {
"triangles": 328,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailRabbit": {
"triangles": 440,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadBadger": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsBadger": {
"triangles": 1456,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailBadger": {
"triangles": 248,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"HeadOtter": {
"triangles": 6684,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
},
"EarsOtter": {
"triangles": 1456,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"TailOtter": {
"triangles": 360,
"surfaces": 1,
"pivot": [
0,
0.8,
-0.22
],
"stout_shape": false
},
"EarsLop": {
"triangles": 328,
"surfaces": 1,
"pivot": [
0,
2.2,
0
],
"stout_shape": false
},
"Scarf": {
"triangles": 396,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"BagTravel": {
"triangles": 2740,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"BagSatchel": {
"triangles": 640,
"surfaces": 1,
"pivot": [
0,
1.05,
0
],
"stout_shape": true
},
"HatBeret": {
"triangles": 880,
"surfaces": 1,
"pivot": [
0,
1.9,
0
],
"stout_shape": false
},
"HatBrim": {
"triangles": 976,
"surfaces": 1,
"pivot": [
0,
1.9,
0
],
"stout_shape": false
},
"Spectacles": {
"triangles": 644,
"surfaces": 1,
"pivot": [
0,
1.68,
0
],
"stout_shape": false
}
}
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 3
height = 0.98
body_fullness = 1.0
leg_length = 0.9
outfit = 2
arm_pose = 0
bag = 0
hat = 1
scarf = false
fur_color = Color(0.79, 0.77, 0.71, 1)
outfit_color = Color(0.47, 0.5, 0.35, 1)
@@ -0,0 +1,16 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 1
height = 1.12
body_fullness = 0.15
leg_length = 1.12
outfit = 1
arm_pose = 0
hat = 2
bag = 1
fur_color = Color(0.72, 0.43, 0.24, 1)
outfit_color = Color(0.42, 0.49, 0.33, 1)
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 2
ears = 4
height = 0.92
body_fullness = 0.8
ear_length = 1.12
outfit = 2
arm_pose = 0
bag = 0
scarf = false
fur_color = Color(0.80, 0.78, 0.73, 1)
outfit_color = Color(0.6, 0.4, 0.28, 1)
@@ -0,0 +1,15 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 4
height = 1.06
body_fullness = 0.42
head_size = 0.94
outfit = 0
arm_pose = 0
bag = 1
fur_color = Color(0.51, 0.42, 0.33, 1)
outfit_color = Color(0.41, 0.43, 0.54, 1)
@@ -0,0 +1,17 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
species = 2
height = 0.84
body_fullness = 0.18
head_size = 1.08
leg_length = 0.92
outfit = 1
arm_pose = 0
bag = 1
spectacles = true
fur_color = Color(0.76, 0.72, 0.64, 1)
outfit_color = Color(0.57, 0.37, 0.31, 1)
@@ -0,0 +1,6 @@
[gd_resource type="Resource" script_class="CharacterAppearanceProfile" load_steps=2 format=3]
[ext_resource type="Script" path="res://world/presentation/CharacterAppearanceProfile.gd" id="1"]
[resource]
script = ExtResource("1")
Binary file not shown.
@@ -0,0 +1,45 @@
[remap]
importer="scene"
importer_version=1
type="PackedScene"
uid="uid://b4p2tfex61t8o"
path="res://.godot/imported/woodland_parts.glb-1394516718c8f9a3ee6122be9d99f45f.scn"
[deps]
source_file="res://assets/characters/woodland_parts.glb"
dest_files=["res://.godot/imported/woodland_parts.glb-1394516718c8f9a3ee6122be9d99f45f.scn"]
[params]
nodes/root_type=""
nodes/root_name=""
nodes/root_script=null
mesh_library/use_node_names_as_mesh_names=false
array_mesh/deduplicate_surfaces=true
nodes/apply_root_scale=true
nodes/root_scale=1.0
nodes/import_as_skeleton_bones=false
nodes/use_name_suffixes=true
nodes/use_node_type_suffixes=true
meshes/ensure_tangents=true
meshes/generate_lods=true
meshes/create_shadow_meshes=true
meshes/light_baking=1
meshes/lightmap_texel_size=0.2
meshes/force_disable_compression=false
skins/use_named_skins=true
animation/import=true
animation/fps=30
animation/trimming=false
animation/remove_immutable_tracks=true
animation/import_rest_as_RESET=false
import_script/path=""
materials/extract=0
materials/extract_format=0
materials/extract_path=""
_subresources={}
gltf/naming_version=2
gltf/embedded_image_handling=1
gltf/texture_map_mode=1