Files
Dawn-Godot/overworld/camera/OverworldCamera.gd
T
2026-06-11 21:07:03 -05:00

132 lines
4.5 KiB
GDScript

class_name OverworldCamera extends Camera3D
const COLLISION_MARGIN:float = 0.3
@export_category("Target")
@export var targetNode:Node3D = null
@export var pivotOffset:Vector3 = Vector3(0, 1.2, 0)
@export_category("Orbit")
@export var distance:float = 7.0
@export var minDistance:float = 1.5
@export var orbitSensitivity:float = 144.0
@export var orbitAcceleration:float = 600.0
@export var pitchMin:float = -10.0
@export var pitchMax:float = 70.0
@export_category("Mouse")
@export var mouseSensitivity:float = 0.3
@export_category("Auto-center")
@export var autoReturnDelay:float = 2.0
@export var autoReturnSpeed:float = 90.0
@export var autoReturnPitch:float = 30.0
@export_category("Collision")
@export_flags_3d_physics var collisionMask:int = 1
@export var distanceReturnSpeed:float = 5.0
var _yaw:float = 0.0
var _pitch:float = 30.0
var _currentDistance:float = 0.0
var _camVelocity:Vector2 = Vector2.ZERO
var _idleTimer:float = 0.0
var _mouseDelta:Vector2 = Vector2.ZERO
var _rightMouseHeld:bool = false
func _ready() -> void:
_currentDistance = distance
func _input(event:InputEvent) -> void:
if event is InputEventMouseButton and event.button_index == MOUSE_BUTTON_RIGHT:
_rightMouseHeld = event.pressed
Input.mouse_mode = Input.MOUSE_MODE_CAPTURED if _rightMouseHeld else Input.MOUSE_MODE_VISIBLE
elif event is InputEventMouseMotion and _rightMouseHeld:
_mouseDelta += event.relative
func _process(delta:float) -> void:
if targetNode == null:
return
var xMult:float = -1.0 if SETTINGS.invertCameraX else 1.0
var yMult:float = 1.0 if SETTINGS.invertCameraY else -1.0
var orbitInput:Vector2 = Input.get_vector(
"camera_orbit_left", "camera_orbit_right",
"camera_orbit_up", "camera_orbit_down"
)
# Controller input with speed ramp; instant stop on release
var controllerActive:bool = orbitInput.length() > 0.01
if controllerActive:
_camVelocity = _camVelocity.move_toward(
orbitInput * orbitSensitivity * SETTINGS.cameraSpeedController, orbitAcceleration * delta
)
_yaw += _camVelocity.x * delta * xMult
_pitch += _camVelocity.y * delta * yMult
else:
_camVelocity = Vector2.ZERO
# Right-click mouse drag
var mouseActive:bool = _mouseDelta.length_squared() > 0.0
if mouseActive:
_yaw += _mouseDelta.x * mouseSensitivity * SETTINGS.cameraSpeedMouse * xMult
_pitch += _mouseDelta.y * mouseSensitivity * SETTINGS.cameraSpeedMouse * yMult
_mouseDelta = Vector2.ZERO
# Auto-center behind player: only when player is moving and camera has been idle
var centerPressed:bool = Input.is_action_just_pressed("center_camera")
if centerPressed:
_idleTimer = autoReturnDelay
elif controllerActive or mouseActive:
_idleTimer = 0.0
else:
var playerBody := targetNode as CharacterBody3D
if playerBody != null and playerBody.velocity.length_squared() > 0.1:
_idleTimer += delta
else:
_idleTimer = 0.0
if _idleTimer >= autoReturnDelay:
_pitch = move_toward(_pitch, autoReturnPitch, autoReturnSpeed * delta)
var behindYaw:float = rad_to_deg(atan2(
targetNode.global_transform.basis.z.x,
targetNode.global_transform.basis.z.z
))
var yawDiff:float = fposmod(behindYaw - _yaw + 180.0, 360.0) - 180.0
_yaw += move_toward(0.0, yawDiff, autoReturnSpeed * delta)
_pitch = clamp(_pitch, pitchMin, pitchMax)
var pivot:Vector3 = targetNode.global_transform.origin + pivotOffset
var yawRad:float = deg_to_rad(_yaw)
var pitchRad:float = deg_to_rad(_pitch)
var dir:Vector3 = Vector3(
sin(yawRad) * cos(pitchRad),
sin(pitchRad),
cos(yawRad) * cos(pitchRad)
)
# Snap in instantly when terrain blocks; restore smoothly when clear
var collisionDist:float = _resolveDistance(pivot, dir)
if collisionDist < _currentDistance:
_currentDistance = collisionDist
else:
_currentDistance = move_toward(_currentDistance, distance, distanceReturnSpeed * delta)
global_transform.origin = pivot + dir * _currentDistance
look_at(pivot, Vector3.UP)
# Ray from pivot outward; returns the safe camera distance along dir.
func _resolveDistance(pivot:Vector3, dir:Vector3) -> float:
var space:PhysicsDirectSpaceState3D = get_world_3d().direct_space_state
var query:PhysicsRayQueryParameters3D = PhysicsRayQueryParameters3D.create(pivot, pivot + dir * distance)
query.collision_mask = collisionMask
var hit:Dictionary = space.intersect_ray(query)
if hit.is_empty():
return distance
var hitDist:float = (hit["position"] - pivot).length() - COLLISION_MARGIN
return maxf(hitDist, minDistance)