jerstopholes

Camera Control Script

Jul 8th, 2025 (edited)
155
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
  1. extends Node3D
  2.  
  3. # Variables to expose here
  4. ## Mouse Sensitivity
  5. @export var mouse_sensitivity : float = 100.0
  6. ## Minimum viewing angle (in degrees)
  7. @export_range(-90.0, 0.0, 0.1, "radians_as_degrees") var min_vertical_angle: float = -PI/2
  8. ## Maximum viewing angle (in degrees)
  9. @export_range(0.8, 90.0, 0.1, "radians_as_degrees") var max_vertical_angle: float = PI/4
  10.  
  11. ## The target the camera is to follow
  12. @export var follow_target : NodePath
  13.  
  14. # Onready vars here
  15. @onready var camera = %Camera3D
  16. @onready var marker = %Marker3D
  17. @onready var spring_arm = %SpringArm3D
  18. @onready var pivot = $"Pivot"
  19.  
  20. var can_clip : bool = false
  21. var clipping : bool = false
  22. var target : Node
  23. var target_pos : Vector3 = Vector3()
  24. var cam_offset : Vector3 = Vector3()
  25.  
  26. func _ready() -> void:
  27.     Input.mouse_mode = Input.MOUSE_MODE_CAPTURED
  28.    
  29.     if follow_target:
  30.         target = get_node(follow_target)
  31.         spring_arm.add_excluded_object(get_node(follow_target))
  32.        
  33.         cam_offset = global_position - target.global_position
  34.    
  35.     set_as_top_level(true)
  36.    
  37.     # Force physics interpolation OFF
  38.     set_physics_interpolation_mode(Node.PHYSICS_INTERPOLATION_MODE_OFF)
  39.    
  40.     # Force VSync on
  41.     DisplayServer.window_set_vsync_mode(DisplayServer.VSYNC_ENABLED)
  42.    
  43. func _unhandled_input(event: InputEvent) -> void:
  44.     # Handle rotation of the camera here
  45.     if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
  46.         _rotate_camera(event)
  47.  
  48.     # Release/capture the mouse
  49.     if event.is_action_pressed("ui_cancel"):
  50.         if Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
  51.             Input.set_mouse_mode(Input.MOUSE_MODE_VISIBLE)
  52.         else:
  53.             Input.set_mouse_mode(Input.MOUSE_MODE_CAPTURED)
  54.    
  55.     # Quick Quit (ALT + Q)
  56.     if event.is_action_pressed("quick_quit"):
  57.         get_tree().quit()
  58.  
  59.  
  60. func _process(delta):
  61.     _check_collision(delta)
  62.     _rotate_camera(InputEvent)
  63.     _follow_target(delta)
  64.  
  65. func _check_collision(delta):
  66.     var physics_tick = Engine.get_physics_interpolation_fraction()
  67.    
  68.     can_clip = spring_arm.get_hit_length() < spring_arm.spring_length
  69.    
  70.     if can_clip:
  71.         if camera.position.z > marker.position.z:
  72.             if not clipping:
  73.                 camera.position.z = lerp(camera.position.z, marker.position.z, 0.1)
  74.             else:
  75.                 camera.position.z = lerp(camera.position.z, marker.position.z, 0.0001 * delta)
  76.            
  77.         elif camera.position.z < marker.position.z:
  78.             if not clipping:
  79.                 camera.position.z = lerp(camera.position.z, marker.position.z, 0.08)
  80.             else:
  81.                 camera.position.z = lerp(camera.position.z, marker.position.z, 0.0001 * delta)
  82.        
  83.         if is_equal_approx(camera.position.z, marker.position.z):
  84.             clipping = true
  85.     else:
  86.         camera.position.z = lerp(camera.position.z, marker.position.z, 0.05)
  87.         clipping = false
  88.  
  89. func _follow_target(delta):
  90.     var tf : Transform3D = target.get_global_transform_interpolated()
  91.     var t_pos = tf.origin + cam_offset
  92.     var distance = global_position - tf.origin
  93.     target_pos = lerp(target_pos, t_pos, 1.0 - pow(0.01, delta))
  94.     global_position = target_pos
  95.  
  96. func _rotate_camera(event) -> void:
  97.     if event is InputEventMouseMotion and Input.mouse_mode == Input.MOUSE_MODE_CAPTURED:
  98.         var viewport_transform: Transform2D = get_tree().root.get_final_transform()
  99.         var motion: Vector2 = event.xformed_by(viewport_transform).relative
  100.         var degrees_per_unit: float = 0.001
  101.        
  102.         motion *= mouse_sensitivity
  103.         motion *= degrees_per_unit
  104.        
  105.         # Don't apply rotation if we're not moving the mouse
  106.         if is_zero_approx(motion.length() > 0.01):
  107.             return
  108.  
  109.         # Apply pitch
  110.         pivot.rotate_object_local(Vector3.LEFT, deg_to_rad(motion.y))
  111.    
  112.         # Apply yaw
  113.         rotate_object_local(Vector3.DOWN, deg_to_rad(motion.x))
  114.        
  115.     if Input.mouse_mode == Input.MOUSE_MODE_VISIBLE:
  116.         var input_vector = Input.get_vector("look_right", "look_left", "look_down", "look_up")
  117.        
  118.         rotate_y(input_vector.x * 0.05)
  119.         pivot.rotate_x(input_vector.y * 0.05)
  120.        
  121.     # Clamp the rotation
  122.     pivot.rotation.x = clamp(pivot.rotation.x, min_vertical_angle, max_vertical_angle)
  123.     rotation.y = wrapf(rotation.y, 0.0, TAU)
  124.    
  125.     # Orthonormalize the transforms
  126.     pivot.orthonormalize()
  127.     orthonormalize()
Advertisement
Add Comment
Please, Sign In to add comment