follow_camera.gd 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889
  1. extends Camera3D
  2. const MAX_HEIGHT = 2.0
  3. const MIN_HEIGHT = 0.0
  4. @export var min_distance := 0.5
  5. @export var max_distance := 3.5
  6. @export var angle_v_adjust := 0.0
  7. @export var autoturn_ray_aperture := 25.0
  8. @export var autoturn_speed := 50.0
  9. var collision_exception: Array[RID] = []
  10. func _ready():
  11. # Find collision exceptions for ray.
  12. var node: Node = self
  13. while is_instance_valid(node):
  14. if node is RigidBody3D:
  15. collision_exception.append(node.get_rid())
  16. break
  17. else:
  18. node = node.get_parent()
  19. set_physics_process(true)
  20. # This detaches the camera transform from the parent spatial node.
  21. set_as_top_level(true)
  22. func _physics_process(delta: float):
  23. var target := (get_parent() as Node3D).get_global_transform().origin
  24. var pos := get_global_transform().origin
  25. var difference := pos - target
  26. # Regular delta follow.
  27. # Check ranges.
  28. if difference.length() < min_distance:
  29. difference = difference.normalized() * min_distance
  30. elif difference.length() > max_distance:
  31. difference = difference.normalized() * max_distance
  32. # Check upper and lower height.
  33. difference.y = clamp(difference.y, MIN_HEIGHT, MAX_HEIGHT)
  34. # Check autoturn.
  35. var ds := PhysicsServer3D.space_get_direct_state(get_world_3d().get_space())
  36. var col_left = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
  37. target,
  38. target + Basis(Vector3.UP, deg_to_rad(autoturn_ray_aperture)) * (difference),
  39. 0xFFFFFFFF,
  40. collision_exception
  41. ))
  42. var col = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
  43. target,
  44. target + difference,
  45. 0xFFFFFFFF,
  46. collision_exception
  47. ))
  48. var col_right = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
  49. target,
  50. target + Basis(Vector3.UP, deg_to_rad(-autoturn_ray_aperture)) * (difference),
  51. 0xFFFFFFFF,
  52. collision_exception
  53. ))
  54. if not col.is_empty():
  55. # If main ray was occluded, get camera closer, this is the worst case scenario.
  56. difference = col.position - target
  57. elif not col_left.is_empty() and col_right.is_empty():
  58. # If only left ray is occluded, turn the camera around to the right.
  59. difference = Basis(Vector3.UP, deg_to_rad(-delta * (autoturn_speed))) * difference
  60. elif col_left.is_empty() and not col_right.is_empty():
  61. # If only right ray is occluded, turn the camera around to the left.
  62. difference = Basis(Vector3.UP, deg_to_rad(delta * autoturn_speed)) * difference
  63. # Do nothing otherwise, left and right are occluded but center is not, so do not autoturn.
  64. # Apply lookat.
  65. if difference.is_zero_approx():
  66. difference = (pos - target).normalized() * 0.0001
  67. pos = target + difference
  68. look_at_from_position(pos, target, Vector3.UP)
  69. # Turn a little up or down.
  70. transform.basis = Basis(transform.basis[0], deg_to_rad(angle_v_adjust)) * transform.basis