fix: stabilize mouse camera dragging

This commit is contained in:
Alexander Sellite 2026-08-16 16:39:52 -04:00
parent d3272953a5
commit c4e49cdc52
3 changed files with 104 additions and 8 deletions

View file

@ -394,8 +394,11 @@ var _catch_attachment_offset: Vector3 = Vector3(0.0, 0.08, 0.04)
var _gravity: float = float(ProjectSettings.get_setting("physics/3d/default_gravity"))
var _camera_dragging: bool = false
var _camera_drag_button_held: bool = false
var _camera_input_enabled: bool = true
var _camera_drag_prior_mouse_mode: Input.MouseMode = Input.MOUSE_MODE_VISIBLE
var _camera_drag_capture_pending: bool = false
var _camera_drag_generation: int = 0
var _free_camera_active: bool = false
var _free_camera_body: CharacterBody3D
var _free_camera: Camera3D
@ -820,9 +823,6 @@ func _process(delta: float) -> void:
)
return
if _camera_dragging and not Input.is_action_pressed("camera_drag"):
_set_camera_dragging(false)
if _is_camera_input_enabled():
var stick: Vector2 = _get_controller_camera_stick()
if stick.length() > controller_camera_deadzone:
@ -1259,15 +1259,26 @@ func _input(event: InputEvent) -> void:
return
var mouse_button := event as InputEventMouseButton
if mouse_button != null and event.is_action("camera_drag"):
_camera_drag_button_held = mouse_button.pressed
_set_camera_dragging(mouse_button.pressed)
return
var mouse_motion := event as InputEventMouseMotion
if mouse_motion == null or not _camera_dragging:
return
if _free_camera_active:
_rotate_free_camera(mouse_motion.relative * mouse_sensitivity)
_rotate_free_camera(mouse_motion.screen_relative * mouse_sensitivity)
else:
_rotate_camera(mouse_motion.relative * mouse_sensitivity)
_rotate_camera(mouse_motion.screen_relative * mouse_sensitivity)
_request_camera_drag_capture()
func _notification(what: int) -> void:
# A captured pointer can miss its release when the application loses focus.
# Normal camera drags are ended by the matching mouse-button event; polling
# InputMap here is intentionally avoided because changing mouse mode can
# transiently clear the action and release an otherwise active drag.
if what == NOTIFICATION_APPLICATION_FOCUS_OUT and _camera_dragging:
_set_camera_dragging(false)
func _unhandled_input(event: InputEvent) -> void:
@ -1321,11 +1332,38 @@ func _set_camera_dragging(active: bool) -> void:
if _camera_dragging == active:
return
_camera_dragging = active
_camera_drag_generation += 1
_camera_drag_capture_pending = false
if active:
_camera_drag_prior_mouse_mode = Input.mouse_mode
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
elif Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = _camera_drag_prior_mouse_mode
else:
_camera_drag_button_held = false
if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
Input.mouse_mode = _camera_drag_prior_mouse_mode
func _request_camera_drag_capture() -> void:
if (
_camera_drag_capture_pending
or Input.mouse_mode == Input.MOUSE_MODE_CAPTURED
):
return
_camera_drag_capture_pending = true
_capture_camera_drag_pointer.call_deferred(_camera_drag_generation)
func _capture_camera_drag_pointer(generation: int) -> void:
if generation != _camera_drag_generation:
return
_camera_drag_capture_pending = false
if (
not _camera_dragging
or not local_control_enabled
or not _is_camera_input_enabled()
or not _camera_drag_button_held
):
return
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
func _get_network_aware_speed() -> float:

View file

@ -0,0 +1,57 @@
extends SceneTree
const PlayerScene: PackedScene = preload("res://player/player.tscn")
func _initialize() -> void:
call_deferred("_run")
func _run() -> void:
root.size = Vector2i(1280, 720)
var player := PlayerScene.instantiate() as Player
root.add_child(player)
await process_frame
player.local_control_enabled = true
player.set_camera_input_enabled(true)
Input.mouse_mode = Input.MOUSE_MODE_VISIBLE
var press := InputEventMouseButton.new()
press.button_index = MOUSE_BUTTON_RIGHT
press.button_mask = MOUSE_BUTTON_MASK_RIGHT
press.pressed = true
player.call("_input", press)
assert(bool(player.get("_camera_dragging")))
if DisplayServer.get_name() != "headless":
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
var yaw := player.get_node("%CameraYaw") as Node3D
var previous_yaw: float = yaw.rotation.y
var motion := InputEventMouseMotion.new()
motion.button_mask = MOUSE_BUTTON_MASK_RIGHT
motion.relative = Vector2(18.0, -4.0)
motion.screen_relative = motion.relative
player.call("_input", motion)
assert(not is_equal_approx(yaw.rotation.y, previous_yaw))
await process_frame
assert(bool(player.get("_camera_dragging")))
if DisplayServer.get_name() != "headless":
assert(Input.mouse_mode == Input.MOUSE_MODE_CAPTURED)
var release := InputEventMouseButton.new()
release.button_index = MOUSE_BUTTON_RIGHT
release.pressed = false
player.call("_input", release)
assert(not bool(player.get("_camera_dragging")))
if DisplayServer.get_name() != "headless":
assert(Input.mouse_mode == Input.MOUSE_MODE_VISIBLE)
player.call("_input", press)
assert(bool(player.get("_camera_dragging")))
player.notification(NOTIFICATION_APPLICATION_FOCUS_OUT)
assert(not bool(player.get("_camera_dragging")))
player.queue_free()
await process_frame
print("Camera drag validation: PASS")
quit()

View file

@ -0,0 +1 @@
uid://dku3kyarg3fxu