import { CAMERA_FOLLOW_LERP, CAMERA_LERP_REFERENCE_MS, SHAKE_MAGNITUDE, SHAKE_DURATION_MS, } from '../shared/constants'; /** * Camera controller with soft follow and screen shake. * * The camera tracks a target position (the hero's screen-space position) * and applies a smooth lerp to follow. Screen shake is applied as an * additive offset that decays over time. */ export class Camera { /** Current camera position (center of view in world-screen space) */ x = 0; y = 0; /** Target position to follow */ private targetX = 0; private targetY = 0; /** Shake state */ private shakeTimeRemaining = 0; private shakeMagnitude = SHAKE_MAGNITUDE; private shakeOffsetX = 0; private shakeOffsetY = 0; /** Lerp factor (0..1) */ private lerpFactor: number; constructor(lerpFactor = CAMERA_FOLLOW_LERP) { this.lerpFactor = lerpFactor; } /** Set the target position for the camera to follow */ setTarget(x: number, y: number): void { this.targetX = x; this.targetY = y; } /** Snap the camera instantly to the target (no lerp) */ snapToTarget(): void { this.x = this.targetX; this.y = this.targetY; } /** Trigger a screen shake effect (e.g., on hit) */ shake(magnitude = SHAKE_MAGNITUDE, durationMs = SHAKE_DURATION_MS): void { this.shakeMagnitude = magnitude; this.shakeTimeRemaining = durationMs; } /** Update camera position. Call once per frame with delta time in ms. */ update(dtMs: number): void { // Exponential smoothing; same factor as legacy per-frame lerp at ~60 Hz reference. const k = 1 - Math.pow(1 - this.lerpFactor, dtMs / CAMERA_LERP_REFERENCE_MS); this.x += (this.targetX - this.x) * k; this.y += (this.targetY - this.y) * k; // Update screen shake if (this.shakeTimeRemaining > 0) { this.shakeTimeRemaining -= dtMs; const intensity = Math.max(0, this.shakeTimeRemaining / SHAKE_DURATION_MS); const mag = this.shakeMagnitude * intensity; this.shakeOffsetX = (Math.random() * 2 - 1) * mag; this.shakeOffsetY = (Math.random() * 2 - 1) * mag; } else { this.shakeOffsetX = 0; this.shakeOffsetY = 0; } } /** Get the final camera X including shake offset */ get finalX(): number { return this.x + this.shakeOffsetX; } /** Get the final camera Y including shake offset */ get finalY(): number { return this.y + this.shakeOffsetY; } /** * Apply camera transform to a PixiJS container. * The container is shifted so the camera target appears at screen center. */ applyTo(container: { x: number; y: number }, screenWidth: number, screenHeight: number): void { container.x = Math.round(screenWidth / 2 - this.finalX); container.y = Math.round(screenHeight / 2 - this.finalY); } }