1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889 |
- extends Camera3D
- const MAX_HEIGHT = 2.0
- const MIN_HEIGHT = 0.0
- @export var min_distance := 0.5
- @export var max_distance := 3.5
- @export var angle_v_adjust := 0.0
- @export var autoturn_ray_aperture := 25.0
- @export var autoturn_speed := 50.0
- var collision_exception: Array[RID] = []
- func _ready():
- # Find collision exceptions for ray.
- var node: Node = self
- while is_instance_valid(node):
- if node is RigidBody3D:
- collision_exception.append(node.get_rid())
- break
- else:
- node = node.get_parent()
- set_physics_process(true)
- # This detaches the camera transform from the parent spatial node.
- set_as_top_level(true)
- func _physics_process(delta: float):
- var target := (get_parent() as Node3D).get_global_transform().origin
- var pos := get_global_transform().origin
- var difference := pos - target
- # Regular delta follow.
- # Check ranges.
- if difference.length() < min_distance:
- difference = difference.normalized() * min_distance
- elif difference.length() > max_distance:
- difference = difference.normalized() * max_distance
- # Check upper and lower height.
- difference.y = clamp(difference.y, MIN_HEIGHT, MAX_HEIGHT)
- # Check autoturn.
- var ds := PhysicsServer3D.space_get_direct_state(get_world_3d().get_space())
- var col_left = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
- target,
- target + Basis(Vector3.UP, deg_to_rad(autoturn_ray_aperture)) * (difference),
- 0xFFFFFFFF,
- collision_exception
- ))
- var col = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
- target,
- target + difference,
- 0xFFFFFFFF,
- collision_exception
- ))
- var col_right = ds.intersect_ray(PhysicsRayQueryParameters3D.create(
- target,
- target + Basis(Vector3.UP, deg_to_rad(-autoturn_ray_aperture)) * (difference),
- 0xFFFFFFFF,
- collision_exception
- ))
- if not col.is_empty():
- # If main ray was occluded, get camera closer, this is the worst case scenario.
- difference = col.position - target
- elif not col_left.is_empty() and col_right.is_empty():
- # If only left ray is occluded, turn the camera around to the right.
- difference = Basis(Vector3.UP, deg_to_rad(-delta * (autoturn_speed))) * difference
- elif col_left.is_empty() and not col_right.is_empty():
- # If only right ray is occluded, turn the camera around to the left.
- difference = Basis(Vector3.UP, deg_to_rad(delta * autoturn_speed)) * difference
- # Do nothing otherwise, left and right are occluded but center is not, so do not autoturn.
- # Apply lookat.
- if difference.is_zero_approx():
- difference = (pos - target).normalized() * 0.0001
- pos = target + difference
- look_at_from_position(pos, target, Vector3.UP)
- # Turn a little up or down.
- transform.basis = Basis(transform.basis[0], deg_to_rad(angle_v_adjust)) * transform.basis
|