feat: Fortnite-style OTS camera + Alt free-look orbit in third person

- Third-person camera chain is now HeadPivot > OrbitPivot > ShoulderOffset
  (0.55m right, 0.12m up) > SpringArm(2.6m, 14 deg) — the character sits
  slightly left of screen centre over-the-shoulder instead of dead centre
  behind. Aim is unchanged (still the first-person camera).
- Hold Alt in third person to orbit the camera freely around the character
  with the mouse (yaw wraps, pitch clamped -69..+29 deg); the character
  keeps facing and aiming where it was. Release Alt and the camera springs
  back behind the shoulder.
- debug/orbit_capture.gd: screenshots the OTS framing, a driven orbit, and
  the spring-back for visual regression.

Co-Authored-By: Claude Fable 5 <[email protected]>
This commit is contained in:
Nicholas Butzke
2026-07-19 23:59:44 -04:00
co-authored by Claude Fable 5
parent b0f26e6dde
commit 87b8ae70df
4 changed files with 114 additions and 9 deletions
+16 -4
View File
@@ -168,15 +168,27 @@ func _ready() -> void:
func _setup_third_person_camera() -> void:
if not head_pivot or not is_instance_valid(camera):
return
# Chain: head_pivot > OrbitPivot (Alt free-look, springs back to zero)
# > ShoulderOffset (camera right of the spine — the character sits
# slightly LEFT of screen centre, Fortnite-style) > SpringArm.
var orbit := Node3D.new()
orbit.name = "OrbitPivot"
head_pivot.add_child(orbit)
var shoulder := Node3D.new()
shoulder.name = "ShoulderOffset"
shoulder.position = Vector3(0.55, 0.12, 0.0)
orbit.add_child(shoulder)
var boom := SpringArm3D.new()
boom.name = "ThirdPersonBoom"
boom.spring_length = 3.2
boom.spring_length = 2.6
boom.margin = 0.3
boom.collision_mask = 1 # environment only
boom.add_excluded_object(get_rid())
# Aim the arm up-and-back from the head so the camera sits behind/above.
boom.rotation_degrees = Vector3(20, 0, 0)
head_pivot.add_child(boom)
# Aim the arm up-and-back from the shoulder so the camera sits behind/above.
boom.rotation_degrees = Vector3(14, 0, 0)
shoulder.add_child(boom)
_tp_camera = Camera3D.new()
_tp_camera.name = "ThirdPersonCamera"