Replaces the inline color-only demo_tilemap with a dep on the prototype-fa-starter asset lib and its baked fa_terrain_v1 atlas. The map atlases[] alias-string is updated to fa_terrain_v1; the packed-u32 layer-tiles keep their numeric values because the baker assigned the expected IDs (1 for grass_field surface tile, 2 for stone_wall_brick wall tile). Adds maps.load_textures() call in init. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
195 lines
7.4 KiB
Lua
195 lines
7.4 KiB
Lua
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 command = require("lib-core.command")
|
|
|
|
local demo_map = maps.load("maps/demo.map.json")
|
|
maps.set_current(demo_map)
|
|
maps.load_textures(engine.module.asset_aliases())
|
|
|
|
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")
|
|
|
|
-- P.3.7: Command bindings (RMB → move selected units).
|
|
input.bind("rmb", { "mouse_right" })
|
|
|
|
command.bind_move_action("rmb")
|
|
command.bind_target_provider(function() return selection.list() end)
|
|
|
|
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/P.3.7: 6 stones as selectables + movable units (3 speed classes).
|
|
local stones = {
|
|
{ x = 64, y = 96, w = 20, h = 20, color = {120, 120, 140}, speed = 100 },
|
|
{ x = 224, y = 96, w = 20, h = 20, color = {120, 120, 140}, speed = 100 },
|
|
{ x = 96, y = 256, w = 20, h = 20, color = {130, 110, 100}, speed = 150 },
|
|
{ x = 320, y = 192, w = 20, h = 20, color = {130, 110, 100}, speed = 150 },
|
|
{ x = 192, y = 320, w = 20, h = 20, color = {140, 130, 110}, speed = 200 },
|
|
{ x = 384, y = 256, w = 20, h = 20, color = {140, 130, 110}, speed = 200 },
|
|
}
|
|
|
|
-- 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/P.3.7: Register each stone as selectable AND movable.
|
|
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)
|
|
s.cmd_handle = command.register(
|
|
function() return { x = s.x, y = s.y } end,
|
|
function(nx, ny) s.x = nx; s.y = ny end,
|
|
s.speed
|
|
)
|
|
end
|
|
engine.print(string.format("selection: %d selectables registered",
|
|
selection.count_registered()))
|
|
engine.print(string.format("command: %d movable units registered",
|
|
command.count_registered()))
|
|
end
|
|
|
|
function render(ctx)
|
|
engine.render.clear_color(engine.render.rgb(20, 20, 30))
|
|
camera.begin()
|
|
maps.draw_map_pre_entities()
|
|
-- 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()
|
|
maps.draw_map_post_entities()
|
|
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)
|
|
command.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
|