feat: Implement facing profiles and bend direction toggles in test harness
- Added functionality for skeleton IK bone switches, allowing users to dynamically change the bend direction of joints via a context menu. - Introduced facing profiles (LEFT, RIGHT, FORWARD) that modify the bend direction of limbs based on the selected profile. - Implemented body part z-ordering based on the facing profile, ensuring correct rendering order of limbs relative to the torso. - Added a coordinates display panel that shows the position and rotation of the Skeleton2D and its bones, as well as IK target positions. - Created a new script to generate a walk animation for the stickman rig, including keyframe tracks for various IK targets. - Updated documentation to reflect the new features and their specifications.
This commit is contained in:
@@ -256,6 +256,78 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
overlay also draws a **null-guarded semi-transparent yellow aim line** from the Head bone
|
||||
origin to the head marker (`_draw_ik_handles`, width `1.5/zoom`, alpha `0.5`) — a visual aid
|
||||
for the LookAt test.
|
||||
- **Phase 9 Task 1 skeleton IK bone switches:** adds a "Facing" `MenuButton` (leftmost control
|
||||
in the top-bar `HBox`) and per-joint bend-direction toggles for the rig's TwoBoneIK "Flip
|
||||
Bend Direction" flags. `enum FacingProfile { LEFT, RIGHT, FORWARD }` (values used directly as
|
||||
menu item ids); `BEND_JOINTS: Array[String] = ["LeftArm","RightArm","LeftLeg","RightLeg"]`
|
||||
with `BEND_JOINT_BONE_PATHS` (each joint → its lower `Bone2D` NodePath relative to
|
||||
`Skeleton2D`, e.g. `Torso/LeftUpperArm/LeftLowerArm`); `PROFILE_FLAGS` maps each profile to a
|
||||
per-joint `flip_bend_direction` set (LEFT: arms off / legs on; RIGHT: arms on / legs off;
|
||||
FORWARD: LeftArm off, RightArm on, LeftLeg on, RightLeg off — matching `master_rig.tscn`'s
|
||||
authored defaults). State `_facing_profile` (default `FORWARD`, harness-level, persists across
|
||||
spawns), `_context_joint`, `_bend_joint_bones`, `_bend_modifications`, `_facing_button`/
|
||||
`_facing_menu`, `_context_menu`. The Facing popup (Left/Right/Forward) uses dynamic text-only
|
||||
labels with the current profile prefixed `[√] `, refreshed on `about_to_popup` and after
|
||||
selection (mirroring the editor's snap-menu pattern). Runtime resolution:
|
||||
`_resolve_bend_joints()` (called from `_resolve_rig_nodes()`) resolves the 4 lower-limb
|
||||
`Bone2D`s via `get_node_or_null` and matches each `SkeletonModification2DTwoBoneIK` in the
|
||||
modification stack by its `joint_two_bone2d_node` NodePath (no hardcoded stack index);
|
||||
`push_warning` on missing nodes/mods. Per-joint toggle: right-click inside the viewport on an
|
||||
elbow/knee (the upper↔lower limb connector, within `JOINT_HIT_RADIUS_PX := 14.0` screen px
|
||||
converted to world by `_camera.zoom.x`, nearest joint wins) pops a one-item context menu
|
||||
labeled **"Normal Bend"** (when `flip_bend_direction` is currently true) or **"Invert Bend"**
|
||||
(when false); selecting toggles that joint's `flip_bend_direction` on the live TwoBoneIK
|
||||
modification. Only the 4 elbows/knees are right-click targets — shoulders/hips/wrists/ankles/
|
||||
head/torso are not. Lifecycle: `_free_current_rig()` clears `_bend_joint_bones`/
|
||||
`_bend_modifications`/`_context_joint`; `_load_and_spawn()` re-applies
|
||||
`_apply_facing_profile(_facing_profile)` right after `_ensure_modification_stack_enabled()` so
|
||||
every fresh spawn matches the current profile. No persistence to disk.
|
||||
- **Phase 9 Task 2 body-part z-order:** `_apply_facing_profile()` now also sets the
|
||||
`_facing_profile` state itself (previously only the menu handler did) and calls
|
||||
`_apply_body_z_order()`, so bend flags + draw order stay in sync from one entry point.
|
||||
`const BODY_CONTAINER_PATH := "Body"` and `const Z_ORDER_BY_PROFILE: Dictionary` map each
|
||||
`FacingProfile` to the rig `Body/*` visual part node names in **back-to-front draw order**
|
||||
(Godot 4 `Node2D` draws siblings in tree order; all parts keep `z_index = 0`). FORWARD:
|
||||
torso → left/right upper legs → left/right lower legs → left/right upper arms → left/right
|
||||
lower arms → head (all limbs in front of the torso); LEFT: left arm pair then left leg
|
||||
pair **behind** the torso, right leg pair then right arm pair in front; RIGHT: mirrored.
|
||||
In every profile upper limbs stay behind lower limbs; far-side (behind-torso) arms draw
|
||||
behind the legs while near-side arms draw in front of the legs; the **head is always
|
||||
frontmost**. State `_body_container: Node2D` (resolved in
|
||||
`_resolve_rig_nodes()` via `get_node_or_null`, null-guarded with `push_warning`, cleared
|
||||
in `_free_current_rig()`). `_apply_body_z_order()` walks the profile array back-to-front
|
||||
and `move_child(part, count - 1)`s each existing child (missing parts skipped), which
|
||||
yields the profile order; unknown extra children stay at the back. Safe with the adapter:
|
||||
shapes are children of the part nodes, so moving a part moves its whole shape group. No
|
||||
persistence to disk.
|
||||
- **Phase 9 Task 3 coordinates display:** adds a "Show Coords" `CheckBox` in the top-bar
|
||||
`HBox` (after "Show IK Handles", before the status label), default ON, toggled via
|
||||
`_on_show_coords_toggled(pressed)` (sets `_show_coords: bool = true`, flips
|
||||
`_coords_panel.visible`). A code-built right-side readout (`_build_coords_panel()`, called
|
||||
from `_build_ui()` after the viewport container so it renders in front): `_coords_panel`
|
||||
(`PanelContainer`) + monospace selectable `_coords_label` (`RichTextLabel`,
|
||||
`selection_enabled` + `context_menu_enabled` + `fit_content`, autowrap off, scroll off,
|
||||
`FOCUS_CLICK`; `SystemFont`: Consolas/Menlo/DejaVu Sans Mono/Courier New via the
|
||||
`normal_font`/`normal_font_size` (18) theme overrides), anchored `PRESET_TOP_RIGHT`,
|
||||
`offset_top = 40.0`, `offset_right = -8.0`, `offset_left = -COORDS_PANEL_WIDTH` (320.0),
|
||||
`grow_vertical = GROW_DIRECTION_END` + `grow_horizontal = GROW_DIRECTION_BEGIN`
|
||||
(auto-height/width, grows left so text never runs off-screen), `mouse_filter =
|
||||
MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for selection);
|
||||
StyleBoxFlat
|
||||
bg `Color(0,0,0,0.55)`, border `Color(1,1,1,0.12)` w1, corner radius 4, content margin 8.
|
||||
Consts `COORDS_PANEL_WIDTH := 320.0` and `COORD_BONE_PATHS` (10 Skeleton2D-relative bone
|
||||
paths: Torso, Torso/Head, both upper/lower arms, both upper/lower legs). State
|
||||
`_coord_bones: Dictionary` (bone display name → `Bone2D`, keyed by `path.get_file()`),
|
||||
`_show_coords`. `_resolve_rig_nodes()` calls `_resolve_coord_bones()` (via
|
||||
`_skeleton.get_node_or_null`, `push_warning` on missing); `_free_current_rig()` clears
|
||||
`_coord_bones` (toggle persists across respawns). `_process(delta)` early-outs when hidden
|
||||
or label null, else `_update_coords_display()` — sections "Skeleton2D" pos + rot deg,
|
||||
"Bones" pos + rot deg, "IK Targets" pos only (reusing `IK_HANDLE_PATHS`/`_ik_handles`);
|
||||
"No rig loaded" fallback; every read guarded with `is_instance_valid`; the label text is
|
||||
only reassigned when the built string changes, so an active text selection survives idle
|
||||
frames. Values are
|
||||
world-space (`global_position`/`global_rotation`), rotation in degrees via
|
||||
`_fmt_deg(rad)` (1 decimal, `°`), `_fmt_vec2(v)` for positions. No persistence to disk.
|
||||
- Scenes:
|
||||
- `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
|
||||
for typed `@onready` access: `%MenuBar`, `%StickmanNameEdit`, `%LeftColumn`,
|
||||
@@ -269,7 +341,8 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
|
||||
height in their parent VBoxContainer.
|
||||
- `scenes/test_harness.tscn` — **standalone staging scene** (Phase 9, not wired into the
|
||||
editor; run via **F6**). Backed by `scripts/test_harness.gd`. Top UI bar with file open /
|
||||
quick-select, "Show Bones" / "Show IK Handles" toggles, and a loaded-filename status label;
|
||||
quick-select, "Show Bones" / "Show IK Handles" / "Show Coords" toggles, and a
|
||||
loaded-filename status label;
|
||||
`SubViewport` world with an enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on
|
||||
spawn); interactive limb IK via `SkeletonModificationStack2D` TwoBoneIK.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user