Wire up editor: input bindings, frame hooks, launcher script
init.lua loads the work map, binds hotkeys (E/R/S/Esc), tracks mouse-cell per frame, dispatches clicks to the UI hit-tester and falls through to paint-cell for unconsumed map-area clicks. run-map-editor.sh handles copy-in/copy-out for a real target map or runs in-place against the module's work slot.
This commit is contained in:
45
README.md
45
README.md
@@ -1,15 +1,46 @@
|
||||
# sporel-module-map-editor
|
||||
|
||||
Interactive map editor module for v2 Sporel maps. Click-to-paint, vertical layer-chip overlay, collapsible atlas+tile picker.
|
||||
|
||||
## Status
|
||||
|
||||
`v0.1.0` — initial POC. See `docs/superpowers/specs/2026-05-23-sporel-module-map-editor-design.md` in `sporel-meta` for the full spec.
|
||||
Interactive map editor for v2 Sporel map files. Click-to-paint, vertical layer-chip overlay on the left edge, collapsible atlas+tile picker bottom-left.
|
||||
|
||||
## Run
|
||||
|
||||
Default — edit the module's in-place work map:
|
||||
|
||||
```bash
|
||||
bash scripts/run-map-editor.sh [target_map.json]
|
||||
bash scripts/run-map-editor.sh
|
||||
```
|
||||
|
||||
(Detailed hotkey reference and UI walkthrough added in a later commit.)
|
||||
With a target — copy a real map in, edit, copy back out:
|
||||
|
||||
```bash
|
||||
bash scripts/run-map-editor.sh ~/Projects/Sporel/sporel-modules/spine-prototype/maps/demo.map.json
|
||||
```
|
||||
|
||||
## UI
|
||||
|
||||
- **Layer chips** (left edge, vertical): top-of-stack down. `Rf` at top toggles roof-edit mode; the other eight chips select the active layer.
|
||||
- **Left-click** = set active layer (or toggle roof mode)
|
||||
- **Right-click** = toggle layer visibility (Rf chip ignores right-click)
|
||||
- **Atlas + tile picker** (bottom-left): collapsed chip shows the active tile. Click to expand; click again or outside to collapse.
|
||||
- **Status chip** (bottom-right): current mouse-cell coordinate plus a `*` when the map has unsaved changes.
|
||||
|
||||
## Hotkeys
|
||||
|
||||
| Key | Action |
|
||||
|---|---|
|
||||
| `Left click` on map | Paint cell with active tile (or set roof flag in roof-mode) |
|
||||
| `E` | Erase the cell under the mouse |
|
||||
| `R` | Cycle rotation 0 → 1 → 2 → 3 → 0 |
|
||||
| `S` | Save to disk (overwrites the work map) |
|
||||
| `Esc` | Quit |
|
||||
|
||||
## Limitations (v0.1.0)
|
||||
|
||||
- Tile thumbnails in the picker render as gray placeholders with tile-IDs as text — lib-core.maps v0.4.0 does not expose per-tile UVs to Lua, so visual thumbnails are a v0.2.0 task.
|
||||
- Atlas list in the picker is purely informational; if a map declares multiple atlases, the active atlas is set from `state.lua` defaults at startup and cannot yet be switched via UI (v0.2.0 hit-tests inside the expanded picker).
|
||||
- Engine bindings `engine.render.draw_rect_lines` and `engine.render.draw_line` are not yet registered — active-chip borders, hidden-layer strikethroughs, and the entity-slot divider will trigger runtime errors when their code paths execute. Adding the bindings is a small engine-side follow-up.
|
||||
- No undo/redo, no multi-tile brush, no entity placement, no heightmap editing — see the design spec §1 for the full out-of-scope list.
|
||||
|
||||
## Spec
|
||||
|
||||
`docs/superpowers/specs/2026-05-23-sporel-module-map-editor-design.md` in the `sporel-meta` repo.
|
||||
|
||||
83
init.lua
83
init.lua
@@ -1,18 +1,91 @@
|
||||
-- sporel-module-map-editor v0.1.0
|
||||
-- Interactive map editor; full wiring lands in subsequent commits.
|
||||
-- Interactive map editor — see docs/superpowers/specs/2026-05-23-sporel-module-map-editor-design.md
|
||||
|
||||
local maps = require("lib-core.maps")
|
||||
local maps = require("lib-core.maps")
|
||||
local camera = require("lib-core.camera")
|
||||
local input = require("lib-core.input")
|
||||
local state = require("src.state")
|
||||
local actions = require("src.actions")
|
||||
local ui = require("src.ui")
|
||||
|
||||
local work_map_id = maps.load("maps/work.map.json")
|
||||
-- =====================================================================
|
||||
-- Setup
|
||||
-- =====================================================================
|
||||
local WORK_PATH = "maps/work.map.json"
|
||||
local work_map_id = maps.load(WORK_PATH)
|
||||
maps.set_current(work_map_id)
|
||||
maps.load_textures(engine.module.asset_aliases())
|
||||
state.set_map(work_map_id, WORK_PATH)
|
||||
|
||||
engine.print("map-editor: scaffold loaded, map_id=" .. work_map_id)
|
||||
-- Center camera on the map
|
||||
local m_size = maps.size()
|
||||
local t_size = maps.tile_size()
|
||||
camera.set_target((m_size.w * t_size) / 2, (m_size.h * t_size) / 2)
|
||||
camera.set_zoom(1.0)
|
||||
|
||||
-- =====================================================================
|
||||
-- Input bindings
|
||||
-- =====================================================================
|
||||
input.bind("quit", { "escape" })
|
||||
input.bind("erase", { "e" })
|
||||
input.bind("rotate", { "r" })
|
||||
input.bind("save_map", { "s" })
|
||||
|
||||
engine.print("map-editor: ready. Hotkeys: E=erase, R=rotate, S=save, ESC=quit")
|
||||
|
||||
-- =====================================================================
|
||||
-- Frame hooks
|
||||
-- =====================================================================
|
||||
function update(ctx, dt)
|
||||
-- TODO: wired in later commits
|
||||
-- Quit
|
||||
if input.was_action_pressed("quit") then
|
||||
engine.exit()
|
||||
return
|
||||
end
|
||||
|
||||
-- Update mouse-cell tracking for status chip
|
||||
local mx, my = engine.input.get_mouse_pos()
|
||||
local wx, wy = camera.screen_to_world(mx, my)
|
||||
local cell_x = math.floor(wx / t_size)
|
||||
local cell_y = math.floor(wy / t_size)
|
||||
if cell_x >= 0 and cell_y >= 0 and cell_x < m_size.w and cell_y < m_size.h then
|
||||
state.set_mouse_cell(cell_x, cell_y)
|
||||
else
|
||||
state.set_mouse_cell(nil)
|
||||
end
|
||||
|
||||
-- Hotkey: rotate
|
||||
if input.was_action_pressed("rotate") then
|
||||
actions.cycle_rotation()
|
||||
end
|
||||
|
||||
-- Hotkey: save
|
||||
if input.was_action_pressed("save_map") then
|
||||
actions.save()
|
||||
end
|
||||
|
||||
-- Hotkey: erase (uses current mouse-hover cell)
|
||||
if input.was_action_pressed("erase") then
|
||||
local cell = state.get_mouse_cell()
|
||||
if cell then actions.erase_cell_at(cell.x, cell.y) end
|
||||
end
|
||||
|
||||
-- Mouse clicks: hit-test UI first, then map area
|
||||
if engine.input.was_mouse_pressed(engine.input.MOUSE_LEFT) then
|
||||
local consumed = ui.handle_click(mx, my, "left")
|
||||
if not consumed and state.get_mouse_cell() then
|
||||
local cell = state.get_mouse_cell()
|
||||
actions.paint_cell_at(cell.x, cell.y)
|
||||
end
|
||||
end
|
||||
if engine.input.was_mouse_pressed(engine.input.MOUSE_RIGHT) then
|
||||
ui.handle_click(mx, my, "right")
|
||||
end
|
||||
|
||||
camera.update(dt)
|
||||
end
|
||||
|
||||
function draw()
|
||||
maps.draw_map()
|
||||
ui.draw()
|
||||
end
|
||||
|
||||
51
scripts/run-map-editor.sh
Normal file
51
scripts/run-map-editor.sh
Normal file
@@ -0,0 +1,51 @@
|
||||
#!/usr/bin/env bash
|
||||
# run-map-editor.sh — launcher for sporel-module-map-editor
|
||||
# Usage: bash scripts/run-map-editor.sh [target_map.json]
|
||||
#
|
||||
# If a target is given, copy it into the editor's work-slot before
|
||||
# launching, then copy the edited result back to the target after exit.
|
||||
# Without a target, edits the in-place work.map.json.
|
||||
|
||||
set -e
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
||||
EDITOR_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
|
||||
WORK_SLOT="$EDITOR_DIR/maps/work.map.json"
|
||||
SPOREL_ROOT="$(cd "$EDITOR_DIR/../.." && pwd)"
|
||||
SPOREL_BIN="${SPOREL_BIN:-$SPOREL_ROOT/sporel-engine/build/Sporel.exe}"
|
||||
|
||||
if [ ! -x "$SPOREL_BIN" ]; then
|
||||
echo "error: Sporel binary not found at $SPOREL_BIN" >&2
|
||||
echo "set SPOREL_BIN env-var or build the engine first" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
TARGET="${1:-}"
|
||||
if [ -n "$TARGET" ]; then
|
||||
if [ ! -f "$TARGET" ]; then
|
||||
echo "error: target map not found: $TARGET" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "copying $TARGET -> $WORK_SLOT"
|
||||
cp "$TARGET" "$WORK_SLOT"
|
||||
fi
|
||||
|
||||
echo "launching map-editor on $WORK_SLOT"
|
||||
|
||||
export SPOREL_LIBS_DIR="$SPOREL_ROOT/sporel-libs"
|
||||
export SPOREL_MODULES_DIR="$SPOREL_ROOT/sporel-modules"
|
||||
export SPOREL_MODULE=map-editor
|
||||
|
||||
LOG_FILE="${SPOREL_LOG:-/tmp/sporel-last-run.log}"
|
||||
set +e
|
||||
"$SPOREL_BIN" "$@" 2> >(tee "$LOG_FILE" >&2)
|
||||
rc=$?
|
||||
set -e
|
||||
|
||||
if [ -n "$TARGET" ] && [ $rc -eq 0 ]; then
|
||||
echo "copying $WORK_SLOT -> $TARGET"
|
||||
cp "$WORK_SLOT" "$TARGET"
|
||||
fi
|
||||
|
||||
echo "done (exit $rc)"
|
||||
exit $rc
|
||||
Reference in New Issue
Block a user