Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- extends Node3D
- @export_group("Nodes")
- @export var character: Node3D = null
- @export var camera: Node3D = null
- @export var spring_arm: Node3D = null
- @export_group("Settings")
- @export_subgroup("Sensitivity")
- @export_range(1.0, 100.00, 0.1) var mouse_sensitivity: float = 1.309090 ## The aiming sensitivity for mice.
- @export_range(1.0, 10.0, 0.1) var stick_sensitivity: float = 5.0 ## The aiming sensitivity for gamepad sticks.
- @export_range(0.1, 0.9, 0.1) var stick_deadzone: float = 0.1 ## Determines the amount of input that is ignored when moving a gamepad stick.
- @export_subgroup("Pitch Clamp")
- @export_range(0.0, 89.0, 0.1) var max_pitch : float = 60.0 ## Maximum pitch in degrees. Values higher than 89 can cause gimbal lock.
- @export_range(-89.0, 0, 0.1) var min_pitch : float = -60.0 ## Minimum pitch in degrees. Values lower than 89 can cause gimbal lock.
- func _ready() -> void:
- Input.set_use_accumulated_input(false) # Disabling input accumulation increases the responsiveness and precision of inputs, at the cost of increased CPU usage.
- func _unhandled_input(event: InputEvent) -> void:
- # If the mouse is not currently captured we can left click to capture the mouse and enter "aiming" mode.
- if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
- if event is InputEventMouseButton:
- if event.button_index == 1:
- Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
- return # Input processing can be very expensive, so we return from the function when it is not needed to save performance.
- # If the mouse is currently captured we can press a key to exit aiming mode and release the mouse.
- if event is InputEventKey:
- if event.is_action_pressed("ui_cancel"):
- Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
- return # We return here for the same reason as above.
- # This handles the aiming motion by calling the mouse_look function whenever the mouse is moving.
- if event is InputEventMouseMotion:
- mouse_look(event)
- # Handles aim look with a mouse.
- func mouse_look(event: InputEventMouseMotion) -> void:
- var viewport_transform: Transform2D = get_tree().root.get_final_transform() # Window scaling or stretching can affect aiming sensitivity, so we need another variable to cancel out that effect.
- var motion: Vector2 = event.xformed_by(viewport_transform).screen_relative # This variable is used to drive the actual aiming movement. It also transforms input events by the main window's final transform of the viewport to keep aiming sensitivity consistent.
- var degrees_per_unit: float = 0.001 # Because modern mice have such high DPI values we need another variable to multiply the motion so that aiming sensitivity is flexible. 0.001 corresponds to 0.1% of a degree per unit.
- var sensitivity_multiplier: float = (mouse_sensitivity * 0.00038397243458548043006658879114174)
- motion *= sensitivity_multiplier
- #motion *= degrees_per_unit
- add_yaw(motion.x)
- add_pitch(motion.y)
- clamp_pitch()
- # Handles aim look with a gamepad stick.
- func stick_look(delta: float) -> void:
- var input_direction: Vector2 = Input.get_vector("look_left","look_right","look_up","look_down", stick_deadzone)
- var motion: Vector2 = Vector2.ZERO
- var sensitivity_multiplier: float = 10.0 # Gamepad sticks tend to aim very slowly so we need a multiplier to make stick aiming more reasonable.
- if input_direction.length() < motion.length():
- motion = input_direction
- else:
- motion = motion.lerp(input_direction, (5.0 * delta))
- motion *= stick_sensitivity
- motion *= sensitivity_multiplier
- add_yaw(motion.x)
- add_pitch(motion.y)
- clamp_pitch()
- # To avoid gimbal lock we need two functions to handle camera rotation, one for yaw and one for pitch.
- # Rotates the camera around the local Y axis by a given amount (in degrees) to achieve yaw.
- func add_yaw(amount: float) -> void: # The amount is determined by aim movement when calling this function during mouse_look or stick_look.
- if is_zero_approx(amount): # If no movement is detected we stop rotating in that direction.
- return
- #character.rotate_object_local(Vector3.DOWN, deg_to_rad(amount))
- #character.orthonormalize() # We orthonormalize the node to prevent its transform from deteriorating (successive operations on transforms lose precision due to floating point errors).
- camera.rotation.y -= (deg_to_rad(amount))
- camera.orthonormalize() # We orthonormalize the node to prevent its transform from deteriorating (successive operations on transforms lose precision due to floating point errors).
- # Rotates the head/camera rig around the local X axis by a given amount (in degrees) to achieve pitch.
- func add_pitch(amount: float) -> void:
- if is_zero_approx(amount):
- return
- camera.rotate_object_local(Vector3.LEFT, deg_to_rad(amount))
- camera.orthonormalize()
- # Clamps the pitch between min_pitch and max_pitch.
- func clamp_pitch() -> void:
- if camera.rotation.x > deg_to_rad(min_pitch) and camera.rotation.x < deg_to_rad(max_pitch): # So long as the cameras current rotation is in between the clamp values this function will not affect aiming.
- return
- camera.rotation.x = clamp(camera.rotation.x, deg_to_rad(min_pitch), deg_to_rad(max_pitch)) # This prevents the camera from rotating beyond a certain value, otherwise the camera could rotate 360 degrees around the character.
- camera.orthonormalize()
- func _target_lock() -> void:
- pass
- # Called every frame. 'delta' is the elapsed time since the previous frame.
- func _process(delta: float) -> void:
- stick_look(delta)
Advertisement
Add Comment
Please, Sign In to add comment