Phase 14 - Organized directories.
This commit is contained in:
@@ -4,7 +4,16 @@
|
||||
|
||||
- **Godot 4.7.1** 2D/GUI project (Forward Plus renderer)
|
||||
- Headless tests + command-line scene playback exist (see "Command-line testing & debug playback" below); the GUI editor runs via `run/main_scene`.
|
||||
- **Main scene:** `res://scenes/stickman_editor.tscn` (set as `run/main_scene` in `project.godot`)
|
||||
- **Main scene:** `res://scenes/main.tscn` (set as `run/main_scene` in `project.godot`; hosts the Stickman Editor)
|
||||
|
||||
## Repository layout
|
||||
|
||||
- `scripts/` and `scenes/` stay as separate roots with matching feature groups. Editor controllers live in `scripts/editors/{stickman,prop,stage}`; stage, stickman runtime, props, terrain, and speech systems live in `scripts/{stage,characters/stickman,props,terrain,speech}`.
|
||||
- Reusable UI, thumbnails, and `ContentPaths` live in `scripts/shared/`. Harnesses and rig-authoring scripts live in `scripts/tools/`.
|
||||
- The startup wrapper is `scenes/main.tscn`. Feature scenes are grouped under `scenes/editors/`, runtime stage scenes under `scenes/stage/`, stickman rigs under `scenes/characters/stickman/`, tools under `scenes/tools/`, and preserved old rigs under `scenes/legacy/`.
|
||||
- Project libraries live in `content/stickmen/`, `content/props/`, and `content/stages/`; fonts stay in `resources/fonts/`.
|
||||
- Maintained docs live in `docs/{guides,architecture,specs,plans}/`; older plans and project references live in `docs/archive/`. Keep `README.md` and `AGENTS.md` at the root.
|
||||
- `ContentPaths.canonicalize(path)` maps only old project paths under `stickmen/`, `props/`, and `game_scenes/` into `content/`. It preserves unknown and external paths. Keep stage serialization and persisted paths routed through it when adding path-bearing data.
|
||||
|
||||
## Command-line testing & debug playback
|
||||
|
||||
@@ -14,11 +23,12 @@
|
||||
- `<project>` = the repo root (e.g. `/Users/ryan/Projects/stickman`, or `C:\Godot4\stickman` on Windows).
|
||||
- **Run a headless test suite** (`tests/*.gd` extend `SceneTree`, print PASS/FAIL, `quit(0/1)`):
|
||||
`<godot> --headless --path <project> --script res://tests/test_cli_scene.gd`
|
||||
- Path migration compatibility: `tests/test_content_paths.gd`.
|
||||
- Prop Editor suites (same pattern): `tests/test_prop_editor_file.gd`, `tests/test_prop_editor_theme.gd`,
|
||||
`tests/test_prop_editor_canvas.gd`, `tests/test_prop_editor_thumb.gd`, `tests/test_prop_editor_round3.gd`.
|
||||
- **Launch the Sandbox Stage and auto-PLAY a `.scene`** (visual/runtime debugging — `print()` output streams to the launching terminal):
|
||||
`<godot> --path <project> res://scenes/sandbox_stage.tscn -- --play --scene <path>.scene`
|
||||
- Args after `--` are parsed by `scripts/cli_scene_launcher.gd` (`CliSceneLauncher.parse`) via `OS.get_cmdline_user_args()`.
|
||||
`<godot> --path <project> res://scenes/stage/sandbox_stage.tscn -- --play --scene <path>.scene`
|
||||
- Args after `--` are parsed by `scripts/tools/cli_scene_launcher.gd` (`CliSceneLauncher.parse`) via `OS.get_cmdline_user_args()`.
|
||||
- `--scene <path>` (also `--scene=<path>` or a single bare positional) loads the scene; `--play` enters PLAY after a successful load.
|
||||
- `--play` requires a scene — alone it shows the error dialog and stays blank/EDIT.
|
||||
- A load failure shows a "Could Not Load Scene" dialog and falls back to a blank scene.
|
||||
@@ -41,18 +51,18 @@ that form the foundation of the broader moviemaking experience.
|
||||
|
||||
The **Prop Editor** (Phase 9) is a standalone, kid-friendly vector-prop authoring tool — the
|
||||
authoring counterpart to the stickman editor — for creating, editing, and saving `.prop`
|
||||
vector prop assets. It is launched via **F6** on `res://scenes/prop_editor.tscn` and is **not**
|
||||
vector prop assets. It is launched via **F6** on `res://scenes/editors/prop/prop_editor.tscn` and is **not**
|
||||
the configured `run/main_scene`.
|
||||
|
||||
## Required addon
|
||||
|
||||
- **Scalable Vector Shapes 2D** (v2.27.7) at `addons/curved_lines_2d/`
|
||||
- Declared dependency for the project. The current editor UI does not instantiate it directly,
|
||||
but keep it present — it is required for the legacy `stick.tscn` rig.
|
||||
but keep it present — it is required for the legacy `scenes/legacy/stick.tscn` rig.
|
||||
|
||||
## Architecture
|
||||
|
||||
- `scripts/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar,
|
||||
- `scripts/editors/stickman/stickman_editor.gd` — `extends Control`; the main controller. Owns the menu bar,
|
||||
save/load/clear flow, JSON (de)serialization, and populates the 10 body-part panels.
|
||||
Writes `FILE_VERSION "1.5"`; auto-migrates `"1.0"`–`"1.4"` files on load. Coordinates cross-panel
|
||||
selection so only one shape is selected at a time (`shape_selected` → deselect others).
|
||||
@@ -98,7 +108,7 @@ the configured `run/main_scene`.
|
||||
and broadcasts. `_broadcast_settings()` pushes the value to the preview via
|
||||
`WholeStickmanPreview.set_show_guide(_show_guide)` (called in `_ready()` after
|
||||
`_load_settings()`).
|
||||
- `scripts/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`.
|
||||
- `scripts/editors/stickman/body_part_panel.gd` — `class_name BodyPartPanel`, `extends PanelContainer`.
|
||||
Reusable per-part editor. Public API:
|
||||
- `set_shape_data(data: Variant)` — import shape data (Array or single Dictionary; used on Load/Clear)
|
||||
- `get_shape_data() -> Array[Dictionary]` — export array of `{shape_type, points, color, closed, vertex_flags}`
|
||||
@@ -130,7 +140,7 @@ the configured `run/main_scene`.
|
||||
- **Selection gizmos always on top:** bounding box, rotation circle, and scale
|
||||
crosses for the selected part are drawn in a second pass after all parts,
|
||||
via `_selected_gizmo_bounds`, so they always render in front.
|
||||
- `scripts/whole_stickman_preview.gd` — `class_name WholeStickmanPreview`, `extends Control`;
|
||||
- `scripts/editors/stickman/whole_stickman_preview.gd` — `class_name WholeStickmanPreview`, `extends Control`;
|
||||
the assembly preview. Owns per-part position/rotation/scale, Z-order (`_part_order`),
|
||||
selection + gizmos, grid drawing, and pan/zoom. Public API includes `set_body_parts()`,
|
||||
`set_show_guide(enabled: bool)`, `reset_view()`, `is_cursor_over_preview(global_pos)`, and
|
||||
@@ -146,7 +156,7 @@ the configured `run/main_scene`.
|
||||
`_guide_to_preview()` = `(master_pos - GUIDE_FIGURE_CENTER) * GUIDE_SCALE +
|
||||
preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
world-space fixture that moves with pan/zoom. Joint positions are **hardcoded
|
||||
constants** derived from the `master_rig.tscn` rest pose (`GUIDE_JOINTS`: 13 anchors
|
||||
constants** derived from the `stickman_rig.tscn` rest pose (`GUIDE_JOINTS`: 13 anchors
|
||||
Head/Neck/Shoulders/Elbows/Wrists/Hips/Knees/Ankles;
|
||||
`GUIDE_SCALE = 1.0`, `GUIDE_FIGURE_CENTER = (0, -93.75)`, `GUIDE_HEAD_RADIUS = 100.0`).
|
||||
Color-coded: left limbs cyan-blue `Color(0.35, 0.70, 1.00)`, right limbs orange-red
|
||||
@@ -154,9 +164,9 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
dots at alpha `0.65`. Joint dots use `GUIDE_JOINT_RADIUS / _zoom` (6 px constant screen
|
||||
size). The guide is pure drawing — **no hit-testing** is added, so it never intercepts
|
||||
part dragging/selection.
|
||||
- `scripts/prop_editor.gd` — `class_name PropEditor`, `extends Control`; the **Prop Editor**
|
||||
- `scripts/editors/prop/prop_editor.gd` — `class_name PropEditor`, `extends Control`; the **Prop Editor**
|
||||
controller (Phase 9, **not wired into the editor**; launched via **F6** on
|
||||
`res://scenes/prop_editor.tscn`). Owns the menu bar, save/load/new flow, `.prop`
|
||||
`res://scenes/editors/prop/prop_editor.tscn`). Owns the menu bar, save/load/new flow, `.prop`
|
||||
(de)serialization, the material-preset + per-shape inspector, the mode toolbar, the status
|
||||
bar, the window title, the exit dialog, and the autosave/recovery pipeline. State:
|
||||
`_theme: PropTheme`, `_material_preset: int` (`PropBlock.MaterialPreset`), `_current_path`,
|
||||
@@ -249,7 +259,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
Ctrl snaps rotation to 15°, editable anchors follow, and Esc cancels the preview. Reshape
|
||||
and Attach hide Transform Whole Prop and Normalize Size; Center Prop and Show Stickman Guide
|
||||
remain available.
|
||||
- `scripts/prop_canvas.gd` — `class_name PropCanvas`, `extends Control`; the Prop Editor's
|
||||
- `scripts/editors/prop/prop_canvas.gd` — `class_name PropCanvas`, `extends Control`; the Prop Editor's
|
||||
**multi-shape vector canvas** (Phase 9, **not used by the editor**), mirroring
|
||||
`BodyPartPanel`'s proven input model (cursor-centered zoom, pinch/pan gestures, middle-mouse
|
||||
pan, vertex drag, right-click context menu, z-order) while adding prop-specific needs
|
||||
@@ -329,9 +339,9 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
and corner gestures use snapshot-only previews and commit one atomic document edit on
|
||||
release; `set_whole_prop_transform_enabled()` and related transform names are the preferred
|
||||
API. Resize-named compatibility aliases remain internally for existing controller paths.
|
||||
- `scripts/prop_file.gd` — `class_name PropFile`, `extends RefCounted`; the static `.prop`
|
||||
- `scripts/editors/prop/prop_file.gd` — `class_name PropFile`, `extends RefCounted`; the static `.prop`
|
||||
schema + converters (Phase 9, **not used by the editor**). Consts `FILE_VERSION := "1.0"`,
|
||||
`FILE_EXT := ".prop"`, `DEFAULT_DIR := "res://props"`, `SUPPORTED_VERSIONS: ["1.0"]`,
|
||||
`FILE_EXT := ".prop"`, `DEFAULT_DIR := "res://content/props"`, `SUPPORTED_VERSIONS: ["1.0"]`,
|
||||
`PRESET_STRINGS` (index == `PropBlock.MaterialPreset`: none/wood/rubber/cardboard/metal),
|
||||
`SHAPE_STRINGS` (polygon/circle/line), `DEFAULT_RADIUS`, `DEFAULT_CIRCLE_SEGMENTS`.
|
||||
- **Schema (v1.0):** top-level `version`, `prop_name`, `material_preset`, `shapes[]`, and
|
||||
@@ -362,7 +372,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
element becomes `Vector2.ZERO`). Round 1–4 files saved this way are unrecoverable. Any typed
|
||||
`Packed*Array` assignment from dictionary/JSON data must go through a Variant-coercion
|
||||
helper, and regression tests must assert point **values**, not just counts.
|
||||
- `scripts/prop_theme.gd` — `class_name PropTheme`, `extends RefCounted`; the **validated theme
|
||||
- `scripts/editors/prop/prop_theme.gd` — `class_name PropTheme`, `extends RefCounted`; the **validated theme
|
||||
loader** for `res://prop_theme.json` (Phase 9, **not used by the editor**), modeled on
|
||||
`SandboxTheme`. Consts `SCHEMA_VERSION := "1.0"`, `DEFAULT_PATH := "res://prop_theme.json"`,
|
||||
and `DEFAULT_CONFIG` (mirrors the committed file). `load_from(path)` deep-merges the file over
|
||||
@@ -383,14 +393,14 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
`button_min_height`, `row_min_height`, `mode_button_min_height`, `text_color`,
|
||||
`text_muted_color`, `border_color`, `accent_color`. All are clamped with `DEFAULT_CONFIG`
|
||||
fallbacks and applied through a **local** `Theme` (no global GUI mutation).
|
||||
- `scripts/thumbnails/thumbnail_cache.gd` — see its existing section; the Phase 9 extension
|
||||
- `scripts/shared/thumbnails/thumbnail_cache.gd` — see its existing section; the Phase 9 extension
|
||||
adds `prop_file_key(path)` → `"<basename>_<mtime>"` and `prop_file_png(path)` →
|
||||
`PROP_DIR + "/" + key + ".png"` for **custom `.prop`** files (mtime-keyed so a re-edited prop
|
||||
invalidates its cached PNG). Built-in template thumbnails keep `prop_png(id)` →
|
||||
`"<id>_v<PROP_VERSION>.png"`; `stickman_key`/`stickman_png` are unchanged.
|
||||
- `scripts/stk_rig_adapter.gd` — `class_name StkRigAdapter`, `extends RefCounted`; a **standalone
|
||||
- `scripts/characters/stickman/stk_rig_adapter.gd` — `class_name StkRigAdapter`, `extends RefCounted`; a **standalone
|
||||
runtime adapter** (Phase 8, **not referenced by the editor**, extended by Phase 9).
|
||||
`static func apply(stk_data, rig)` fits an instantiated `master_rig.tscn` to a loaded `.stk`
|
||||
`static func apply(stk_data, rig)` fits an instantiated `stickman_rig.tscn` to a loaded `.stk`
|
||||
dictionary, calling three private helpers in order: `_fit_bones` (re-fits the 8 limb `Bone2D`
|
||||
lengths + lower-bone origins, and zeroes the Head driver's local position so the chin sits on
|
||||
the neck joint), `_recalibrate_ik` (repositions the `IK_Targets/Left|Right_Hand` and
|
||||
@@ -401,7 +411,7 @@ preview_area.size * 0.5`, so it also re-centers on window resize; it remains a
|
||||
- **Phase 2 (Sandbox) single-shape support:** `_mount_shapes()` reads a part's `shapes` array
|
||||
when present (v1.2+), and otherwise — when the part dict itself carries `points` — wraps the
|
||||
whole dict as a single shape (`shapes = [pd]`), so v1.0/v1.1 single-shape `.stk` files (e.g.
|
||||
`stickmen/test.stk`) mount as visible geometry instead of being cleared to nothing.
|
||||
`content/stickmen/test.stk`) mount as visible geometry instead of being cleared to nothing.
|
||||
- **Phase 9 extension:** also fits the head bone (`Skeleton2D/Torso/Head.position.y =
|
||||
-proportions.torso_length`, x preserved) and mounts the head as **full geometry** like every
|
||||
other part — it clears the `Body/Head` node's inline `@tool` circle script via
|
||||
@@ -484,11 +494,11 @@ f_prime`). This replaces the per-side family choice **and** the 180° flip heuri
|
||||
the key keep the previous family rules + flip heuristic exactly as before. `theta`, `s`, the
|
||||
Round 5 offset `t`, and the `HEAD_CHIN_DROP` fallback are unchanged — they consume
|
||||
`anchor`/`v` generically.
|
||||
Targets `master_rig.tscn` node paths; every node lookup is null-guarded (missing node →
|
||||
Targets `stickman_rig.tscn` node paths; every node lookup is null-guarded (missing node →
|
||||
`push_warning` + skip, never crash). Consumed by a future runtime pipeline.
|
||||
- `scripts/stickman_rig.gd` — `class_name StickmanRig`, `extends Node2D`; the **runtime owner of
|
||||
facing direction, per-joint bone bend, and `Body/*` z-order** for `master_rig.tscn` (Phase 9
|
||||
Task 4). Attached to the `Master` root node of `master_rig.tscn`. **Non-`@tool`** — node
|
||||
- `scripts/characters/stickman/stickman_rig.gd` — `class_name StickmanRig`, `extends Node2D`; the **runtime owner of
|
||||
facing direction, per-joint bone bend, and `Body/*` z-order** for `stickman_rig.tscn` (Phase 9
|
||||
Task 4). Attached to the `Master` root node of `stickman_rig.tscn`. **Non-`@tool`** — node
|
||||
resolution, flag writes, and z-order reordering run only at runtime (`_ready` + setters on a
|
||||
live instance). Enums `FacingProfile { LEFT, RIGHT, FORWARD }` (values are the harness facing-menu
|
||||
ids), `BendDirection { NORMAL, INVERTED }`, and `RigState { ANIMATED, RAGDOLL, RECOVERING }`.
|
||||
@@ -533,7 +543,7 @@ f_prime`). This replaces the per-side family choice **and** the 180° flip heuri
|
||||
mounted `Body/*` geometry, while z-order (depth) is unaffected by an X-mirror. `walk_to()`
|
||||
always plays the canonical `walk_right` clip (X-mirrored by the root for LEFT; `walk_left` is
|
||||
no longer used at runtime) and sets the facing profile explicitly. Two **head-related fixes**
|
||||
under the mirror: the master_rig.tscn head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`)
|
||||
under the mirror: the stickman_rig.tscn head `RemoteTransform2D` (`Skeleton2D/Torso/Head/Pivot`)
|
||||
no longer sets `update_scale = false` — it pushes the **full transform** like every other `Body`
|
||||
driver, so `Body/Head.scale` stays identity under the mirrored root (the old partial-channel
|
||||
push re-canonicalized the scale and caused per-frame Y-flips/wrap-jumps). And
|
||||
@@ -576,7 +586,7 @@ RAGDOLL` + emits. `_build_ragdoll()` creates a `Node2D`
|
||||
the table. Shoulder/hip ±160°, neck free. Cached
|
||||
momentum is applied to the torso body. `apply_ragdoll_velocity_boost(velocity)` applies the
|
||||
same velocity delta (mass-scaled `apply_central_impulse`) to every ragdoll body — used by the
|
||||
harness "Knock Up" button. All ragdoll nodes are spawned procedurally — `master_rig.tscn` is
|
||||
harness "Knock Up" button. All ragdoll nodes are spawned procedurally — `stickman_rig.tscn` is
|
||||
**not** modified.
|
||||
- **Phase 11 instant handoff + recovery:** `_update_rest_detection()` (only when `state ==
|
||||
RAGDOLL`)
|
||||
@@ -728,30 +738,30 @@ target, speed = -1.0)` (feet/ground destination; no-op unless `state == ANIMATED
|
||||
private runtime lane without mutating `action_queue`: Overlay speech runs concurrently in one
|
||||
replaceable slot, Sequential actions cancel the current primary action while retaining authored
|
||||
future work, and Takeover actions cancel both primary and overlay work before taking control.
|
||||
- `scripts/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
|
||||
- `scripts/characters/stickman/stickman_factory.gd` — `class_name StickmanFactory`, `extends RefCounted`; a **static
|
||||
factory** and the **runtime entry point** (Phase 9, **not used by the editor**) that turns a
|
||||
`.stk` file into a live, rigged `master_rig.tscn` instance:
|
||||
`.stk` file into a live, rigged `stickman_rig.tscn` instance:
|
||||
- `static func load_stk(path: String) -> Dictionary` — reads a `.stk` file (`FileAccess` +
|
||||
`JSON.parse_string`); returns `{}` + `push_warning` on failure.
|
||||
- `static func spawn_from_data(stk_data: Dictionary) -> StickmanRig` — instantiates
|
||||
`res://master_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root
|
||||
`res://scenes/characters/stickman/stickman_rig.tscn`, calls `StkRigAdapter.apply(stk_data, rig)`, returns the rig root
|
||||
(typed as `StickmanRig` since the rig now carries the `StickmanRig` root script).
|
||||
- `static func spawn(path: String) -> StickmanRig` — `load_stk()` then `spawn_from_data()`;
|
||||
returns `null` on empty data.
|
||||
- `scripts/create_animations.gd` — `@tool extends EditorScript`; a **standalone editor utility**
|
||||
(run manually with `master_rig.tscn` open, **not** auto-loaded or referenced at runtime).
|
||||
- `scripts/tools/create_animations.gd` — `@tool extends EditorScript`; a **standalone editor utility**
|
||||
(run manually with `stickman_rig.tscn` open, **not** auto-loaded or referenced at runtime).
|
||||
Supersedes the deleted `scripts/create_walk.gd`. `_run()` bakes `walk_left`/`walk_right`
|
||||
(same keyframes as the old script, via `_generate_walk_animation()`) and a one-shot `stand_up`
|
||||
(via `_generate_pose_animation()`, `STAND_UP_DURATION` = 2.0, `loop_mode = LOOP_NONE`) into the
|
||||
open scene's default `AnimationLibrary`. `stand_up` keys the 6 `IK_Targets/*:position` tracks
|
||||
plus a `IK_Targets/Torso:rotation` track from `POSE_DOWN` (a generic "lying on back" pose) to
|
||||
`POSE_STANDING` (matching `master_rig.tscn` defaults), with `POSE_PATHS`/`POSE_MARKERS` consts.
|
||||
`POSE_STANDING` (matching `stickman_rig.tscn` defaults), with `POSE_PATHS`/`POSE_MARKERS` consts.
|
||||
The baked `stand_up` is an **authored reference only** — runtime recovery does not play it
|
||||
(StickmanRig tweens the IK targets directly from the captured ragdoll pose).
|
||||
- `scripts/test_harness.gd` — **standalone staging scene** (Phase 9, **not wired into the editor**;
|
||||
run via **F6** on `res://scenes/test_harness.tscn`) for debugging bone scales, vector drawing
|
||||
- `scripts/tools/test_harness.gd` — **standalone staging scene** (Phase 9, **not wired into the editor**;
|
||||
run via **F6** on `res://scenes/tools/test_harness.tscn`) for debugging bone scales, vector drawing
|
||||
offsets, and IK limits in isolation. Top UI bar: "Open .stk…" button → `FileDialog` (`*.stk`);
|
||||
quick-select buttons for `stickmen/break.stk`, `stickmen/basic.stk`, `stickmen/test.stk`; "Show
|
||||
quick-select buttons for `content/stickmen/break.stk`, `content/stickmen/basic.stk`, `content/stickmen/test.stk`; "Show
|
||||
Bones" / "Show IK Handles" checkboxes; a status label showing the loaded filename. Viewport:
|
||||
`SubViewportContainer` → `SubViewport` → world `Node2D` + enabled `Camera2D`; middle-mouse pan,
|
||||
mouse-wheel zoom, camera recenters on each spawn. Each load frees the previous rig and spawns a
|
||||
@@ -869,7 +879,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
|
||||
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
|
||||
- `scripts/terrain/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,
|
||||
@@ -877,7 +887,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
|
||||
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
|
||||
- `scripts/terrain/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
|
||||
@@ -887,7 +897,7 @@ MOUSE_FILTER_IGNORE` on the panel (the label itself stays interactive for select
|
||||
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
|
||||
- `scripts/props/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
|
||||
@@ -914,12 +924,12 @@ PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_wi
|
||||
collision geometry, including the custom one-hull path and nonuniform circle fallback;
|
||||
returning to EDIT restores the authored root scale. Preset mass and material physics stay
|
||||
unchanged.
|
||||
- `scripts/trigger_area.gd` — `class_name TriggerArea`, `extends Node2D`; a **placeable sensor**
|
||||
- `scripts/stage/trigger_area.gd` — `class_name TriggerArea`, `extends Node2D`; a **placeable sensor**
|
||||
(Phase 4, **not used by the editor**). `@export size: Vector2` (default 96×96), `get_area_rect()
|
||||
-> Rect2` (centered local-space rectangle), and `_draw()` (translucent green fill +
|
||||
dashed border). **No physics and no signals** — it is a pure geometric region evaluated by
|
||||
`sandbox_stage.gd`'s event engine (`_update_area_entry`) for `entered_area` rules.
|
||||
- `scripts/prop_utils.gd` — `class_name PropUtils`, `extends RefCounted`; static factory
|
||||
- `scripts/props/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
|
||||
@@ -930,10 +940,10 @@ PackedVector2Array`, `radius: float`, `fill_color`, `outline_color`, `outline_wi
|
||||
(`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
|
||||
- `scripts/tools/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
|
||||
run via **F6** on `res://scenes/tools/physics_test_harness.tscn`). Builds flat ground, angled ramps, and stepped
|
||||
`TerrainBlock` instances via `TerrainUtils`, instantiates `res://scenes/characters/stickman/stickman_rig.tscn` standing on the
|
||||
flat ground, and handles camera input. A top-bar UI (`_build_ui()`, a `CanvasLayer` +
|
||||
`PanelContainer` matching `test_harness`'s style) replaces the old key bindings: **Spawn
|
||||
Crate** / **Spawn Ball** / **Spawn Plank** buttons spawn dynamic props above the angled ramp
|
||||
@@ -966,9 +976,9 @@ KNOCK_UP_VELOCITY = (0, -450))` (mass-scaled `apply_central_impulse` on every ra
|
||||
always `_update_ragdoll_toggle()` (proxy helpers are idempotent, so the toggle handler's own
|
||||
add/remove is harmless). The toggle label reads "Stickman" during `RECOVERING` (since
|
||||
` is_in_ragdoll()` is false).
|
||||
- `scripts/sandbox_stage.gd` — `class_name SandboxStage`, `extends Node2D`; the **Sandbox Stage
|
||||
- `scripts/editors/stage/sandbox_stage.gd` — `class_name SandboxStage`, `extends Node2D`; the **Sandbox Stage
|
||||
Builder** root controller (Phase 2, **not wired into the editor**; run via **F6** on
|
||||
`res://scenes/sandbox_stage.tscn`). Owns the EDIT/DIRECT/PLAY mode state machine, placement mode,
|
||||
`res://scenes/stage/sandbox_stage.tscn`). Owns the EDIT/DIRECT/PLAY mode state machine, placement mode,
|
||||
camera pan/zoom, deletion, status bar, and signal fan-out; instantiates `StageSpawner` /
|
||||
`StageSelection` / `StageGizmos` (via `preload` consts). `enum StageMode { EDIT, DIRECT, PLAY }`
|
||||
(default EDIT). EDIT freezes `RigidBody2D` props with `freeze = true` +
|
||||
@@ -1085,7 +1095,7 @@ Array[Dictionary]` of `{id, trigger, actions}` rules (trigger types: `arrived_at
|
||||
are off by default.
|
||||
- **Phase 3b asset-library selector integration:** the **Stickman** and **Prop** palette buttons no
|
||||
longer place directly — pressing them opens a modal **selector grid** (`_selector: AssetSelector`,
|
||||
an instantiated `scenes/asset_selector.tscn` `PopupPanel`, added to the UI `CanvasLayer` in
|
||||
an instantiated `scenes/stage/asset_selector.tscn` `PopupPanel`, added to the UI `CanvasLayer` in
|
||||
`_build_ui()` with theme/font overrides applied via `AssetSelector.apply_font`), backed by a
|
||||
dim backdrop `_selector_dim` (a black `ColorRect` at `SELECTOR_DIM_ALPHA` = 0.5, `mouse_filter =
|
||||
MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only while the selector
|
||||
@@ -1115,7 +1125,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
early-return while `_selector_open` (belt-and-suspenders over the modal popup). Selection is
|
||||
**session-only** — persists across EDIT/DIRECT/PLAY toggles, resets on scene reload, **no disk
|
||||
save** (`_save_settings` is untouched).
|
||||
- `scripts/stickman_speech_bubble.gd` — `class_name SpeechBubble`, `extends Node2D`; a **world-space
|
||||
- `scripts/speech/stickman_speech_bubble.gd` — `class_name SpeechBubble`, `extends Node2D`; a **world-space
|
||||
speech bubble** drawn in `_draw()` (Phase 3a, **not used by the editor**). Child of a `StickmanRig`
|
||||
at `SPEECH_BUBBLE_OFFSET` (above the head), so it follows the figure and scales with the camera.
|
||||
Consts `FONT_SIZE`/`PADDING`/`TAIL_HEIGHT`/`MAX_WIDTH`/`BG_COLOR`/`BORDER_COLOR`/`TEXT_COLOR`.
|
||||
@@ -1123,7 +1133,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
a downward tail triangle centered on the rig-local origin, then `draw_string(...)`; `visible = false`
|
||||
by default, no hit-testing. Public API: `show_text(text)` (store, `visible = true`, `queue_redraw()`),
|
||||
`hide_bubble()`. Driven by `StickmanRig.speak()`; the rig owns hiding + the `speech_finished` signal.
|
||||
- `scripts/stage_director_visuals.gd` — `class_name StageDirectorVisuals`, `extends Node2D`; the
|
||||
- `scripts/stage/stage_director_visuals.gd` — `class_name StageDirectorVisuals`, `extends Node2D`; the
|
||||
**Edit-mode director overlay** (Phase 3a, **not used by the editor**), mirroring `StageGrid`/
|
||||
`StageGizmos`. State `camera`/`world`/`enabled`/`_dirty`; public `set_enabled(value)` /
|
||||
`mark_dirty()`. `_process()` redraws on a dirty flag; `_draw()` reads each rig's queue via
|
||||
@@ -1141,7 +1151,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
badge**, an orange **→ action badge**, a dark label with `rule_summary()` text, and a **✕ delete
|
||||
icon** (the label + icon are hit-testable via `hit_test_rule()`, `Vector2.INF` sentinel from
|
||||
`hit_test_waypoint()`); click-label → consequence-only edit, click-✕ → delete.
|
||||
- `scripts/stage_spawner.gd` — `class_name StageSpawner`, `extends RefCounted`; registry-driven
|
||||
- `scripts/stage/stage_spawner.gd` — `class_name StageSpawner`, `extends RefCounted`; registry-driven
|
||||
spawner (Phase 2). A `_registry: Array[Dictionary]` maps ids to terrain/prop/stickman templates;
|
||||
adding a type = appending an entry (no hard-coded id `match`). Reuses `TerrainUtils.spawn_block`,
|
||||
`PropUtils.spawn_prop`, `StickmanFactory.spawn_from_data` via `preload` consts. Terrain entries
|
||||
@@ -1157,7 +1167,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
`ground/ramp/step` (terrain) + `prop` + `stickman` + `area` — the separate `crate`/`ball`
|
||||
entries were **removed** and a single `"prop"` entry (label "Prop", kind `"prop"`) added, so
|
||||
`get_spawnable_ids()` == `["ground","ramp","step","prop","stickman","area"]`. Session state:
|
||||
`selected_stickman_path` (default `DEFAULT_STICKMAN_PATH` = `res://stickmen/test.stk`) and
|
||||
`selected_stickman_path` (default `DEFAULT_STICKMAN_PATH` = `res://content/stickmen/test.stk`) and
|
||||
`selected_prop_id` (default `"crate"`), read/written by the sandbox selector (Phase 3b) — they
|
||||
are **session-only** (no disk save). `_stickman_cache: Dictionary` (path → parsed data) seeds
|
||||
the default path in `_init` and lazily loads+parses any newly selected path on first spawn.
|
||||
@@ -1165,9 +1175,9 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
so feet land at the cursor); `_spawn_prop` looks the **selected** prop id up via
|
||||
`PropLibrary.get_entry(id)` and spawns its payload (`PropUtils.spawn_prop`) + material preset.
|
||||
New getters `get_selected_stickman_path()` / `get_selected_prop_id()` (status + tests).
|
||||
- `scripts/stickman_library.gd` — `class_name StickmanLibrary`, `extends RefCounted` (Phase 3b,
|
||||
**not used by the editor**). Scans `res://stickmen/*.stk` into entry models for the asset
|
||||
selector. `const STICKMEN_DIR := "res://stickmen"`; `var entries: Array[Dictionary]`. Public API:
|
||||
- `scripts/characters/stickman/stickman_library.gd` — `class_name StickmanLibrary`, `extends RefCounted` (Phase 3b,
|
||||
**not used by the editor**). Scans `res://content/stickmen/*.stk` into entry models for the asset
|
||||
selector. `const STICKMEN_DIR := "res://content/stickmen"`; `var entries: Array[Dictionary]`. Public API:
|
||||
`scan(dir_path: String = STICKMEN_DIR) -> Array[Dictionary]` (via `DirAccess` + per-file
|
||||
`StickmanFactory.load_stk`; a corrupt or missing-`body_parts` file is **skipped** with a
|
||||
`push_warning` — never `{}`-as-entry; the display `name` = `stickman_name` (stripped) if
|
||||
@@ -1177,7 +1187,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
(ad-hoc single-file entry for **Browse**: `load_stk` + name fallback; `{}` on load failure or
|
||||
missing `body_parts`). Entry shape `{ "path", "name", "data" }`; thumbnails are **not** embedded
|
||||
in entries — resolved via `ThumbnailCache` at display time by the selector.
|
||||
- `scripts/prop_library.gd` — `class_name PropLibrary`, `extends RefCounted` (Phase 3b, **not used
|
||||
- `scripts/props/prop_library.gd` — `class_name PropLibrary`, `extends RefCounted` (Phase 3b, **not used
|
||||
by the editor**). A **static** registry of the 4 built-in prop templates for the asset selector.
|
||||
`static var _templates: Array[Dictionary]` (lazily built on first access — `PropUtils.create_*()`
|
||||
are `static func` and cannot run in a `const`). Public API: `static get_entries()` (builds once,
|
||||
@@ -1186,7 +1196,7 @@ MOUSE_FILTER_IGNORE`, on the UI `CanvasLayer` behind the selector, shown only wh
|
||||
material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOOD`), Ball/Rubber
|
||||
(`create_ball()` + `RUBBER`), Plank/Metal (`create_plank()` + `METAL`), Triangle/Cardboard
|
||||
(`create_triangle()` + `CARDBOARD`) — generator color themes already match the presets.
|
||||
- `scripts/thumbnails/thumbnail_cache.gd` — `class_name ThumbnailCache`, `extends RefCounted`
|
||||
- `scripts/shared/thumbnails/thumbnail_cache.gd` — `class_name ThumbnailCache`, `extends RefCounted`
|
||||
(Phase 3b, **not used by the editor**). Disk PNG cache under `user://thumbnails/`. Consts
|
||||
`STICKMEN_DIR := "user://thumbnails/stickmen"`, `PROP_DIR := "user://thumbnails/props"`,
|
||||
`PROP_VERSION := 1`. Public API: `stickman_key(path)` → `"<basename>_<FileAccess.get_modified_time(path)>"`
|
||||
@@ -1195,7 +1205,7 @@ material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOO
|
||||
thumbnails); `load_png(png_path) -> Texture2D` (`Image.load_from_file` + `ImageTexture`, `null`
|
||||
if missing/unloadable); `save_png(tex, png_path) -> Error` (creates the parent dir); `ensure_dir`;
|
||||
`clean_stale_stickmen(valid_keys)` (deletes `basename_*` PNGs not in the valid key set).
|
||||
- `scripts/thumbnails/stickman_thumbnail.gd` — `class_name StickmanThumbnail`, `extends Node`
|
||||
- `scripts/shared/thumbnails/stickman_thumbnail.gd` — `class_name StickmanThumbnail`, `extends Node`
|
||||
(Phase 3b, **not used by the editor**). Renders a parsed `.stk` into a `Texture2D` for the asset
|
||||
selector by spawning the **real rig**. `const SIZE := Vector2i(200, 200)`. `_ready()` builds one
|
||||
persistent offscreen `SubViewport` (`transparent_bg`, `render_target_update_mode = UPDATE_ALWAYS`)
|
||||
@@ -1207,7 +1217,7 @@ material_preset, material_label, payload}` — Crate/Wood (`create_box()` + `WOO
|
||||
texture, frees the rig, and returns an `ImageTexture` — or `null` (a placeholder) when the capture
|
||||
is blank/empty (the **headless degrade**; pixel rendering is manual/F6 verification only). Output is
|
||||
identical to the rig actually placed on stage, not a re-implementation of the editor preview.
|
||||
- `scripts/thumbnails/prop_thumbnail.gd` — `class_name PropThumbnail`, `extends Node` (Phase 3b,
|
||||
- `scripts/shared/thumbnails/prop_thumbnail.gd` — `class_name PropThumbnail`, `extends Node` (Phase 3b,
|
||||
**not used by the editor**). Renders a prop template into a `Texture2D`. `const SIZE :=
|
||||
Vector2i(200, 200)`. `_ready()` builds the same offscreen `SubViewport` + `Camera2D` pattern as
|
||||
`StickmanThumbnail`. `render(payload, material_preset) -> Texture2D` mirrors `PropBlock`'s
|
||||
@@ -1216,8 +1226,8 @@ Vector2i(200, 200)`. `_ready()` builds the same offscreen `SubViewport` + `Camer
|
||||
circle payloads are re-expanded via `PropBlock.CIRCLE_SEGMENTS`; non-`NONE` presets tint fill via
|
||||
`PropBlock.tint_for` and darken the outline; then frames + double-`frame_post_draw` captures and
|
||||
returns an `ImageTexture` or `null` (blank → placeholder).
|
||||
- `scripts/asset_selector.gd` — `class_name AssetSelector`, `extends PopupPanel` (Phase 3b, **not
|
||||
used by the editor**); root script of `scenes/asset_selector.tscn`. A **grid popup controller**.
|
||||
- `scripts/shared/ui/asset_selector.gd` — `class_name AssetSelector`, `extends PopupPanel` (Phase 3b, **not
|
||||
used by the editor**); root script of `scenes/stage/asset_selector.tscn`. A **grid popup controller**.
|
||||
Signals `item_selected(entry)`, `cancelled()`, `browse_requested()`, `refresh_requested()`.
|
||||
`const COLUMNS := 4`, `ROWS := 3`, `PAGE_SIZE := 12`, `CELL_MIN_SIZE`, `THUMB_SIZE`. `var kind`
|
||||
(`"stickman"` | `"prop"`). Public API: `open(kind, entries)` (sets
|
||||
@@ -1239,7 +1249,7 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
|
||||
`%EmptyLabel`/`%PageLabel`/`%PrevButton`/`%NextButton`/`%BrowseButton`/`%RefreshButton`/
|
||||
`%CloseButton`); all dynamic per-cell content is built in code at runtime because the entry set is
|
||||
dynamic.
|
||||
- `scripts/stage_selection.gd` — `class_name StageSelection`, `extends RefCounted`; hover/click/
|
||||
- `scripts/stage/stage_selection.gd` — `class_name StageSelection`, `extends RefCounted`; hover/click/
|
||||
box selection via geometric world-space AABB hit-testing (Phase 2). `static get_world_aabb`
|
||||
unions a `Polygon2D` child's world points (terrain/props) or, for a stickman rig, recursively
|
||||
unions its mounted `Body/*` `Line2D`/`Polygon2D` geometry (`_collect_visual_points()`), so the
|
||||
@@ -1250,7 +1260,7 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
|
||||
`clear_selection`/`select_only`/`add_to_selection`/`toggle_selection`/`is_selected`/`hit_test`/
|
||||
`update_hover`/`box_select`. (Phase 4) `get_world_aabb` also handles `TriggerArea` objects via
|
||||
the same **duck-typed** `get_area_rect` AABB branch, so trigger areas are selectable/hit-testable.
|
||||
- `scripts/stage_gizmos.gd` — `class_name StageGizmos`, `extends Node2D`; hover highlight +
|
||||
- `scripts/stage/stage_gizmos.gd` — `class_name StageGizmos`, `extends Node2D`; hover highlight +
|
||||
selection outlines + a rotate ring (Phase 2). **No translate handle** — objects are dragged
|
||||
directly by the root; `set_targets(nodes: Array[Node2D])` holds the current multi-selection,
|
||||
and `begin_translate_drag(node, world_pos)` starts a TRANSLATE drag that moves **all** selected
|
||||
@@ -1271,46 +1281,46 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
|
||||
existing transform signals. Other selections keep the existing gizmos.
|
||||
`transform_committed(nodes: Array[Node2D])` emitted on drag end. `set_enabled(false)` hides +
|
||||
disables in PLAY. Also draws the box-select marquee via `set_box_rect(rect)`.
|
||||
- `scripts/stage_grid.gd` — `class_name StageGrid`, `extends Node2D`; the optional world-space
|
||||
- `scripts/stage/stage_grid.gd` — `class_name StageGrid`, `extends Node2D`; the optional world-space
|
||||
grid overlay (Phase 2). Pure `_draw()`: grid lines pan/zoom with the camera (`camera.get_screen_center_position()`
|
||||
- viewport extent / zoom), with a heavier **major line every 5 cells**; `grid_size` / `enabled`
|
||||
are set by `SandboxStage`. Added as the first child of the stage root so it renders behind
|
||||
`World`; no hit-testing.
|
||||
- Scenes:
|
||||
- `scenes/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
|
||||
- `scenes/editors/stickman/stickman_editor.tscn` — main editor layout; unique-name nodes (`%Prefix`) used
|
||||
for typed `@onready` access: `%MenuBar`, `%StickmanNameEdit`, `%LeftColumn`,
|
||||
`%CenterColumn`, `%WholeStickmanPreview`, `%SaveDialog`, `%LoadDialog`,
|
||||
`%ClearConfirmDialog`, `%ErrorDialog`, `%StatusBar`, `%CursorCoords`, `%SnapStatus`.
|
||||
- **Phase 6 status bar:** `StatusBar` is an `HBoxContainer` bottom-anchored at 28 px
|
||||
height containing `CursorCoords` (left) and `SnapStatus` (right); `MainLayout`
|
||||
`offset_bottom` is `-28.0` to leave room for it.
|
||||
- `scenes/body_part_panel.tscn` — instantiated 10× at runtime (5 per column). Each panel
|
||||
- `scenes/editors/stickman/body_part_panel.tscn` — instantiated 10× at runtime (5 per column). Each panel
|
||||
sets `size_flags_vertical = SIZE_EXPAND_FILL` so the panels expand to fill the column
|
||||
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 /
|
||||
- `scenes/tools/test_harness.tscn` — **standalone staging scene** (Phase 9, not wired into the
|
||||
editor; run via **F6**). Backed by `scripts/tools/test_harness.gd`. Top UI bar with file open /
|
||||
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.
|
||||
- `scenes/physics_test_harness.tscn` — **standalone staging scene** (Vector Terrain System /
|
||||
- `scenes/tools/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)`
|
||||
`scripts/tools/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. Top-bar buttons
|
||||
**Spawn Crate / Spawn Ball / Spawn Plank** spawn dynamic props (`PropUtils`) above the
|
||||
angled ramp; a toggle button flips the rig **Stickman** ↔ **Ragdoll** (removing/restoring
|
||||
the best-effort `StaticBody2D` rig collision proxy); a **Knock Up** button impulses the
|
||||
ragdoll and all props upward.
|
||||
- `scenes/sandbox_stage.tscn` — **standalone staging scene** (Sandbox Stage Builder, Phase 2,
|
||||
not wired into the editor; run via **F6**). Backed by `scripts/sandbox_stage.gd`. Minimal
|
||||
- `scenes/stage/sandbox_stage.tscn` — **standalone staging scene** (Sandbox Stage Builder, Phase 2,
|
||||
not wired into the editor; run via **F6**). Backed by `scripts/editors/stage/sandbox_stage.gd`. Minimal
|
||||
root `Node2D` + script, `Camera2D` at position `(0, -400)` zoom `(0.5, 0.5)`, and an empty
|
||||
`World` (Node2D) container; the `GridLayer` (`StageGrid`), `GizmoLayer` (`StageGizmos`),
|
||||
`PlacementGhost` holder, and the CanvasLayer top-bar UI (mode toggle + six palette buttons +
|
||||
Grid/Snap/Size controls + status label) are built in code at `_ready()`. Phase 3b instantiates
|
||||
the `AssetSelector` grid popup (see below) into the UI `CanvasLayer`.
|
||||
- `scenes/asset_selector.tscn` — **standalone asset-selector grid popup** (Phase 3b, not wired
|
||||
into the editor). Rooted at `AssetSelector` (`PopupPanel`, `scripts/asset_selector.gd`); a
|
||||
- `scenes/stage/asset_selector.tscn` — **standalone asset-selector grid popup** (Phase 3b, not wired
|
||||
into the editor). Rooted at `AssetSelector` (`PopupPanel`, `scripts/shared/ui/asset_selector.gd`); a
|
||||
**minimal shell/layout skeleton** — authored title bar, empty `GridContainer`, empty-state
|
||||
`Label`, and a footer (`Prev` / page label / `Next` / `Browse…` / `Refresh` / `Close`), all as
|
||||
unique-name nodes (`%TitleLabel`, `%GridContainer`, `%EmptyLabel`, `%PageLabel`, `%PrevButton`,
|
||||
@@ -1318,8 +1328,8 @@ page_count}` helper. `_build_cell(entry)` returns a `Button` with a `TextureRect
|
||||
(thumbnail + name + prop material badge) is built in code at runtime because the entry set is
|
||||
dynamic; instantiated by `sandbox_stage.gd` (`_build_ui`) for the **Stickman** / **Prop**
|
||||
palette-button selector grids.
|
||||
- `scenes/prop_editor.tscn` — **standalone Prop Editor scene** (Phase 9, not wired into the
|
||||
editor; launched via **F6**). Backed by `scripts/prop_editor.gd`, minimal shell with
|
||||
- `scenes/editors/prop/prop_editor.tscn` — **standalone Prop Editor scene** (Phase 9, not wired into the
|
||||
editor; launched via **F6**). Backed by `scripts/editors/prop/prop_editor.gd`, minimal shell with
|
||||
`%MenuBar` inside `%TopRow` (beside `%TopRightControls` — the grid spinbox + snap checkbox),
|
||||
`%TopBar`/`%PropNameEdit`, `%MainLayout`/`%CanvasPanel`/`%CanvasArea`/`%ModeBar`/`%PropCanvas`,
|
||||
`%InspectorColumn`/`%InspectorList`, a bottom status bar with `%SaveStatus` left +
|
||||
@@ -1355,7 +1365,7 @@ rotation, scale}`.
|
||||
### `.prop` file format (Phase 9)
|
||||
|
||||
- The Prop Editor's `.prop` vector-prop assets are versioned JSON (`version "1.0"`),
|
||||
defined in `README.md`, saved under `res://props/*.prop`, and converted to a runtime
|
||||
defined in `README.md`, saved under `res://content/props/*.prop`, and converted to a runtime
|
||||
payload by `PropFile.flatten_to_payload()`.
|
||||
- Shape dictionary (per entry in `shapes[]`): `{ "shape_type": "polygon"|"circle"|"line",
|
||||
"points": Array[{x,y}] (polygon/line), "radius": float (circle), "fill_color": "#hex",
|
||||
@@ -1388,7 +1398,7 @@ rotation, scale}`.
|
||||
|
||||
### Legacy scene (do not delete)
|
||||
|
||||
- `stick.tscn` — the original rigged/animated figure using `Skeleton2D` + IK targets +
|
||||
- `scenes/legacy/stick.tscn` — the original rigged/animated figure using `Skeleton2D` + IK targets +
|
||||
`RemoteTransform2D` + `Line2D` limbs, plus an embedded `@tool` script drawing the head
|
||||
circle. Animations: "walk", "walk_to", "RESET". **Not** the current main scene; kept for
|
||||
future animation/rigging phases.
|
||||
|
||||
Reference in New Issue
Block a user