Smooth map edge scrolling

This commit is contained in:
2026-06-19 16:07:20 +09:00
parent 19af1359d1
commit ee6e82e5eb
2 changed files with 109 additions and 14 deletions

View File

@@ -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)

View File

@@ -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()