rimlike/scenes/world/camera_rig.gd
megaproxy 6d04c8229b Phase 5 visual polish: real 3/4 walls, bigger cabin, subtle grass
User feedback after first visual inspection: 'walls looked really thick
and there was hardly any inside space'. Five targeted fixes:

1. Wall._draw rewrite — proper within-tile 3/4 perspective. Previously the
   wall was a 16w×32h bottom-anchored rect that rose UP into the cell
   above (which encroached on the cabin interior whenever the wall faced
   south). New layout: 16×16 fitting strictly inside its own tile, with
   a lit-top 5px band + shaded-front 11px band + mortar lines + outline.
   Reads as 'a wall standing up' without overlapping adjacent cells.
   Same shape for stone and wood materials (different palettes).

2. Demo cabin expanded from 5×4 to 8×6 — interior went from 6 cells
   (closet) to 24 cells (actual room).

3. Grass-tile border darken: 0.15 → 0.04. Killed the graph-paper effect
   that dominated at 3×+ zoom. Tile boundary still readable when looking
   carefully; doesn't dominate the visual.

4. Removed Phase 1 _paint_sample_walls() seed. That 8×8 stone ring lived
   only on the Wall TileMap layer (set to visible=false in the Phase 5
   rendering pivot) — so it was an invisible path obstacle. Cleaner to
   not seed it at all now that walls render at entity level.

5. CameraRig default target_zoom: 1.0 → 2.5. At 1× on a 1280-px viewport
   the world feels sparse and pawns become 6-pixel dots. 2.5× shows ~30
   tiles wide which is the 'comfortable inspection' level.

MCP-verified visually: the cabin now reads as a proper 8×6 stone room
with raised 3/4 walls and a real 24-cell interior. Subtle grass field.
The 'commit to 3/4 perspective' rendering decision is now actually
visible (was conceptual-only before).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-10 22:30:40 +01:00

116 lines
4 KiB
GDScript
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.

extends Camera2D
## World-view camera rig — pinch-zoom, drag-pan, double-tap-centre.
## All input handled via _unhandled_input so CanvasLayer UI overlays swallow first.
## Call set_world_bounds(rect) once from the world scene after map generation.
const MIN_ZOOM: float = 0.5 # strategic / whole-map-ish
const MAX_ZOOM: float = 4.0 # close / sprite-readable
const BOUNDS_BLEED_PX: int = 32 # 2 tiles of bleed at map edge
const DOUBLE_TAP_MS: int = 300 # window for double-tap detection
const DOUBLE_TAP_DIST_PX: float = 16.0 # 1 tile radius
var target_zoom: float = 1.0
var _dragging: bool = false # desktop left-mouse drag tracking
var _last_tap_time_ms: int = 0
var _last_tap_world_pos: Vector2 = Vector2.ZERO
var _centre_tween: Tween = null
func _ready() -> void:
position_smoothing_enabled = false
# Phase 5 visual polish: default 2.5× — at 1× the world feels sparse on phone
# and pawns become 6-pixel dots; 2.5× shows ~30 tiles wide which is the
# "comfortable inspection" zoom level.
target_zoom = 2.5
zoom = Vector2(target_zoom, target_zoom)
func _process(delta: float) -> void:
# Lerp zoom toward target each render frame.
# Plain 0.15 factor is correct at 60 fps; frame-rate independent form used for safety.
var t: float = 1.0 - pow(1.0 - 0.15, delta * 60.0)
zoom = zoom.lerp(Vector2(target_zoom, target_zoom), t)
func _unhandled_input(event: InputEvent) -> void:
# --- Pinch-zoom via magnify gesture (trackpad + native touch pinch) ---
if event is InputEventMagnifyGesture:
target_zoom = clampf(target_zoom * event.factor, MIN_ZOOM, MAX_ZOOM)
return
# --- Scroll-wheel zoom (desktop) ---
if event is InputEventMouseButton:
if event.button_index == MOUSE_BUTTON_WHEEL_UP and event.pressed:
target_zoom = clampf(target_zoom * 1.1, MIN_ZOOM, MAX_ZOOM)
return
if event.button_index == MOUSE_BUTTON_WHEEL_DOWN and event.pressed:
target_zoom = clampf(target_zoom / 1.1, MIN_ZOOM, MAX_ZOOM)
return
# --- Desktop left-mouse: drag-pan tracking + double-tap detection ---
if event.button_index == MOUSE_BUTTON_LEFT:
if event.pressed:
_dragging = true
_check_double_tap(event.position)
else:
_dragging = false
return
# --- Touch drag-pan ---
if event is InputEventScreenDrag:
_apply_pan(event.relative)
return
# --- Desktop mouse-motion drag-pan (only while left mouse held) ---
if event is InputEventMouseMotion and _dragging:
_apply_pan(event.relative)
return
# --- Touch press: double-tap detection ---
if event is InputEventScreenTouch and event.pressed:
_check_double_tap(event.position)
func _apply_pan(relative: Vector2) -> void:
# zoom.x > 1 means more zoomed-in; each screen pixel = fewer world units.
position -= relative / zoom.x
func _check_double_tap(screen_pos: Vector2) -> void:
var now_ms: int = Time.get_ticks_msec()
var world_pos: Vector2 = get_canvas_transform().affine_inverse() * screen_pos
var dt: int = now_ms - _last_tap_time_ms
var dist: float = world_pos.distance_to(_last_tap_world_pos)
if dt < DOUBLE_TAP_MS and dist < DOUBLE_TAP_DIST_PX:
_centre_on(world_pos)
# Reset so a triple-tap doesn't re-trigger immediately
_last_tap_time_ms = 0
else:
_last_tap_time_ms = now_ms
_last_tap_world_pos = world_pos
func _centre_on(world_pos: Vector2) -> void:
# Kill any in-progress centre tween before starting a new one
if _centre_tween != null and _centre_tween.is_valid():
_centre_tween.kill()
_centre_tween = create_tween()
_centre_tween.set_ease(Tween.EASE_OUT)
_centre_tween.set_trans(Tween.TRANS_QUAD)
_centre_tween.tween_property(self, "position", world_pos, 0.2)
## Public API — call once from the world scene after map bounds are known.
func set_world_bounds(rect: Rect2) -> void:
limit_left = int(rect.position.x) - BOUNDS_BLEED_PX
limit_top = int(rect.position.y) - BOUNDS_BLEED_PX
limit_right = int(rect.end.x) + BOUNDS_BLEED_PX
limit_bottom = int(rect.end.y) + BOUNDS_BLEED_PX
Audit.log("camera", "bounds set: %s (bleed %d)" % [rect, BOUNDS_BLEED_PX])