From ee6e82e5ebc27bc52e0892f6825a2a4789374e64 Mon Sep 17 00:00:00 2001 From: Wickedness Date: Fri, 19 Jun 2026 16:07:20 +0900 Subject: [PATCH] Smooth map edge scrolling --- scripts/scenes/battle_scene.gd | 61 +++++++++++++++++++++++------- tools/smoke_chapter_one_polish.gd | 62 +++++++++++++++++++++++++++++++ 2 files changed, 109 insertions(+), 14 deletions(-) diff --git a/scripts/scenes/battle_scene.gd b/scripts/scenes/battle_scene.gd index f499c4a..56419e9 100644 --- a/scripts/scenes/battle_scene.gd +++ b/scripts/scenes/battle_scene.gd @@ -47,8 +47,9 @@ const UI_SFX_VOLUME_DB := -8.0 const FLOATING_TEXT_LIFETIME := 1.0 const FLOATING_TEXT_RISE := 42.0 const OBJECTIVE_NOTICE_DURATION := 2.6 -const EDGE_SCROLL_MARGIN := 34.0 -const EDGE_SCROLL_SPEED := 360.0 +const EDGE_SCROLL_MARGIN := 48.0 +const EDGE_SCROLL_MIN_SPEED := 80.0 +const EDGE_SCROLL_MAX_SPEED := 430.0 const UNIT_MOVE_ANIMATION_DURATION := 0.18 const TARGET_PREVIEW_BADGE_SIZE := Vector2(104, 22) const LOW_HP_WARNING_RATIO := 0.35 @@ -4316,26 +4317,58 @@ func _clamped_board_scroll_offset(offset: Vector2) -> Vector2: return Vector2(clampf(offset.x, min_x, 0.0), clampf(offset.y, min_y, 0.0)) +func _edge_scroll_velocity_for_position(mouse_position: Vector2, view_rect: Rect2) -> Vector2: + if not view_rect.has_point(mouse_position): + return Vector2.ZERO + var velocity := Vector2( + _edge_scroll_axis_velocity(mouse_position.x, view_rect.position.x, view_rect.end.x, 1.0), + _edge_scroll_axis_velocity(mouse_position.y, view_rect.position.y, view_rect.end.y, 1.0) + ) + return _capped_edge_scroll_velocity(velocity) + + +func _capped_edge_scroll_velocity(velocity: Vector2) -> Vector2: + if velocity == Vector2.ZERO or velocity.length() <= EDGE_SCROLL_MAX_SPEED: + return velocity + return velocity.normalized() * EDGE_SCROLL_MAX_SPEED + + +func _edge_scroll_axis_velocity(position: float, start: float, end: float, direction_multiplier: float) -> float: + var left_pressure := _edge_scroll_pressure(start + EDGE_SCROLL_MARGIN - position) + if left_pressure > 0.0: + return _edge_scroll_speed_for_pressure(left_pressure) * direction_multiplier + var right_pressure := _edge_scroll_pressure(position - (end - EDGE_SCROLL_MARGIN)) + if right_pressure > 0.0: + return -_edge_scroll_speed_for_pressure(right_pressure) * direction_multiplier + return 0.0 + + +func _edge_scroll_pressure(distance_inside_margin: float) -> float: + return clampf(distance_inside_margin / EDGE_SCROLL_MARGIN, 0.0, 1.0) + + +func _edge_scroll_speed_for_pressure(pressure: float) -> float: + var eased := pressure * pressure * (3.0 - 2.0 * pressure) + return lerpf(EDGE_SCROLL_MIN_SPEED, EDGE_SCROLL_MAX_SPEED, eased) + + +func _edge_scroll_next_offset(current_offset: Vector2, mouse_position: Vector2, view_rect: Rect2, delta: float) -> Vector2: + var velocity := _edge_scroll_velocity_for_position(mouse_position, view_rect) + if velocity == Vector2.ZERO: + return current_offset + return _clamped_board_scroll_offset(current_offset + velocity * delta) + + func _update_edge_scroll(delta: float) -> bool: if _is_input_locked(): return false var view_rect := _map_view_rect() var mouse_position := get_viewport().get_mouse_position() - if not view_rect.has_point(mouse_position): - return false - var velocity := Vector2.ZERO - if mouse_position.x <= view_rect.position.x + EDGE_SCROLL_MARGIN: - velocity.x += EDGE_SCROLL_SPEED - elif mouse_position.x >= view_rect.end.x - EDGE_SCROLL_MARGIN: - velocity.x -= EDGE_SCROLL_SPEED - if mouse_position.y <= view_rect.position.y + EDGE_SCROLL_MARGIN: - velocity.y += EDGE_SCROLL_SPEED - elif mouse_position.y >= view_rect.end.y - EDGE_SCROLL_MARGIN: - velocity.y -= EDGE_SCROLL_SPEED + var velocity := _edge_scroll_velocity_for_position(mouse_position, view_rect) if velocity == Vector2.ZERO: return false var previous_offset := board_scroll_offset - board_scroll_offset = _clamped_board_scroll_offset(board_scroll_offset + velocity * delta) + board_scroll_offset = _edge_scroll_next_offset(board_scroll_offset, mouse_position, view_rect, delta) if previous_offset.distance_squared_to(board_scroll_offset) <= 0.01: return false var next_hover := _cell_from_screen(mouse_position) diff --git a/tools/smoke_chapter_one_polish.gd b/tools/smoke_chapter_one_polish.gd index 7345254..b36c628 100644 --- a/tools/smoke_chapter_one_polish.gd +++ b/tools/smoke_chapter_one_polish.gd @@ -86,9 +86,71 @@ func _check_edge_scroll_layout_contract(failures: Array[String]) -> void: var first_cell_screen := scene._rect_for_cell(Vector2i(0, 0)).position + Vector2(4.0, 4.0) if scene._cell_from_screen(first_cell_screen) != Vector2i(0, 0): failures.append("screen-to-cell mapping should stay stable after scrolling") + _check_edge_scroll_velocity_curve(scene, view_rect, failures) scene.free() +func _check_edge_scroll_velocity_curve(scene, view_rect: Rect2, failures: Array[String]) -> void: + for method_name in [ + "_edge_scroll_velocity_for_position", + "_capped_edge_scroll_velocity", + "_edge_scroll_axis_velocity", + "_edge_scroll_pressure", + "_edge_scroll_speed_for_pressure", + "_edge_scroll_next_offset" + ]: + if not scene.has_method(method_name): + failures.append("missing edge-scroll helper: %s" % method_name) + + var center_velocity: Vector2 = scene._edge_scroll_velocity_for_position(view_rect.get_center(), view_rect) + if center_velocity != Vector2.ZERO: + failures.append("edge scroll should be idle away from map edges: %s" % str(center_velocity)) + + var near_right: Vector2 = scene._edge_scroll_velocity_for_position(Vector2(view_rect.end.x - 36.0, view_rect.get_center().y), view_rect) + var hard_right: Vector2 = scene._edge_scroll_velocity_for_position(Vector2(view_rect.end.x - 2.0, view_rect.get_center().y), view_rect) + if near_right.x >= 0.0 or hard_right.x >= 0.0: + failures.append("right-edge scroll should move the board left: near=%s hard=%s" % [str(near_right), str(hard_right)]) + if absf(hard_right.x) <= absf(near_right.x): + failures.append("edge scroll should accelerate as the mouse reaches the outer edge") + + var near_left: Vector2 = scene._edge_scroll_velocity_for_position(Vector2(view_rect.position.x + 36.0, view_rect.get_center().y), view_rect) + var hard_left: Vector2 = scene._edge_scroll_velocity_for_position(Vector2(view_rect.position.x + 2.0, view_rect.get_center().y), view_rect) + if near_left.x <= 0.0 or hard_left.x <= 0.0: + failures.append("left-edge scroll should move the board right: near=%s hard=%s" % [str(near_left), str(hard_left)]) + if hard_left.x <= near_left.x: + failures.append("left-edge scroll should accelerate as the mouse reaches the outer edge") + + var top_left: Vector2 = scene._edge_scroll_velocity_for_position(view_rect.position + Vector2(2.0, 2.0), view_rect) + if top_left.x <= 0.0 or top_left.y <= 0.0: + failures.append("corner edge scroll should support diagonal movement: %s" % str(top_left)) + var max_edge_speed := float(scene._edge_scroll_speed_for_pressure(1.0)) + if top_left.length() > max_edge_speed + 0.01: + failures.append("corner edge scroll should cap diagonal speed: %.2f > %.2f" % [top_left.length(), max_edge_speed]) + + var outside_velocity: Vector2 = scene._edge_scroll_velocity_for_position(view_rect.position - Vector2(4.0, 4.0), view_rect) + if outside_velocity != Vector2.ZERO: + failures.append("edge scroll should ignore mouse positions outside the map view: %s" % str(outside_velocity)) + + var hard_right_position := Vector2(view_rect.end.x - 2.0, view_rect.get_center().y) + var moved_right: Vector2 = scene._edge_scroll_next_offset(Vector2.ZERO, hard_right_position, view_rect, 0.25) + if moved_right.x >= 0.0: + failures.append("edge scroll next offset should apply delta movement toward the right edge: %s" % str(moved_right)) + + var hard_left_position := Vector2(view_rect.position.x + 2.0, view_rect.get_center().y) + var min_offset: Vector2 = scene._clamped_board_scroll_offset(Vector2(-9999.0, -9999.0)) + var moved_left: Vector2 = scene._edge_scroll_next_offset(min_offset, hard_left_position, view_rect, 0.25) + if moved_left.x <= min_offset.x: + failures.append("edge scroll next offset should recover from the left clamp toward the map origin: %s -> %s" % [str(min_offset), str(moved_left)]) + + var clamped_at_origin: Vector2 = scene._edge_scroll_next_offset(Vector2.ZERO, hard_left_position, view_rect, 0.25) + if clamped_at_origin != Vector2.ZERO: + failures.append("edge scroll next offset should not exceed the origin clamp: %s" % str(clamped_at_origin)) + + var clamped_at_far_edge: Vector2 = scene._edge_scroll_next_offset(min_offset, hard_right_position, view_rect, 0.25) + if clamped_at_far_edge.x < min_offset.x - 0.01: + failures.append("edge scroll next offset should not exceed the far clamp: %s below %s" % [str(clamped_at_far_edge), str(min_offset)]) + + func _check_readability_contract(failures: Array[String]) -> void: var scene = BattleSceneScript.new() scene._create_hud()