Refactor: position / size / speed leben jetzt auf einer Actor-Entity
(lib-core.actor + lib-core.composition); player_control hält nur noch
die Action-Bindings und das per-frame Update.
Removed surface (use actor.* / composition.* instead):
M.set_position / M.position / M.set_size / M.size /
M.set_speed / M.speed
Neuer Flow:
composition.define_template{id="player", properties={position,
movement_speed, sprite_w, sprite_h, ...}, ...}
local p = actor.create{template="player", properties={...}}
player_control.bind_movement(...)
player_control.update(p, dt) -- liest position/movement_speed/sprite_w/h
-- aus dem actor; AABB-Collision um center,
-- commit via actor.move oder no-op
position.x/.y wird als visual CENTER behandelt (Phase-A Konvention).
4-corner-AABB-Check verschiebt sich entsprechend.
DEPRECATED-MVP-Marker auf :99 entfernt (actor-integration realisiert).
Bumps: 0.1.0 → 0.2.0 (BREAKING, pre-1.0 minor); +actor v0.1.0 dep.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
102 lines
3.8 KiB
Lua
102 lines
3.8 KiB
Lua
-- =====================================================================
|
|
-- lib-core.player_control v0.2.0 — Input → Actor Movement + AABB-Collision
|
|
-- See: meta/docs/superpowers/specs/2026-05-09-p0-lib-player_control-design.md
|
|
-- meta/docs/superpowers/specs/2026-06-09-phase-A-...
|
|
--
|
|
-- v0.2.0 (Phase A.6) — BREAKING REFACTOR:
|
|
-- Position/size/speed leben jetzt auf einer Actor-Entity (lib-core.actor
|
|
-- + lib-core.composition); player_control hält nur noch die Action-
|
|
-- Bindings und das per-frame Update.
|
|
--
|
|
-- Caller sets up:
|
|
-- composition.define_template{id="player", properties={position,
|
|
-- movement_speed, sprite_w, sprite_h, sprite_color, ...}, ...}
|
|
-- local p = actor.create{template="player", properties={...}}
|
|
-- player_control.bind_movement("move_left", ...)
|
|
-- player_control.update(p, dt) -- jeden Frame
|
|
--
|
|
-- position.x/.y on the actor is treated as visual CENTER (per Phase-A
|
|
-- render-convention). The collision-check uses sprite_w/sprite_h
|
|
-- read from the actor's properties.
|
|
--
|
|
-- Removed in v0.2.0 (use actor.* / composition.* instead):
|
|
-- M.set_position / M.position / M.set_size / M.size /
|
|
-- M.set_speed / M.speed — alle obsolete; lese via
|
|
-- actor.position(handle) / handle:get_property("sprite_w") /
|
|
-- actor.movement_speed(handle).
|
|
--
|
|
-- DEPRECATED-MVP (unverändert):
|
|
-- - separate-axis wall-sliding (fluidity slice)
|
|
-- - sprite-rendering (lebt jetzt in render.draw_entities)
|
|
-- - click-to-move + drag-select (P.3 RTS slice)
|
|
-- - velocity/momentum (juice slice)
|
|
-- - collision against maps.walls field (when walls populated)
|
|
-- =====================================================================
|
|
|
|
local maps = require("lib-core.maps")
|
|
local input = require("lib-core.input")
|
|
local actor = require("lib-core.actor")
|
|
|
|
local actions = { left = nil, right = nil, up = nil, down = nil }
|
|
|
|
local M = {}
|
|
|
|
function M.bind_movement(left, right, up, down)
|
|
actions.left = left
|
|
actions.right = right
|
|
actions.up = up
|
|
actions.down = down
|
|
end
|
|
|
|
-- 4-corner-AABB-check: returns true iff all 4 corners of the rect
|
|
-- centered at (cx, cy) with size (w, h) sit on walkable tiles per
|
|
-- maps.is_walkable. cx, cy is the visual center (Phase-A convention).
|
|
local function can_occupy_centered(cx, cy, w, h)
|
|
local ts = maps.tile_size()
|
|
local x0 = cx - w / 2
|
|
local y0 = cy - h / 2
|
|
local corners = {
|
|
{x0, y0},
|
|
{x0 + w - 1, y0},
|
|
{x0, y0 + h - 1},
|
|
{x0 + w - 1, y0 + h - 1},
|
|
}
|
|
for _, c in ipairs(corners) do
|
|
local tx, ty = engine.spatial.pixel_to_tile(c[1], c[2], ts)
|
|
if not maps.is_walkable(tx, ty) then return false end
|
|
end
|
|
return true
|
|
end
|
|
|
|
-- Per-frame input-driven movement. Reads movement-speed + sprite-size
|
|
-- from the actor's own properties; applies 4-corner collision check
|
|
-- against the current map; commits via actor.move on pass, no-op on
|
|
-- blocked (no wall-sliding in v0.2.0).
|
|
--
|
|
-- Caller is expected to have invoked actor.create + bind_movement
|
|
-- before the first update.
|
|
function M.update(actor_handle, dt)
|
|
if actor_handle == nil then
|
|
error("player_control.update: actor_handle must not be nil")
|
|
end
|
|
if not actions.left then return end -- not bound, silent no-op
|
|
|
|
local d = input.direction(actions.left, actions.right, actions.up, actions.down)
|
|
if d.x == 0 and d.y == 0 then return end
|
|
|
|
local cx, cy = actor.position(actor_handle)
|
|
local speed = actor.movement_speed(actor_handle)
|
|
local w = actor_handle:get_property("sprite_w")
|
|
local h = actor_handle:get_property("sprite_h")
|
|
|
|
local ncx = cx + d.x * speed * dt
|
|
local ncy = cy + d.y * speed * dt
|
|
|
|
if can_occupy_centered(ncx, ncy, w, h) then
|
|
actor.move(actor_handle, ncx - cx, ncy - cy)
|
|
end
|
|
-- else: blocked, no movement (no wall-sliding in P.0/v0.2.0)
|
|
end
|
|
|
|
return M
|