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
+64
View File
@@ -365,6 +365,63 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
persistence to disk. The "Facing" menu and all animation controls are **hidden until an .stk is
loaded** (`_set_rig_controls_visible(false)` at the end of `_build_ui()` and in
`_free_current_rig()`; shown on successful spawn in `_load_and_spawn()`).
- `scripts/terrain_block.gd` — `class_name TerrainBlock`, `extends StaticBody2D`; a **reusable
vector terrain component** (Vector Terrain System, **not used by the editor**). Builds its three
children in code: `Polygon2D` (interior fill, `fill_color`), `Line2D` (crisp vector border,
`outline_color`/`outline_width`, auto-closed loop by appending the first vertex to the end,
`LINE_JOINT_ROUND` + round caps), and `CollisionPolygon2D` (`BUILD_SOLIDS` solid decomposition —
supports concave blocks). Exported properties: `polygon_points: PackedVector2Array`,
`fill_color`, `outline_color`, `outline_width`; a unified setter pushes vertex changes to all
three children live (no manual rebuilds).
- `scripts/terrain_utils.gd` — `class_name TerrainUtils`, `extends RefCounted`; static utility
(**not used by the editor**):
- `sanitize_points(points: PackedVector2Array, grid_size: float = 16.0) -> PackedVector2Array` —
sanitization pipeline in order: grid snap → redundancy removal via a **local
`_simplify_polyline()`** (Godot 4.7 has **no `Geometry2D.simplify_polyline()`**; the local
version drops consecutive duplicates, a closing duplicate when `last == first`, and collinear
vertices) → clockwise enforcement via `Geometry2D.is_polygon_clockwise()` (reverses if false,
guaranteeing clockwise output).
- `spawn_block(...)` — factory that sanitizes raw input vectors (`sanitize_points`), creates a
`TerrainBlock`, applies the cleaned points, and adds it to the target container.
- `scripts/prop_block.gd` — `class_name PropBlock`, `extends RigidBody2D`; a **reusable
dynamic vector prop component** (Dynamic Vector Props, **not used by the editor**), `@tool`.
Builds its children in code: `Polygon2D` (interior fill, `fill_color`), `Line2D` (crisp
vector outline, `outline_color`/`outline_width`, auto-closed loop by appending the first
vertex, `LINE_JOINT_ROUND` + `LINE_CAP_ROUND`), and a collision node — `CollisionPolygon2D`
(`BUILD_SOLIDS`) in `POLYGON` mode, or `CollisionShape2D` + `CircleShape2D` (48-segment
radial loop, `CIRCLE_SEGMENTS = 48`) in `CIRCLE` mode, toggled via `shape_type`. Exported
properties: `shape_type` (`@export_enum("Polygon","Circle")`), `polygon_points:
PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_width`, and
`material_preset` (`@export_enum("None","Wood","Rubber","Cardboard","Metal")`). Presets set
`mass` + `physics_material_override` via static `mass_for()`/`physics_material()`/`tint_for()`
(Wood: mass 3.0, friction 0.6, bounce 0.1; Rubber: mass 0.5, friction 0.9, bounce 0.85;
Cardboard: mass 0.4, friction 0.3, bounce 0.05; Metal: mass 8.0, friction 0.9, bounce 0.0;
None: mass 1.0, friction 0.5, bounce 0.05) and recolor fill/outline for non-`NONE`. Unified
live-update setters push geometry/color changes to all children (null-guarded for `@tool`
editor safety); `_apply_shape()` enables exactly one collision node.
- `scripts/prop_utils.gd` — `class_name PropUtils`, `extends RefCounted`; static factory
(**not used by the editor**):
- `create_box(size := Vector2(48,48))` / `create_ball(radius := 24.0)` /
`create_plank(length := 160.0, thickness := 16.0)` / `create_triangle(base := 56.0, height
:= 48.0)` — primitive generators returning shape-payload dictionaries with default
dimensions + color themes (wood/rubber/metal/cardboard).
- `spawn_prop(container, position, shape_payload, material_preset := WOOD, initial_velocity
:= Vector2.ZERO) -> PropBlock` — instantiates a `PropBlock`, applies the payload
(`shape_type` + geometry + colors via `_apply_shape_payload()`, sanitizing polygon points
through `TerrainUtils.sanitize_points()`), sets `linear_velocity` after `add_child` (only
when non-zero), and returns the spawned prop.
- `scripts/physics_test_harness.gd` — `class_name PhysicsTestHarness`, `extends Node2D`; standalone
staging scene root (Vector Terrain System / Dynamic Vector Props, **not wired into the editor**;
run via **F6** on `res://scenes/physics_test_harness.tscn`). Builds flat ground, angled ramps, and stepped
`TerrainBlock` instances via `TerrainUtils`, instantiates `res://master_rig.tscn` standing on the
flat ground, and handles camera input. **Keys 1/2/3** spawn dynamic props above the angled ramp
via `PropUtils.spawn_prop()` (`PROP_SPAWN_POSITION = (300, -300)`): **1** Wood Crate
(`create_box()`, `WOOD`, velocity `(60,0)`), **2** Bouncy Ball (`create_ball()`, `RUBBER`,
`(-80,0)`), **3** Heavy Plank (`create_plank()`, `METAL`, `(30,-40)`). Adds a **best-effort
`StaticBody2D` collision proxy** (`RigCollisionProxy`, `_add_rig_collision_proxy()`) since the
rig has **no physics bodies of its own** — a 240×1000 px `RectangleShape2D` centered at `(0,-500)`
(`RIG_PROXY_SIZE`/`RIG_PROXY_CENTER`) matching the standing figure's world bounds, so props
bounce/rest against it; the proxy is a code-only stand-in, not part of the rig.
- Scenes:
- `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
for typed `@onready` access: `%MenuBar`, `%StickmanNameEdit`, `%LeftColumn`,
@@ -382,6 +439,13 @@ assembled in a "Whole Stickman" preview that supports translation, rotation, and
loaded-filename status label;
`SubViewport` world with an enabled `Camera2D` (middle-mouse pan, wheel zoom, recenter on
spawn); interactive limb IK via `SkeletonModificationStack2D` TwoBoneIK.
- `scenes/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
Dynamic Vector Props, not wired into the editor; run via **F6**). Backed by
`scripts/physics_test_harness.gd`. Root `Node2D` + script, `Camera2D` at position `(0, -400)`
zoom `0.5`, empty Environment container. Wheel-zoom scales between `0.25x` and `3.0x`
(resolution independence / vector outline thickness); middle-drag pans. Keys **1/2/3** spawn
dynamic props (`PropUtils`) above the angled ramp; a best-effort `StaticBody2D` rig collision
proxy provides a surface for props to bounce/rest against.
### Body-part data model
- 10 internal part keys (ordered): `head`, `torso`, `left_upper_arm`, `left_lower_arm`,