mirror of
https://github.com/jcreek/CosmicClash.git
synced 2026-09-10 16:04:04 +00:00
108 lines
3.8 KiB
GDScript
108 lines
3.8 KiB
GDScript
class_name ShipCameraRig
|
|
extends Node3D
|
|
|
|
# Third-person camera for a Ship. Ball Cam keeps the ship between the camera
|
|
# and the ball; Ship Cam chases behind the ship. The game mode spawns this
|
|
# rig and assigns `target` after spawning the player's ship — ships
|
|
# themselves are camera-free (a headless/AI ship never needs one).
|
|
|
|
signal camera_mode_changed(is_ball_cam: bool)
|
|
|
|
@export var camera_distance := 8.0
|
|
@export var camera_height := 4.0
|
|
@export var camera_smoothing := 10.0
|
|
|
|
var target: Ship
|
|
var ball_cam_enabled := true
|
|
|
|
@onready var camera: Camera3D = $Camera3D
|
|
|
|
var _ball: Node3D
|
|
|
|
|
|
func _ready():
|
|
# Group lets the HUD discover the rig for the camera-mode instrument
|
|
add_to_group("ship_camera")
|
|
|
|
|
|
func _input(event):
|
|
if event.is_action_pressed("ui_accept"): # Enter key
|
|
ball_cam_enabled = !ball_cam_enabled
|
|
camera_mode_changed.emit(ball_cam_enabled)
|
|
|
|
|
|
func _physics_process(delta):
|
|
if not is_instance_valid(target):
|
|
return
|
|
var ball := _get_ball()
|
|
if ball_cam_enabled and ball:
|
|
_update_ball_cam(delta, ball)
|
|
else:
|
|
_update_ship_cam(delta)
|
|
|
|
|
|
func _get_ball() -> Node3D:
|
|
if not is_instance_valid(_ball):
|
|
_ball = get_tree().get_first_node_in_group("ball")
|
|
return _ball
|
|
|
|
|
|
func _update_ball_cam(delta, ball: Node3D):
|
|
# In ball cam, camera positions itself so the ship is between camera and ball
|
|
# Physics: Vector mathematics for 3D positioning
|
|
var ship_pos = target.global_transform.origin
|
|
var ball_pos = ball.global_transform.origin
|
|
|
|
# Calculate direction from ball to ship
|
|
# Physics: Vector subtraction and normalization
|
|
# Direction vector: d̂ = (P₂ - P₁) / |P₂ - P₁|
|
|
var ball_to_ship = (ship_pos - ball_pos).normalized()
|
|
|
|
# Position camera behind the ship relative to the ball's position
|
|
# This ensures the ship is always between the camera and ball
|
|
# Physics: Vector addition for position calculation
|
|
# P_camera = P_ship + d̂ * distance + height_offset
|
|
var camera_target_pos = ship_pos + ball_to_ship * camera_distance + Vector3.UP * camera_height
|
|
|
|
# Smoothly move camera to target position
|
|
# Physics: Linear interpolation (LERP) for smooth motion
|
|
# P(t) = P₀ + t * (P₁ - P₀), where t ∈ [0,1]
|
|
# This creates exponential approach to target position
|
|
camera.global_transform.origin = camera.global_transform.origin.lerp(camera_target_pos, camera_smoothing * delta)
|
|
|
|
# Make camera look at the ball
|
|
if camera.global_transform.origin.distance_to(ball_pos) > 0.1:
|
|
# Calculate direction to ball
|
|
var camera_pos = camera.global_transform.origin
|
|
var to_ball = (ball_pos - camera_pos).normalized()
|
|
|
|
# Create look-at transform manually
|
|
# Physics: 3D rotation matrices and basis vectors
|
|
# Uses right-hand rule: forward = -Z, up = Y, right = X
|
|
# Basis matrix transforms local coordinates to world coordinates
|
|
var camera_transform = Transform3D()
|
|
camera_transform.origin = camera_pos
|
|
camera_transform.basis = Basis.looking_at(to_ball, Vector3.UP)
|
|
|
|
# Apply the rotation smoothly
|
|
# Physics: Spherical linear interpolation (SLERP) for rotation
|
|
# SLERP provides smooth rotation along great circle on unit sphere
|
|
# Maintains constant angular velocity during interpolation
|
|
camera.global_transform.basis = camera.global_transform.basis.slerp(camera_transform.basis, camera_smoothing * delta)
|
|
|
|
|
|
func _update_ship_cam(delta):
|
|
# In ship cam, camera follows and looks in the same direction as the ship
|
|
var ship_pos = target.global_transform.origin
|
|
var ship_forward = -target.global_transform.basis.z
|
|
|
|
# Position camera behind and above the ship
|
|
var camera_target_pos = ship_pos - ship_forward * camera_distance + Vector3.UP * camera_height
|
|
|
|
# Smoothly move camera
|
|
camera.global_transform.origin = camera.global_transform.origin.lerp(camera_target_pos, camera_smoothing * delta)
|
|
|
|
# Make camera look in the same direction as the ship
|
|
var look_target = ship_pos + ship_forward * 10.0 # Look ahead of the ship
|
|
camera.look_at(look_target, Vector3.UP)
|