Guest User

Untitled

a guest
Sep 10th, 2024
70
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. extends Node3D
  2.  
  3. @export_group("Nodes")
  4. @export var character: Node3D = null
  5. @export var camera: Node3D = null
  6. @export var spring_arm: Node3D = null
  7.  
  8. @export_group("Settings")
  9. @export_subgroup("Sensitivity")
  10. @export_range(1.0, 100.00, 0.1) var mouse_sensitivity: float = 1.309090 ## The aiming sensitivity for mice.
  11. @export_range(1.0, 10.0, 0.1) var stick_sensitivity: float = 5.0 ## The aiming sensitivity for gamepad sticks.
  12. @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.
  13.  
  14. @export_subgroup("Pitch Clamp")
  15. @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.
  16. @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.
  17.  
  18.  
  19. func _ready() -> void:
  20.     Input.set_use_accumulated_input(false) # Disabling input accumulation increases the responsiveness and precision of inputs, at the cost of increased CPU usage.
  21.  
  22.  
  23. func _unhandled_input(event: InputEvent) -> void:
  24.     # If the mouse is not currently captured we can left click to capture the mouse and enter "aiming" mode.
  25.     if Input.mouse_mode != Input.MOUSE_MODE_CAPTURED:
  26.         if event is InputEventMouseButton:
  27.             if event.button_index == 1:
  28.                 Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  29.  
  30.         return # Input processing can be very expensive, so we return from the function when it is not needed to save performance.
  31.  
  32.     # If the mouse is currently captured we can press a key to exit aiming mode and release the mouse.
  33.     if event is InputEventKey:
  34.         if event.is_action_pressed("ui_cancel"):
  35.             Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  36.            
  37.         return # We return here for the same reason as above.
  38.  
  39.     # This handles the aiming motion by calling the mouse_look function whenever the mouse is moving.
  40.     if event is InputEventMouseMotion:
  41.         mouse_look(event)
  42.  
  43.  
  44. # Handles aim look with a mouse.
  45. func mouse_look(event: InputEventMouseMotion) -> void:
  46.     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.
  47.     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.
  48.     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.
  49.     var sensitivity_multiplier: float = (mouse_sensitivity * 0.00038397243458548043006658879114174)
  50.  
  51.     motion *= sensitivity_multiplier
  52.     #motion *= degrees_per_unit
  53.  
  54.     add_yaw(motion.x)
  55.     add_pitch(motion.y)
  56.     clamp_pitch()
  57.  
  58.  
  59. # Handles aim look with a gamepad stick.
  60. func stick_look(delta: float) -> void:
  61.     var input_direction: Vector2 = Input.get_vector("look_left","look_right","look_up","look_down", stick_deadzone)
  62.     var motion: Vector2 = Vector2.ZERO
  63.     var sensitivity_multiplier: float = 10.0 # Gamepad sticks tend to aim very slowly so we need a multiplier to make stick aiming more reasonable.
  64.  
  65.     if input_direction.length() < motion.length():
  66.         motion = input_direction
  67.     else:
  68.         motion = motion.lerp(input_direction, (5.0 * delta))
  69.  
  70.     motion *= stick_sensitivity
  71.     motion *= sensitivity_multiplier
  72.  
  73.     add_yaw(motion.x)
  74.     add_pitch(motion.y)
  75.     clamp_pitch()
  76.  
  77.  
  78. # To avoid gimbal lock we need two functions to handle camera rotation, one for yaw and one for pitch.
  79.  
  80. # Rotates the camera around the local Y axis by a given amount (in degrees) to achieve yaw.
  81. func add_yaw(amount: float) -> void: # The amount is determined by aim movement when calling this function during mouse_look or stick_look.
  82.     if is_zero_approx(amount): # If no movement is detected we stop rotating in that direction.
  83.         return
  84.  
  85.     #character.rotate_object_local(Vector3.DOWN, deg_to_rad(amount))
  86.     #character.orthonormalize() # We orthonormalize the node to prevent its transform from deteriorating (successive operations on transforms lose precision due to floating point errors).
  87.  
  88.     camera.rotation.y -= (deg_to_rad(amount))
  89.     camera.orthonormalize() # We orthonormalize the node to prevent its transform from deteriorating (successive operations on transforms lose precision due to floating point errors).
  90.  
  91. # Rotates the head/camera rig around the local X axis by a given amount (in degrees) to achieve pitch.
  92. func add_pitch(amount: float) -> void:
  93.     if is_zero_approx(amount):
  94.         return
  95.  
  96.     camera.rotate_object_local(Vector3.LEFT, deg_to_rad(amount))
  97.     camera.orthonormalize()
  98.  
  99.  
  100. # Clamps the pitch between min_pitch and max_pitch.
  101. func clamp_pitch() -> void:
  102.     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.
  103.         return
  104.  
  105.     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.
  106.     camera.orthonormalize()
  107.  
  108.  
  109. func _target_lock() -> void:
  110.     pass
  111.  
  112. # Called every frame. 'delta' is the elapsed time since the previous frame.
  113. func _process(delta: float) -> void:
  114.     stick_look(delta)
Advertisement
Add Comment
Please, Sign In to add comment