feat(client): manual exponential camera smoothing (#117)
Replace Godot built-in Camera2D smoothing with manual lerp using CAMERA_SMOOTHING_SPEED (8.0) in constants.gd — same exponential smoothing pattern as entity_renderer.gd. Teleport snap preserved via _teleport_in_progress flag. D-015 fixed-north camera lock intact. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -99,6 +99,11 @@ const DIALOGUE_MAX_WIDTH: int = 1200
|
||||
# Default camera zoom — used as fallback when get_camera_2d() returns null
|
||||
const CAMERA_DEFAULT_ZOOM: Vector2 = Vector2(2.0, 2.0)
|
||||
|
||||
# #117: Camera smoothing speed — exponential interpolation via manual lerp in main.gd.
|
||||
# Same pattern as EntityRenderer.LERP_SPEED. At 8.0: ~55% convergence after 0.1s.
|
||||
# Slightly softer than entity movement (12.0) for a touch of cinematic camera lag.
|
||||
const CAMERA_SMOOTHING_SPEED: float = 8.0
|
||||
|
||||
# #517: Implant UI font color grading — avoid pure white, project through a lens
|
||||
const IMPLANT_TEXT_COLOR: Color = Color("#E0F7FA") # Cyan-white — primary text
|
||||
const IMPLANT_TEXT_DIM: Color = Color("#9EBFC4") # Dimmed variant — secondary text
|
||||
|
||||
+16
-25
@@ -35,13 +35,10 @@ const LISTENING_FOCUS_TICKS: int = 30 # D-071: stationary ticks before Listenin
|
||||
func _ready() -> void:
|
||||
print("The Settled Reach — client initialized")
|
||||
|
||||
# Disable camera smoothing during init. Camera2D's position_smoothing
|
||||
# lerps an internal smoothed_camera_pos toward global_position each frame.
|
||||
# That smoothed position initializes at (0,0) — the Camera2D's default in
|
||||
# the .tscn. Even after we set global_position to the player coords,
|
||||
# smoothing causes the viewport to still show (0,0) on the first rendered
|
||||
# frame because the lerp hasn't converged. With smoothing OFF, the viewport
|
||||
# uses global_position directly. Re-enabled in _process() after anchor.
|
||||
# #117: Manual lerp approach — disable Godot's built-in Camera2D smoothing.
|
||||
# We lerp camera.global_position directly in _process() using CAMERA_SMOOTHING_SPEED,
|
||||
# matching entity_renderer.gd's exponential smoothing pattern. Built-in smoothing
|
||||
# would conflict because we'd be setting global_position to the target every frame.
|
||||
camera.position_smoothing_enabled = false
|
||||
|
||||
# Connect to simulation (test mode sets CONNECTED immediately)
|
||||
@@ -70,7 +67,7 @@ func _ready() -> void:
|
||||
SimBridge.connection_state_changed.connect(_on_connection_state_changed)
|
||||
|
||||
|
||||
func _process(_delta: float) -> void:
|
||||
func _process(delta: float) -> void:
|
||||
# Main game loop: poll snapshot, apply state, flush input
|
||||
var snapshot: Variant = SimBridge.poll_snapshot()
|
||||
if snapshot != null:
|
||||
@@ -159,23 +156,18 @@ func _process(_delta: float) -> void:
|
||||
_consume_conversation_ended()
|
||||
_consume_dialogue_response()
|
||||
|
||||
# Track camera to player position every frame (D-015: locked, no panning)
|
||||
# Track camera to player (D-015: locked, fixed-north).
|
||||
# #117: Manual exponential smoothing — same pattern as EntityRenderer.LERP_SPEED.
|
||||
# Teleport (flag set by _teleport_transition): snap immediately, resume lerp next frame.
|
||||
# Init: camera already snapped in _ready() or late-anchor path above.
|
||||
if _camera_anchored:
|
||||
camera.global_position = GameState.player_position * Constants.TILE_SIZE
|
||||
|
||||
# Re-enable smoothing after the first anchored frame. The frame that just
|
||||
# rendered used smoothing=OFF (correct viewport from frame one). Now we
|
||||
# turn smoothing back on and sync its internal state so subsequent frames
|
||||
# get smooth camera tracking during gameplay.
|
||||
# #501: Skip re-enable during teleport — _teleport_transition() disables
|
||||
# smoothing for a clean camera snap. Defer by one frame to avoid the
|
||||
# re-enable block in the same _process() call undoing the snap.
|
||||
if _camera_anchored and not camera.position_smoothing_enabled:
|
||||
var target := GameState.player_position * Constants.TILE_SIZE
|
||||
if _teleport_in_progress:
|
||||
camera.global_position = target
|
||||
_teleport_in_progress = false
|
||||
else:
|
||||
camera.position_smoothing_enabled = true
|
||||
camera.reset_smoothing()
|
||||
var weight := 1.0 - exp(-Constants.CAMERA_SMOOTHING_SPEED * delta)
|
||||
camera.global_position = camera.global_position.lerp(target, weight)
|
||||
|
||||
# Send queued input to simulation
|
||||
# #507: Server-bound inputs are accumulated into _pending_record_inputs across frames.
|
||||
@@ -433,10 +425,9 @@ func _detect_teleport(old_pos: Vector2, new_pos: Vector2) -> bool:
|
||||
# Clears dialogue/monologue/interaction state (server clears its side too).
|
||||
# Scoped to Gauntlet testing only — production fast-travel uses diegetic gates.
|
||||
func _teleport_transition() -> void:
|
||||
# Snap camera: disable smoothing, force re-anchor.
|
||||
# _teleport_in_progress defers smoothing re-enable by one frame so the
|
||||
# re-enable block at the bottom of _process() doesn't undo the snap.
|
||||
camera.position_smoothing_enabled = false
|
||||
# Snap camera immediately to new position. _teleport_in_progress causes
|
||||
# the lerp block in _process() to snap again on the same frame (in case
|
||||
# player_position updates after this call) and skip lerp next frame.
|
||||
camera.global_position = GameState.player_position * Constants.TILE_SIZE
|
||||
_camera_anchored = true
|
||||
_teleport_in_progress = true
|
||||
|
||||
Reference in New Issue
Block a user