// Camera/Components/AC_Camera.ts import type { AC_DebugHUD } from '#root/Debug/Components/AC_DebugHUD.ts'; import type { AC_InputDevice } from '#root/Input/Components/AC_InputDevice.ts'; import { ActorComponent } from '#root/UE/ActorComponent.ts'; import type { Float } from '#root/UE/Float.ts'; import { MathLibrary } from '#root/UE/MathLibrary.ts'; import { SystemLibrary } from '#root/UE/SystemLibrary.ts'; import { Vector } from '#root/UE/Vector.ts'; /** * Camera System Component * Deterministic camera control with smooth rotation and device-aware sensitivity * Provides precise control over camera behavior for consistent experience */ export class AC_Camera extends ActorComponent { // ════════════════════════════════════════════════════════════════════════════════════════ // FUNCTIONS // ════════════════════════════════════════════════════════════════════════════════════════ /** * Process look input and update camera rotation * @param InputDelta - Input delta (X = Yaw, Y = Pitch) * @param DeltaTime - Time since last frame * @category Input Processing */ public ProcessLookInput(InputDelta: Vector, DeltaTime: Float): void { if (this.IsInitialized) { const invertMultiplier = this.InvertYAxis ? -1.0 : 1.0; let sensitivity: Float = 0; if (SystemLibrary.IsValid(this.InputDeviceComponent)) { sensitivity = this.InputDeviceComponent.IsGamepad() ? this.GamepadSensitivity : this.MouseSensitivity; } else { sensitivity = this.MouseSensitivity; } const CalculateTargetPitch = ( targetPitch: Float, inputDeltaY: Float, deltaTime: Float ): Float => targetPitch - inputDeltaY * sensitivity * invertMultiplier * deltaTime; const CalculateTargetYaw = ( targetYaw: Float, inputDeltaX: Float, deltaTime: Float ): Float => targetYaw + inputDeltaX * sensitivity * deltaTime; this.TargetPitch = MathLibrary.ClampFloat( CalculateTargetPitch(this.TargetPitch, InputDelta.Y, DeltaTime), this.PitchMin, this.PitchMax ); this.TargetYaw = CalculateTargetYaw( this.TargetYaw, InputDelta.X, DeltaTime ); this.InputMagnitude = MathLibrary.VectorLength(InputDelta); } } /** * Update camera rotation with smooth interpolation * @param DeltaTime - Time since last frame * @category Camera Updates */ public UpdateCameraRotation(DeltaTime: Float): void { if (this.IsInitialized) { if (this.SmoothingSpeed > 0) { // Smooth interpolation to target rotation this.CurrentPitch = MathLibrary.FInterpTo( this.CurrentPitch, this.TargetPitch, DeltaTime, this.SmoothingSpeed ); this.CurrentYaw = MathLibrary.FInterpTo( this.CurrentYaw, this.TargetYaw, DeltaTime, this.SmoothingSpeed ); } else { // Instant rotation (no smoothing) this.CurrentPitch = this.TargetPitch; this.CurrentYaw = this.TargetYaw; } } } /** * Get current camera rotation for applying to SpringArm * @returns Current camera rotation values * @category Public Interface * @pure true */ public GetCameraRotation(): { Pitch: Float; Yaw: Float } { return { Pitch: this.CurrentPitch, Yaw: this.CurrentYaw, }; } /** * Check if camera is currently rotating * @returns True if there's active rotation input * @category State Queries * @pure true */ public IsCameraRotating(): boolean { return this.InputMagnitude > 0.01; } /** * Initialize camera system with default settings * @category System Setup */ public InitializeCameraSystem( InputDeviceRef: AC_InputDevice, DebugComponentRef: AC_DebugHUD ): void { this.InputDeviceComponent = InputDeviceRef; this.DebugHUDComponent = DebugComponentRef; this.IsInitialized = true; if (SystemLibrary.IsValid(this.DebugHUDComponent)) { this.DebugHUDComponent.AddDebugPage( this.DebugPageID, 'Camera System', 60 ); } } /** * Update debug HUD with current camera info * @category Debug */ public UpdateDebugPage(): void { if (SystemLibrary.IsValid(this.DebugHUDComponent)) { if ( this.DebugHUDComponent.ShouldUpdatePage( this.DebugPageID, SystemLibrary.GetGameTimeInSeconds() ) ) { this.DebugHUDComponent.UpdatePageContent( this.DebugPageID, `Current Device: ${SystemLibrary.IsValid(this.InputDeviceComponent) ? this.InputDeviceComponent.GetCurrentInputDevice() : 'Input Device Component Not Found'}\n` + `Sensitivity: ${SystemLibrary.IsValid(this.InputDeviceComponent) && this.InputDeviceComponent.IsGamepad() ? this.GamepadSensitivity : this.MouseSensitivity}\n` + `Pitch: ${this.GetCameraRotation().Pitch}°\n` + `Yaw: ${this.GetCameraRotation().Yaw}°\n` + `Is Rotating: ${this.IsCameraRotating() ? 'Yes' : 'No'}\n` + `Smoothing: ${this.SmoothingSpeed}\n` + `Invert Y: ${this.InvertYAxis ? 'Yes' : 'No'}` ); } } } /** * Get camera configuration and state data for testing purposes * Provides read-only access to private variables for automated tests * Only includes essential data needed for test validation * @category Debug * @returns Object containing camera settings (sensitivity, pitch limits) for test assertions */ public GetTestData(): { MouseSensitivity: Float; GamepadSensitivity: Float; PitchMin: Float; PitchMax: Float; } { return { MouseSensitivity: this.MouseSensitivity, GamepadSensitivity: this.GamepadSensitivity, PitchMin: this.PitchMin, PitchMax: this.PitchMax, }; } // ════════════════════════════════════════════════════════════════════════════════════════ // VARIABLES // ════════════════════════════════════════════════════════════════════════════════════════ /** * Mouse sensitivity multiplier for camera rotation * Higher values result in faster camera movement with mouse input * Typical range: 50.0 (slow) - 200.0 (fast) * @category Camera Config * @instanceEditable true * @default 100.0 */ private readonly MouseSensitivity: Float = 100.0; /** * Gamepad sensitivity multiplier for camera rotation * Higher than mouse sensitivity to compensate for analog stick precision * Typical range: 100.0 (slow) - 300.0 (fast) * @category Camera Config * @instanceEditable true * @default 150.0 */ private readonly GamepadSensitivity: Float = 150.0; /** * Invert vertical axis for camera rotation * When true, pushing up on input rotates camera down and vice versa * Common preference for flight-sim style controls * @category Camera Config * @instanceEditable true * @default false */ private readonly InvertYAxis: boolean = false; /** * Minimum pitch angle in degrees (looking down) * Prevents camera from rotating beyond this angle * Set to -89° to avoid gimbal lock at -90° * @category Camera Config * @instanceEditable true * @default -89.0 */ private readonly PitchMin: Float = -89.0; /** * Maximum pitch angle in degrees (looking up) * Prevents camera from rotating beyond this angle * Set to +89° to avoid gimbal lock at +90° * @category Camera Config * @instanceEditable true * @default 89.0 */ private readonly PitchMax: Float = 89.0; /** * Speed of smooth interpolation to target rotation * Higher values make camera more responsive but less smooth * Set to 0 for instant rotation without interpolation * Typical range: 10.0 (smooth) - 30.0 (responsive) * @category Camera Config * @instanceEditable true * @default 20.0 */ private readonly SmoothingSpeed: Float = 20.0; /** * Current pitch angle for rendering * Smoothly interpolates towards TargetPitch based on SmoothingSpeed * Updated every frame by UpdateCameraRotation() * @category Camera State */ private CurrentPitch: Float = 0; /** * Current yaw angle for rendering * Smoothly interpolates towards TargetYaw based on SmoothingSpeed * Updated every frame by UpdateCameraRotation() * @category Camera State */ private CurrentYaw: Float = 0; /** * Target pitch angle from player input * Updated by ProcessLookInput() based on input delta * Clamped to PitchMin/PitchMax range * @category Camera State */ private TargetPitch: Float = 0; /** * Target yaw angle from player input * Updated by ProcessLookInput() based on input delta * No clamping - can rotate freely beyond 360° * @category Camera State */ private TargetYaw: Float = 0; /** * Magnitude of current input vector * Used by IsCameraRotating() to detect active camera input * Cached to avoid recalculating VectorLength every frame * @category Camera State * @default 0.0 */ private InputMagnitude: Float = 0; /** * System initialization state flag * @category Camera State */ private IsInitialized: boolean = false; /** * Reference to input device component for device detection * Set externally, used for sensitivity adjustments * @category Components */ public InputDeviceComponent: AC_InputDevice | null = null; /** * Reference to debug HUD component for displaying camera info * Optional, used for debugging purposes * @category Components */ public DebugHUDComponent: AC_DebugHUD | null = null; /** * Debug page identifier for organizing debug output * Used by debug HUD to categorize information * @category Debug */ public readonly DebugPageID: string = 'CameraInfo'; }