feat: Add project roadmap and initial specifications for dynamic vector props and terrain system

- Created ROADMAP.md outlining core features, implementation phases, and detailed breakdown for the Stickman Sandbox Builder project.
- Introduced plans for dynamic vector props with `PropBlock` specification, including reusable components and utility functions for prop creation.
- Developed a vector terrain system plan detailing the reusable `TerrainBlock` component and associated utility functions for geometry handling.
- Implemented `PhysicsTestHarness` scene for testing physics interactions with dynamic props and terrain.
- Added scripts for `PropBlock`, `PropUtils`, `TerrainBlock`, and `TerrainUtils` to support dynamic prop creation and terrain management.
This commit is contained in:
2026-08-26 09:35:09 -04:00
parent 4d490eef6c
commit 6edf3e53e3
16 changed files with 1139 additions and 0 deletions
+86
View File
@@ -267,6 +267,86 @@ The factory is the intended runtime API: `StickmanFactory.spawn("res://stickmen/
Debug overlay (a world-space `Node2D` `_draw()`): true bone **segments** drawn between each `Bone2D` origin and its Bone2D children (color-coded left cyan / right orange / central white, with a joint dot per bone), with limb leaf bones drawn out to their IK targets so the forearm/shin segments and wrist/ankle joints are visible (Phase 9 Round 2) and the **Head** leaf drawn along the bone's own direction (~90 px, since its IK target is a LookAt aim point, not a joint) (Phase 9 Round 3), when **Show Bones** is on; colored markers at the six IK targets — hands green, feet blue, head **yellow**, torso **magenta** (Phase 9 Round 7) — plus a semi-transparent yellow aim line from the Head bone to the head marker, when **Show IK Handles** is on. Each load frees the previous rig and spawns a fresh one.
### 15. Vector Terrain System
The **Vector Terrain System** is a standalone, reusable component for building **crisp, resolution-independent vector terrain** (flat ground, angled ramps, stepped platforms) — with matching 2D collision. It is **not wired into the editor**; it is exercised through a dedicated staging scene run via **F6**.
**`TerrainBlock` (`res://scripts/terrain_block.gd`)** — `@tool` `class_name TerrainBlock`, `extends StaticBody2D`. A single terrain segment that builds its three children in code:
| Child | Node type | Purpose |
|---|---|---|
| Interior fill | `Polygon2D` | Fills the block's interior with `fill_color`. |
| Vector border | `Line2D` | Crisp outline using `outline_color` / `outline_width`; auto-closes the loop by appending the first vertex to the end, with `LINE_JOINT_ROUND` and round caps. |
| Collision | `CollisionPolygon2D` | Physical body; `BUILD_SOLIDS` solid decomposition (supports **concave** blocks). |
**Exported properties** (`polygon_points: PackedVector2Array`, `fill_color`, `outline_color`, `outline_width`) are driven by a single unified setter that pushes vertex changes to all three children live — no manual rebuilds.
**`TerrainUtils` (`res://scripts/terrain_utils.gd`)** — `class_name TerrainUtils`, `extends RefCounted`, static utility.
- `sanitize_points(points: PackedVector2Array, grid_size: float = 16.0) -> PackedVector2Array` — sanitizes a raw vertex list through a fixed pipeline, in order:
1. **Grid snap** — rounds each vertex to the `grid_size` grid (default **16.0**).
2. **Redundancy removal** — a local `_simplify_polyline()` (Godot 4.7 has **no** `Geometry2D.simplify_polyline()`), which drops consecutive duplicates, a closing duplicate when `last == first`, and collinear vertices.
3. **Clockwise enforcement**`Geometry2D.is_polygon_clockwise()` reverses the winding if it is not already clockwise, **guaranteeing clockwise output**.
- `spawn_block(...)` — factory that sanitizes the raw input vectors (`sanitize_points`), creates a `TerrainBlock`, applies the cleaned points, and adds it to the target container.
**`physics_test_harness.tscn` / `scripts/physics_test_harness.gd`** — `class_name PhysicsTestHarness`, `extends Node2D`; a **standalone staging scene** (run via **F6**; not wired into the editor). It builds flat ground, angled ramps, and stepped `TerrainBlock` instances via `TerrainUtils`, then instantiates `res://master_rig.tscn` standing on the flat ground. The scene root is a `Node2D` + script with a `Camera2D` at position `(0, -400)` zoom `0.5` and an empty `Environment` container. The camera wheel-zoom scales between **0.25x and 3.0x** (to test resolution independence / vector outline thickness); middle-drag pans. It also hosts the **Dynamic Vector Props** spawner (see below): press **1/2/3** to drop physics props above the angled ramp, plus a best-effort `StaticBody2D` collision proxy for the rig (which has no physics bodies of its own).
### 16. Dynamic Vector Props
The **Dynamic Vector Props** system is a standalone, reusable component for building **physical, dynamic props** (crates, balls, planks) as `RigidBody2D` bodies with crisp vector rendering and matching collision. It is **not wired into the editor**; it is exercised through the physics test harness staging scene via **F6**.
**`PropBlock` (`res://scripts/prop_block.gd`)** — `@tool` `class_name PropBlock`, `extends RigidBody2D`. A single physical prop whose children are built in code, `@tool`-safe (null-guarded for editor safety):
| Child | Node type | Purpose |
|---|---|---|
| Interior fill | `Polygon2D` | Fills the prop's interior with `fill_color`. |
| Vector outline | `Line2D` | Crisp outline using `outline_color` / `outline_width`; auto-closes the loop by appending the first vertex to the end, with `LINE_JOINT_ROUND` / `LINE_CAP_ROUND`. |
| Collision (polygon mode) | `CollisionPolygon2D` | `BUILD_SOLIDS` solid decomposition; used for `POLYGON` shape type. |
| Collision (circle mode) | `CollisionShape2D` + `CircleShape2D` | Radial 48-segment loop (`CIRCLE_SEGMENTS = 48`); used for `CIRCLE` shape type. |
**Exported properties:**
| Property | Type | Default | Behavior |
|---|---|---|---|
| `shape_type` | `@export_enum("Polygon","Circle")` | `POLYGON` | Selects which geometry + which collision node is enabled (polygon ↔ circle). |
| `polygon_points` | `PackedVector2Array` | empty | Polygon geometry; applies only when `shape_type == POLYGON`. |
| `radius` | `float` | `32.0` | Circle radius; applies only when `shape_type == CIRCLE`. |
| `fill_color` | `Color` | `(0.55, 0.35, 0.15)` | Interior fill. |
| `outline_color` | `Color` | `(0.15, 0.08, 0.02)` | Vector outline. |
| `outline_width` | `float` | `2.0` | Vector outline thickness. |
| `material_preset` | `@export_enum("None","Wood","Rubber","Cardboard","Metal")` | `None` | Sets `mass` + `physics_material_override` (`PhysicsMaterial.friction` / `.bounce`) and themed colors. |
**Material presets** (`mass_for` / `physics_material` / `tint_for` static factories):
| Preset | mass | friction | bounce | Fill tint |
|---|---|---|---|---|
| Wood | `3.0` | `0.6` | `0.1` | `(0.55, 0.38, 0.2)` |
| Rubber | `0.5` | `0.9` | `0.85` | `(0.9, 0.2, 0.2)` |
| Cardboard | `0.4` | `0.3` | `0.05` | `(0.85, 0.72, 0.45)` |
| Metal | `8.0` | `0.9` | `0.0` | `(0.5, 0.55, 0.6)` |
| None | `1.0` | `0.5` | `0.05` | default fill |
A **unified live-update setter** drives geometry to all children live — a change to `polygon_points` / `radius` / `shape_type` pushes to the `Polygon2D`, `Line2D`, and the active collision node with no manual rebuilds. Non-`NONE` presets also recolor `fill_color` and `outline_color` (outline = `tint_for(preset).darkened(0.55)`).
**`PropUtils` (`res://scripts/prop_utils.gd`)** — `class_name PropUtils`, `extends RefCounted`, static factory.
- **Primitive generators** return shape-payload dictionaries with default dimensions + color themes:
- `create_box(size := Vector2(48,48), ...)` — a 4-point `POLYGON` (wood theme).
- `create_ball(radius := 24.0, ...)` — a `CIRCLE` payload (rubber theme).
- `create_plank(length := 160.0, thickness := 16.0, ...)` — a 4-point `POLYGON` (metal theme).
- `create_triangle(base := 56.0, height := 48.0, ...)` — a 3-point `POLYGON` (cardboard theme).
- `spawn_prop(container, position, shape_payload, material_preset := WOOD, initial_velocity := Vector2.ZERO) -> PropBlock` — factory that instantiates a `PropBlock`, applies the payload (`shape_type` + geometry + colors), sets the material preset, sets `linear_velocity` after `add_child` (when non-zero), and returns the spawned prop. Polygon `points` are sanitized through `TerrainUtils.sanitize_points()`.
**Physics test harness controls (extended):** the `physics_test_harness.gd` scene now accepts **1 / 2 / 3** key presses to spawn props above the angled ramp (spawn point `(300, -300)`), tumbling them down the terrain:
| Key | Prop | Primitive | Preset | Initial velocity |
|---|---|---|---|---|
| **1** | Wood Crate | `create_box()` | `WOOD` | `(60, 0)` |
| **2** | Bouncy Ball | `create_ball()` | `RUBBER` | `(-80, 0)` |
| **3** | Heavy Plank | `create_plank()` | `METAL` | `(30, -40)` |
**Rig collision proxy caveat:** the standing `master_rig.tscn` figure has **no physics bodies of its own**, so a best-effort code-only `StaticBody2D` proxy (`RigCollisionProxy`) provides a static collision surface matching the figure's world bounds — a 240×1000 px `RectangleShape2D` box centered at `(0, -500)`. Props bounce/rest against it. The proxy is a stand-in for the rig's eventual physics bodies and is not part of the rig itself.
## File format (`.stk`)
Files are UTF-8 JSON, pretty-printed with tab indentation. The format is versioned and designed to remain **backward/forward compatible** — new fields can be added without breaking older files.
@@ -408,6 +488,12 @@ Behavior:
| `res://scripts/stickman_rig.gd` | **Phase 9 Task 4.** `class_name StickmanRig`, `extends Node2D`; the runtime owner of facing direction, per-joint bone bend, and `Body/*` z-order, attached to the `master_rig.tscn` root `Master`. Exports a `facing_profile` preset (`FacingProfile` LEFT/RIGHT/FORWARD, default FORWARD) and four `@export_enum("Normal","Inverted")` per-joint bend vars (`left_arm_bend`/`right_arm_bend`/`left_leg_bend`/`right_leg_bend`). Non-`@tool`: resolves `Skeleton2D`/`Body`/bend joints at runtime, enables its own modification stack, and applies the profile (flag writes + `Body/*` reorder) in `_ready()` and setters. Signals `facing_profile_changed` / `bend_flag_changed`; public API `set_facing_profile`/`get_facing_profile`, `set_joint_bend_flipped`/`get_joint_bend_flipped`, `get_bend_joints()`, `get_bend_joint_global_position()`. Null-guarded (`push_warning` + skip). **Not used by the editor.** |
| `res://scripts/test_harness.gd` | **Phase 9.** Standalone staging scene (run via **F6** on `res://scenes/test_harness.tscn`, not wired into the editor) for debugging bone scales, vector-drawing offsets, and IK limits in isolation. Top UI bar: "Open .stk…" / quick-select buttons (`stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`), "Show Bones" / "Show IK Handles" toggles, loaded-filename label. `SubViewport` world + enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on spawn); each load frees the previous rig and spawns a fresh one via `StickmanFactory.spawn()`. A world-space debug overlay draws true bone segments (joint dots + parent→child lines, with limb leaf bones drawn out to their IK targets so wrist/ankle joints are visible; the **Head** leaf is the exception — its target is a LookAt aim point, not a joint, so it draws a ~90 px segment along the bone's own direction instead) and colored IK-target markers (hands green, feet blue, head yellow, torso magenta) plus a semi-transparent yellow head-aim line; the **6** `Marker2D` IK targets are click-draggable — the 4 limb targets flex limbs live via `SkeletonModificationStack2D` TwoBoneIK (the rig self-enables its stack), the Torso target translates the whole rig via its `RemoteTransform2D`, and the Head target drives the head's LookAt aim rotation (Phase 9 Round 7). |
| `res://scenes/test_harness.tscn` | **Phase 9.** Standalone staging scene backing `scripts/test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/terrain_block.gd` | **Vector Terrain System.** `class_name TerrainBlock`, `extends StaticBody2D` — a reusable vector terrain component building `Polygon2D` (fill) + `Line2D` (border) + `CollisionPolygon2D` (`BUILD_SOLIDS`, supports concave) children in code. |
| `res://scripts/terrain_utils.gd` | **Vector Terrain System.** `class_name TerrainUtils`, `extends RefCounted` — static `sanitize_points()` (grid snap → local `_simplify_polyline()` → clockwise enforcement) and a `spawn_block()` factory. |
| `res://scripts/physics_test_harness.gd` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene root building flat/ramp/step terrain via `TerrainUtils`, instantiating `master_rig.tscn`, spawning props via **1/2/3** (`PropUtils`), and adding a rig collision proxy (run via **F6**; not wired into the editor). |
| `res://scenes/physics_test_harness.tscn` | **Vector Terrain System / Dynamic Vector Props.** Standalone staging scene backing `scripts/physics_test_harness.gd` (run via **F6**; not wired into the editor). |
| `res://scripts/prop_block.gd` | **Dynamic Vector Props.** `class_name PropBlock`, `extends RigidBody2D` — a reusable physical prop building `Polygon2D` (fill) + `Line2D` (outline) + `CollisionPolygon2D`/`CollisionShape2D` (polygon/circle collision) children in code, with material presets (mass + friction/bounce) and live-updating exports. |
| `res://scripts/prop_utils.gd` | **Dynamic Vector Props.** `class_name PropUtils`, `extends RefCounted` — static `create_box()` / `create_ball()` / `create_plank()` / `create_triangle()` primitive generators and a `spawn_prop()` factory (sanitizes polygon points via `TerrainUtils`). |
| `res://scripts/body_part_panel.gd` | Multi-shape creation, vertex editing, shape dragging, per-panel zoom & pan, grid drawing & snap-to-grid, ColorPicker, shape/vertex delete, Z-ordering (Send Back / Bring Forward), shape Copy/Paste, shape Mirror X/Y, drawing (fill + outline for closed shapes). |
| `res://scripts/whole_stickman_preview.gd` | Assembly preview, drag-to-reposition, part selection with white bounding box, rotation gizmo (circle below box) with Ctrl 15° snap, scale gizmo (corner crosses) with Ctrl aspect lock, part Z-ordering (Send Back / Bring Forward) via `part_order`, part Mirror X/Y (scale negation), zoom & pan, grid drawing & snap-to-grid, pose silhouette guide (Phase 7), part hit-bounds, labels, and (Phase 9 Round 5) `get_guide_joint_preview()` — the preview-space position of a guide joint, used by the editor to export per-part `guide_offset`. |
| `res://addons/curved_lines_2d/` | Scalable Vector Shapes 2D addon (v2.27.7) — required dependency. |