# Stickman Studio A Godot 4.4 editor tool for creating and assembling stick figures. Stickman Studio lets you draw individual body-part shapes, edit vertices, assemble them into a whole stickman, and save/load your figure to a JSON file (`.stk`) for later use or animation scripting. ## Overview The editor is organized as **11 sub-windows** in a 3-column layout: - **Left column (5)** — Head, Left Upper Arm, Left Lower Arm, Left Upper Leg, Left Lower Leg - **Center column (5)** — Torso, Right Upper Arm, Right Lower Arm, Right Upper Leg, Right Lower Leg - **Right column (1)** — **Whole Stickman** preview The editor is driven by three menus: **File** (Save / Load / Clear), **Edit** (Configure Grid... / Snap to Grid), and **View** (Reset Views). Each body-part panel is an independent vector drawing surface with per-panel zoom **and panning (middle-mouse drag)**. A configurable background grid helps align vertices, and snap-to-grid can be enabled for both vertex dragging and whole-figure assembly. **Each panel supports multiple shapes** with Z-ordering controls (Send Back / Bring Forward) and shape-level Copy/Paste and Mirror operations. The Whole Stickman panel assembles every part into one figure — treating all shapes in a panel as a single unit — and lets you reposition, rotate, and scale each part, reorder the parts (Z-order), and mirror them. Drawing surfaces **clip** their content to the panel bounds: shapes and parts no longer render outside a panel's borders when dragged or panned. A selected shape can be **dragged to reposition** it within its panel. ## Requirements | Item | Version | |---|---| | Engine | Godot 4.4 or newer | | Addon | Scalable Vector Shapes 2D (`addons/curved_lines_2d/`) | > The legacy `stick.tscn` scene (a rigged walk animation using `Skeleton2D` + IK) remains in the project root for reference but is **no longer the main scene**. ## Running the project No CLI build, test, or lint commands are used. Run the project from the Godot editor: 1. Open the project folder in **Godot 4.4**. 2. Press **F5**, or open and run `res://scenes/stickman_editor.tscn`. The main scene is configured as `run/main_scene="res://scenes/stickman_editor.tscn"` in `project.godot`. ## Quick Start ### 1. Create a body part 1. Locate one of the 10 body-part panels (e.g. **Head**). 2. **Right-click** anywhere inside its drawing area to open the context menu. 3. Choose a starter shape: - **Line** — a 120 px horizontal segment (2 vertices, open shape). - **Rectangle** — a 100 × 60 px rectangle (4 vertices, closed shape). - **Circle** — a 12-segment polygon (12 vertices, closed shape, radius 40). The shape draws immediately and syncs to the Whole Stickman panel. Rendering behavior: - **Closed shapes** (`closed: true`) fill their interior with the chosen color (`draw_colored_polygon`) and draw a 2 px outline. - **Open shapes** (`closed: false`) render as an outline-only segment with no fill. Body-part panels expand vertically to fill their column height. ### 2. Edit vertices Each panel supports vertex-level editing: 1. **Select a shape** — **Left-click** anywhere inside a shape to select it. The outline turns white. 2. **Create a vertex** — With a shape selected, **right-click** on its outline and choose "Create Point". A new vertex (shown as a hollow rectangle) is added at the nearest edge midpoint. 3. **Drag vertices** — **Left-click** on any vertex handle (filled circle = original, hollow rectangle = user-created) and drag to reshape. The shape conforms in real time. Release to apply. With **Snap to Grid** enabled, the dragged vertex jumps to the nearest grid intersection. 4. **Delete a vertex** — **Right-click** on a vertex handle and choose **Remove Point** (see *Deleting* below). 5. **Deselect** — Left-click outside the shape or select a different panel. Only one shape is selected at a time. **Move a whole shape (Phase 5):** 1. **Left-click** a shape **not on a vertex handle** and drag to reposition the entire shape within the panel. 2. Release to commit. All vertices translate together; the shape's Z-order is unchanged. 3. With **Snap to Grid** enabled, the dragged shape jumps to the nearest grid intersection on release. ### 3. Zoom & Pan Scroll the mouse wheel inside any body-part panel or the Whole Stickman preview to zoom. Pan the view with the middle-mouse button: - **Wheel up** — zoom in by 10% - **Wheel down** — zoom out by 10% - **Middle-mouse drag** — pan the view in any direction - **Range** — 30% (minimum) to 300% (maximum) - Vertex handles remain visible at all zoom levels. - Each panel has independent zoom and pan offset. - Panning takes priority over vertex dragging (middle-mouse press cancels any in-progress drag). - **Content is clipped to the panel bounds (Phase 5)** — shapes, grid, and gizmos cannot render outside a panel's drawing area, even when panned or dragged beyond its edge. This keeps each panel self-contained. - Use **View → Reset Views** to restore all panels to 100% zoom and the origin pan offset. ### 4. Assemble a stickman 1. Create shapes for every part you want in the figure. 2. In the **Whole Stickman** panel, **drag any part** to reposition it in the figure. - Parts get a generous hit area and show a yellow highlight while being dragged. - A short label (`H`, `T`, `LUA`, `RUA`, `LUL`, `RLL`, …) marks each part. - Use the mouse wheel to zoom in/out and middle-mouse to pan for precise positioning. - With **Snap to Grid** enabled, the mouse pointer snaps to the nearest grid intersection and the part follows. 3. Optionally type a name in the **Stickman Name** field at the top. 4. **Right-click a selected part (Phase 5)** to reorder or mirror it: - **Send Back / Bring Forward** — change the part's draw order in the preview. - **Mirror X / Mirror Y** — flip the part around its bounding-box center. ### 5. Grid & Panning All 10 body-part panels and the Whole Stickman preview share a global background grid (default cell size **15 px**, Phase 5; previously 5 px): 1. Click **Edit → Configure Grid...** and set the grid size. - **SpinBox** accepts 1–100 px per cell. - The setting is applied to every panel simultaneously and persists to `user://settings.json`. 2. Toggle **Edit → Snap to Grid** to enable snapping (Phase 5: the checkable menu item now reliably shows its ✓ checkmark when toggled): - On **vertices** — while dragging a vertex handle, it jumps to the nearest grid intersection. - On **assembly** — while dragging a part in the Whole Stickman preview, the pointer snaps and the part follows. - On **shape drag** — while dragging a whole shape (Phase 5), it snaps on release. - Existing vertices are *not* retroactively snapped; snapping only applies to new drags. 3. **Pan** — hold the **middle-mouse button** and drag anywhere in a panel to pan the view. - Panning uses the same transform as zoom, and each panel keeps an independent offset. - **View → Reset Views** restores 100% zoom and the origin offset on all panels. 4. Grid lines are drawn behind shapes and scale with pan/zoom, so they stay aligned to world coordinates. ### 6. Changing Colors Colors are easy to change on any existing shape: 1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Color...**. 2. A **ColorPicker** popup appears with the shape's current color pre-loaded. 3. Adjust the color — the shape updates live as a preview. 4. Click **OK** to commit the change or **Cancel** to revert to the original color. The chosen color is stored in the shape's `color` field and is saved/loaded with the `.stk` file. ### 7. Deleting Two levels of deletion are available: **Delete an entire shape:** 1. **Right-click** on a shape (either selected or with the cursor over it) and choose **Delete**. - Delete is only offered when a shape exists and the mouse is over it. 2. The shape is removed from the panel and the Whole Stickman preview. **Remove a single vertex:** 1. **Right-click** on a vertex handle and choose **Remove Point**. 2. If 2 vertices remain, the shape becomes an open **line** (`closed = false`). 3. If 1 vertex or fewer remains, the entire shape is cleared. ### 8. Multiple Shapes & Z-Ordering Each body-part panel can contain **more than one shape**. New shapes are created via the standard right-click context menu and are drawn on top of existing shapes. Shapes are drawn in Z-order (last in the list = frontmost). **Z-ordering controls:** - **Right-click** on a shape and choose **Send Back** to move it one step behind (earlier in draw order). - **Right-click** on a shape and choose **Bring Forward** to move it one step ahead (later in draw order). Z-order is preserved in the `.stk` save file (shapes are stored in draw order within the `shapes` array). Only one shape within a panel can be selected at a time for vertex editing. Left-click a shape to select it; right-click also selects the shape under the mouse for context menu operations. **Shape Copy & Paste (Phase 5):** 1. **Right-click** on a shape and choose **Copy** to place a deep copy of the shape on the editor-wide clipboard. 2. **Right-click** (on empty space or a shape) and choose **Paste** to insert the clipboard shape at the click position. - Paste only appears while the clipboard is populated. - The clipboard **persists after pasting** and is shared across **all 10 body-part panels** — a shape copied in one panel can be pasted into any other. - The pasted shape becomes the newly selected (topmost) shape. - **Copy → Clear → Paste** semantics: the clipboard survives figure clear. **Shape Mirroring (Phase 5):** 1. **Right-click** on a selected shape and choose **Mirror X** or **Mirror Y**. 2. The shape's vertices are mirrored around the shape's bounding-box center. 3. Mirroring recomputes the `points` array in place — no extra fields are stored; the result is saved as normal vertex data. ### 9. Whole Stickman Manipulation (Selection, Rotation, Scale) The Whole Stickman preview treats all shapes in a body-part panel as **one combined object**. Each part can be independently translated, rotated, and scaled. **Selection:** - **Left-click** on any part in the Whole Stickman preview to select it. - A **white bounding box** appears around the selected part, along with manipulation gizmos. - Click empty space to deselect. **Translation:** - **Left-click and drag** a selected part to reposition it. Snap-to-grid applies to the pointer position when enabled. **Rotation:** - When a part is selected, a **filled circle** appears centered below the bounding box. - **Click and drag** the rotation circle to rotate the part around its center. - Hold **Ctrl** while rotating to snap to **15-degree increments** (relative to the original 0° position). - Rotation is **absolute**: 0° always means the original orientation the object was created with. - The accumulated rotation value is saved in the `.stk` file. **Scale:** - When a part is selected, **cross/plus markers** appear at all four corners of the bounding box. - **Click and drag** any corner cross to freely scale the part. - Hold **Ctrl** while scaling to **lock the aspect ratio** (scale uniformly). - Grid snapping snaps the corner position to the nearest grid intersection. - Scale is **relative to the created size**: (1.0, 1.0) = original size. - The scale value is saved in the `.stk` file. Scale may be **negative** for mirrored parts. - **Part mirroring (Phase 5)** — right-click a selected part and choose **Mirror X** or **Mirror Y** to mirror it around its bounding-box center. This is implemented by negating the part's scale factor along the chosen axis (no vertex data is changed). **Part Z-ordering (Phase 5):** - Right-click a selected part and choose **Send Back** or **Bring Forward** to move it one step within the preview's draw order. - Selection respects Z-order: hit-testing runs **front-to-back**, so the frontmost part under the pointer is selected. - Part Z-order is saved in the `.stk` file as the top-level `part_order` array. ### 10. Save 1. Click **File → Save**. 2. Choose a location and name. The default extension is `.stk` (appended automatically if omitted). 3. Click **Save**. The figure is written as JSON. ### 11. Load 1. Click **File → Load**. 2. Select a `.stk` file. 3. On success, all body-part panels and the Whole Stickman preview are populated. On failure, an error dialog reports the problem (missing file, parse error, or unsupported version). > v1.0, v1.1, and v1.2 files are automatically migrated to v1.3 on load (v1.0/v1.1 single shapes wrapped in a `shapes` array, rotation defaults to 0, scale defaults to (1,1); files without `part_order` fall back to the default part order). ### 12. Clear 1. Click **File → Clear**. 2. A confirmation dialog warns that the current stickman will be cleared. 3. Confirm to reset all panels and the preview (zoom resets to 100%, pan offset to origin). ## 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. ```json { "version": "1.3", "stickman_name": "Bob", "part_order": [ "head", "torso", "left_upper_arm", "left_lower_arm", "right_upper_arm", "right_lower_arm", "left_upper_leg", "left_lower_leg", "right_upper_leg", "right_lower_leg" ], "body_parts": { "head": { "shapes": [ { "shape_type": "circle", "closed": true, "points": [ { "x": 120, "y": 40 }, { "x": 124, "y": 39 } ], "color": "#000000", "vertex_flags": [0, 0, 1, 0] } ], "position": { "x": 150, "y": 40 }, "rotation": 0.0, "scale": { "x": 1.0, "y": 1.0 } } }, "metadata": { "created_at": "2026-08-05T12:00:00", "modified_at": "2026-08-05T12:00:00" } } ``` ### Root object | Key | Type | Description | |---|---|---| | `version` | `string` | Format version. Currently `"1.3"`. Loading supports `"1.0"`–`"1.3"` (auto-migrated). | | `stickman_name` | `string` | Optional display name for the figure. | | `part_order` | `array[string]` | **Phase 5.** Render/Z-order of parts in the Whole Stickman preview, front-to-back semantics per array position (first = back, last = front). Absent on v1.0–v1.2 files; defaults to the internal part-key order when missing. | | `body_parts` | `object` | Map of `body_part_name → shape` objects. Keyed by the 10 internal part names below. | | `metadata.created_at` | `string` | Timestamp written on save. | | `metadata.modified_at` | `string` | Timestamp written on save. | ### Part keys `head`, `torso`, `left_upper_arm`, `left_lower_arm`, `right_upper_arm`, `right_lower_arm`, `left_upper_leg`, `left_lower_leg`, `right_upper_leg`, `right_lower_leg` ### Part object Each body part is an object with the following keys: | Key | Type | Description | |---|---|---| | `shapes` | `array` | Array of shape objects (see below). Draw order = array order (first = back, last = front). Wrapped as a single-element array when migrating v1.0/v1.1 files. | | `position` | `object` | `{ "x": float, "y": float }` position of the part within the Whole Stickman preview. | | `rotation` | `float` | Rotation in degrees. `0.0` = original orientation. Defaults to `0.0` for v1.0/v1.1 files. | | `scale` | `object` | `{ "x": float, "y": float }` scale factors relative to created size. `1.0` = original size. May be **negative** (Phase 5) to represent mirroring along an axis. Defaults to `{ "x": 1.0, "y": 1.0 }` for older files. | ### Shape object Each entry in the `shapes` array: | Key | Type | Description | |---|---|---| | `shape_type` | `string` | `"line"`, `"rectangle"`, `"circle"`, or `""` (empty). Descriptive tag only — rendering uses `closed`. | | `closed` | `bool` | Whether the polygon is closed (filled) or open (outline-only). | | `points` | `array` | Array of `{ "x": float, "y": float }` vertex positions **local to the drawing area**. | | `color` | `string` | Hex colour, e.g. `"#000000"`. Changeable in-app via right-click → **Color...** (Phase 3). | | `vertex_flags` | `array[int]` | Same length as `points`. `0` = original vertex, `1` = user-created via "Create Point". Controls handle appearance. | > On load, parts missing from the file fall back to built-in default preview positions. v1.0 files have `closed` inferred from `shape_type` and `vertex_flags` default to all zeros. v1.0/v1.1 files have their single shape wrapped in a `shapes` array and default `rotation`/`scale` applied. Files without `part_order` (v1.0–v1.2) use the default part-key order. Note that shape mirroring (Phase 5) rewrites the `points` array in place, so no new shape fields are required — the resulting vertices load identically across all supported versions. ## Settings file (`user://settings.json`) Phase 3 introduces a global settings file stored in Godot's **user data** directory (`user://`), separate from the `.stk` figure files. It persists the grid configuration and is versioned for future extensibility: ```json { "version": "1.0", "grid_size": 15, "snap_to_grid": false } ``` ### Settings keys | Key | Type | Default | Description | |---|---|---|---| | `version` | `string` | `"1.0"` | Settings file version (for future extensibility). | | `grid_size` | `int` | `15` | Grid interval in pixels, applied equally to width and height. Default changed from `5` to `15` in Phase 5. Clamped to 1–100 on load. | | `snap_to_grid` | `bool` | `false` | Whether snap-to-grid is active. | Behavior: - **Load** — read on editor startup; if the file is missing or fails to parse, defaults (`grid_size = 15`, `snap_to_grid = false`) are used silently (no error dialog). - **Save** — written whenever the user changes the grid size or toggles Snap to Grid. - **Scope** — global; all body-part panels and the Whole Stickman preview share the same grid size and snap setting. - **Pan offsets are NOT persisted** — they reset on load, clear, and "Reset Views". ## Project structure | Path | Purpose | |---|---| | `res://project.godot` | Engine config; sets main scene to the editor and enabled features. | | `res://scenes/stickman_editor.tscn` | **Main scene** — editor layout, File/Edit/View menu bar, dialogs (`GridConfigDialog` + SpinBox), column containers (unique-name nodes). | | `res://scenes/body_part_panel.tscn` | Reusable single body-part editor panel (title, drawing area, context menu, `ColorPickerPopup`); expands vertically in its column. | | `res://scripts/stickman_editor.gd` | Editor controller — File/Edit/View menu actions, save/load/clear, JSON v1.3 serialization with multi-shape/rotation/scale and `part_order`, `settings.json` load/save, editor-wide shape clipboard (Copy/Paste across panels), broadcast of grid/snap settings to panels, Reset Views, populates panels, coordinates selection across panels. | | `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, part hit-bounds, labels. | | `res://addons/curved_lines_2d/` | Scalable Vector Shapes 2D addon (v2.27.7) — required dependency. | | `res://stick.tscn` | **Legacy** rigged/animated stick figure scene (Skeleton2D + IK). Not used by the editor. | | `res://AGENTS.md` | Guidance for AI agents working in this codebase. | ### Menu bar structure ``` File Edit View ──────────── ──────────────────── ─────────── Save Configure Grid... Reset Views Load ───────── ──────── Snap to Grid (check) Clear ``` - **File** — Save, Load, Clear (Phase 1). - **Edit** — `Configure Grid...` (dialog with a 1–100 SpinBox) and the checkable `Snap to Grid` toggle (Phase 3). - **View** — `Reset Views` (resets zoom to 100% and pan offset to origin on every panel) (Phase 3). ### Context menu (per body-part panel) The right-click context menu is context-sensitive. Item IDs: | ID | Label | Shown when | |----|-------|------------| | 0 | Line | Empty space right-click | | 1 | Rectangle | Empty space right-click | | 2 | Circle | Empty space right-click | | 3 | Create Point | Shape under mouse, near outline | | 4 | Color... | Shape under mouse | | 5 | Delete | Shape under mouse | | 6 | Remove Point | Right-click on a vertex handle | | 7 | Send Back | Shape under mouse | | 8 | Bring Forward | Shape under mouse | | 9 | Copy | Shape under mouse (Phase 5) | | 10 | Paste | Shape or empty space right-click; shown only when the clipboard is populated (Phase 5) | | 11 | Mirror X | Shape under mouse (Phase 5) | | 12 | Mirror Y | Shape under mouse (Phase 5) | ### Context menu (Whole Stickman preview) Right-clicking a selected part opens the preview's context menu: | ID | Label | Description | |----|-------|-------------| | 0 | Send Back | Move the selected part one step back in `part_order` (Phase 5) | | 1 | Bring Forward | Move the selected part one step forward in `part_order` (Phase 5) | | 2 | Mirror X | Mirror the selected part around its bounding-box center (scale X negated) (Phase 5) | | 3 | Mirror Y | Mirror the selected part around its bounding-box center (scale Y negated) (Phase 5) | ### Signal flow ``` BodyPartPanel.shape_changed(data) ---(bound to part_name)---> stickman_editor | | | _on_body_part_shape_changed | +--------------------------> WholeStickmanPreview | .set_body_parts(all_data) | ^ | WholeStickmanPreview.part_moved(name, pos) (drag handling) --+ BodyPartPanel.shape_selected() ---(bound to part_name)---> stickman_editor | _on_body_part_shape_selected (deselects all other panels) ``` - `BodyPartPanel` emits `shape_changed(shapes_array)` whenever a shape is created, a vertex is dragged (on release), a color is committed via the ColorPicker (on OK), a shape is deleted, a vertex is removed via "Remove Point", or Z-order changes via "Send Back"/"Bring Forward". The signal carries the full `Array[Dictionary]` of all shapes in the panel. Note: `clear_shape()` does **not** emit the signal; the editor's Clear flow refreshes the preview directly. - `BodyPartPanel` emits `shape_selected` when left-clicked; the editor deselects all other panels. - `stickman_editor.gd` collects shape data from all 10 panels and forwards it to `WholeStickmanPreview.set_body_parts()`. - `WholeStickmanPreview` emits `part_moved(part_name, new_position)` during translation drags. Rotation and scale are stored internally and polled by the editor at save time via `get_part_rotation()` / `get_part_scale()`. Part Z-order is polled the same way via `get_part_order()` and saved to the top-level `part_order` field (Phase 5). - Shape Copy/Paste (Phase 5) is owned by the editor: `_copy_shape()` stores a deep duplicate in `_shape_clipboard`, `_broadcast_clipboard_state()` tells every panel whether Paste should be offered, and `_paste_shape()` inserts the clipboard into the target panel at the click position. The clipboard persists after pasting and is shared across all panels. > **Phase 4:** Each body part can have multiple shapes in Z-order. The Whole Stickman preview treats all shapes in a panel as one combined object, with selection (white bounding box), rotation (circle gizmo below box), and scale (cross gizmos at corners). The `.stk` format evolved to v1.2 with a `shapes` array per part plus `rotation` and `scale` fields. Rotation snaps to 15° with Ctrl; scale locks aspect ratio with Ctrl. > **Phase 5:** Adds drawing-surface clipping, shape dragging, an editor-wide shape clipboard (Copy/Paste across panels), shape Mirror X/Y (vertex recompute), and preview object Z-ordering (Send Back/Bring Forward) with Mirror X/Y (scale negation). The format evolved to v1.3: a new top-level `part_order` array stores preview Z-order; scale may be negative for mirrored parts; shape mirroring stores no new fields because it rewrites `points`. The default grid size changed from 5 px to 15 px, and the Snap to Grid checkmark now renders correctly when toggled. v1.0–v1.2 files remain backward compatible and are migrated on load.