Files
sporel-module-spine-prototype/init.lua
Axel Meyer 1a3f2842b7 feat(P.3.6): selection demo — 6 stones + click/box-select wiring
- camera dep 0.2.0 → 0.3.0 (screen_to_world consumer)
- selection dep 0.1.0 added
- 6 stones registered as selectables
- selection.update + render_highlights + render_drag_box wired
- input: lmb + mod_add (shift) + mod_toggle (ctrl)
2026-05-14 15:14:47 +02:00

179 lines
6.8 KiB
Lua
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
local maps = require("lib-core.maps")
local r_lib = require("lib-core.render")
local camera = require("lib-core.camera")
local input = require("lib-core.input")
local player = require("lib-core.player_control")
local interaction = require("lib-core.interaction")
local selection = require("lib-core.selection")
local demo_map = maps.load("maps/demo.map.json")
maps.set_current(demo_map)
local m_size = maps.size()
local t_size = maps.tile_size()
local map_center_x = (m_size.w * t_size) / 2
local map_center_y = (m_size.h * t_size) / 2
camera.set_target(map_center_x, map_center_y)
camera.set_zoom(1.0)
-- Bind input actions (string-key-names per lib-core.input v0.2.0)
input.bind("quit", { "escape" })
input.bind("move_left", { "a", "left" })
input.bind("move_right", { "d", "right" })
input.bind("move_up", { "w", "up" })
input.bind("move_down", { "s", "down" })
input.bind("interact", { "e" })
-- P.3.5: Free-Cam demo (Tab toggle)
input.bind("toggle_free_cam", { "tab" })
input.bind("pan_drag", { "mouse_middle" })
-- pan_*-Actions teilen sich die Keys mit move_*-Actions (additive bindings).
input.bind("pan_left", { "a", "left" })
input.bind("pan_right", { "d", "right" })
input.bind("pan_up", { "w", "up" })
input.bind("pan_down", { "s", "down" })
-- P.3.6: Selection bindings
input.bind("lmb", { "mouse_left" })
input.bind("mod_add", { "shift" })
input.bind("mod_toggle", { "ctrl" })
selection.bind_action("lmb")
selection.bind_modifier_add("mod_add")
selection.bind_modifier_toggle("mod_toggle")
camera.bind_pan_keys("pan_left", "pan_right", "pan_up", "pan_down")
camera.bind_pan_drag("pan_drag")
camera.enable_pan_edge()
-- Start in follow-mode: pan_keys aus (drag/edge bleiben enabled, sind aber
-- unsichtbar in follow-mode wegen set_target-override jeden Frame).
camera.set_pan_enabled("keys", false)
-- Configure player: defaults are 24x24 + 200 px/sec; place at map-center
local p_size = player.size()
player.set_position(map_center_x - p_size.w / 2, map_center_y - p_size.h / 2)
player.bind_movement("move_left", "move_right", "move_up", "move_down")
local PLAYER_R, PLAYER_G, PLAYER_B = 200, 60, 80
-- Sign as module-local (actor deferred — see actor-deferral spec)
-- Tile (4,4) center: 4*32+16 = 144 px in walkable grass-area
local sign = { x = 144, y = 144, text = "Hier steht: Willkommen im Spine." }
local SIGN_R, SIGN_G, SIGN_B = 220, 200, 60
-- P.3.6: 6 stones as selectables (demo for click/box-select + modifiers).
-- Positions distributed across walkable map area; sizes uniform 20×20.
local stones = {
{ x = 64, y = 96, w = 20, h = 20, color = {120, 120, 140} },
{ x = 224, y = 96, w = 20, h = 20, color = {120, 120, 140} },
{ x = 96, y = 256, w = 20, h = 20, color = {130, 110, 100} },
{ x = 320, y = 192, w = 20, h = 20, color = {130, 110, 100} },
{ x = 192, y = 320, w = 20, h = 20, color = {140, 130, 110} },
{ x = 384, y = 256, w = 20, h = 20, color = {140, 130, 110} },
}
-- P.3.5: Free-Cam-Mode state
local free_cam_mode = false
local frames = 0
local TARGET_FRAMES = 60
function init(ctx)
engine.print("spine-prototype init: maps loaded")
engine.print(string.format("map size: %dx%d", m_size.w, m_size.h))
local corner = maps.tile_at(0, 0)
local interior = maps.tile_at(5, 5)
engine.print("tile at (0,0): " .. corner.id)
engine.print("tile at (5,5): " .. interior.id)
engine.print("walkable at (0,0): " .. tostring(maps.is_walkable(0, 0)))
engine.print("walkable at (5,5): " .. tostring(maps.is_walkable(5, 5)))
engine.print(string.format("render: %d tiles per frame", m_size.w * m_size.h))
local t = camera.target()
engine.print(string.format("camera: target=(%d,%d) zoom=%.2f", t.x, t.y, camera.zoom()))
engine.print(string.format("input: %d actions bound", input.action_count()))
local p = player.position()
local s = player.size()
engine.print(string.format("player: pos=(%d,%d) size=%dx%d speed=%d",
p.x, p.y, s.w, s.h, player.speed()))
interaction.register(sign.x, sign.y, 40, "interact", function(info)
engine.print(sign.text)
end)
engine.print(string.format("interaction: %d trigger(s) registered",
interaction.trigger_count()))
-- P.3.6: Register each stone as selectable.
for _, s in ipairs(stones) do
s.sel_handle = selection.register(function()
return { x = s.x, y = s.y, w = s.w, h = s.h }
end)
end
engine.print(string.format("selection: %d selectables registered",
selection.count_registered()))
end
function render(ctx)
engine.render.clear_color(engine.render.rgb(20, 20, 30))
camera.begin()
r_lib.draw_map()
-- draw sign on top of map
engine.render.draw_rect(sign.x - 6, sign.y - 6, 12, 12,
engine.render.rgb(SIGN_R, SIGN_G, SIGN_B))
-- P.3.6: draw stones
for _, s in ipairs(stones) do
engine.render.draw_rect(s.x, s.y, s.w, s.h,
engine.render.rgb(s.color[1], s.color[2], s.color[3]))
end
-- draw player on top of map/stones
local p = player.position()
local sz = player.size()
engine.render.draw_rect(p.x, p.y, sz.w, sz.h,
engine.render.rgb(PLAYER_R, PLAYER_G, PLAYER_B))
-- P.3.6: highlights over selected entities (world-space)
selection.render_highlights()
camera.finish()
-- P.3.6: drag-box overlay (screen-space, after camera.finish)
selection.render_drag_box()
end
function update(ctx, dt)
if input.was_action_pressed("quit") then
-- ADR-0028: ESC returns to launcher per P.3.4.
engine.switch_module("lib-sporel.launcher")
return
end
-- P.3.5: Tab toggles between follow-cam and free-cam.
if input.was_action_pressed("toggle_free_cam") then
free_cam_mode = not free_cam_mode
camera.set_pan_enabled("keys", free_cam_mode)
end
-- Player + interaction nur in follow-mode (in free-cam ist player pausiert).
if not free_cam_mode then
player.update(dt)
local p = player.position()
local s = player.size()
interaction.update(p.x + s.w / 2, p.y + s.h / 2)
end
-- Pan-Summation jeden Frame (keys nur wenn enabled; drag + edge always-on).
camera.update(dt)
selection.update(dt)
if not free_cam_mode then
-- Follow-cam: set_target jeden Frame -> drag/edge "drift" wird unsichtbar.
local p = player.position()
local s = player.size()
camera.set_target(p.x + s.w / 2, p.y + s.h / 2)
end
-- CI-conditional self-exit (unverändert aus P.0):
frames = frames + 1
if os.getenv("SPOREL_CI") == "1" and frames >= TARGET_FRAMES then
engine.exit(0)
end
end
function cleanup(ctx)
engine.print("spine-prototype cleanup: total_frames=" .. frames)
end