324 lines
10 KiB
TypeScript
324 lines
10 KiB
TypeScript
// 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';
|
|
}
|