import { Container } from 'pixi.js';
import { Shake } from './shake';
/**
* Камера виртуального разрешения: позиция — центр взгляда в мировых (виртуальных) пикселях.
* Округляет смещение до целого пикселя, чтобы пиксель-арт не «дрожал».
* Границы прокрутки — прямоугольник мира (может начинаться с отрицательных координат,
* например у изометрической карты, «ромб» которой уходит в минус по X).
* Поддерживает тряску (addShake) — движок вызывает update(dt) каждый тик.
*/
export interface CameraBounds {
x: number;
y: number;
width: number;
height: number;
}
export class Camera {
x = 0;
y = 0;
bounds: CameraBounds | null = null;
private readonly shake = new Shake();
constructor(
private readonly viewWidth: number,
private readonly viewHeight: number
) {}
follow(targetX: number, targetY: number): void {
this.x = targetX;
this.y = targetY;
this.clamp();
}
/** Запустить толчок: амплитуда в пикселях, длительность в секундах. */
addShake(strength: number, duration: number): void {
this.shake.add(strength, duration);
}
clearShake(): void {
this.shake.clear();
}
get shaking(): boolean {
return this.shake.active;
}
/** Обновление тряски — движок вызывает каждый фиксированный шаг. */
update(dt: number): void {
this.shake.update(dt);
}
apply(container: Container): void {
const o = this.shake.offset;
const halfW = Math.floor(this.viewWidth / 2);
const halfH = Math.floor(this.viewHeight / 2);
container.position.set(
halfW - Math.round(this.x) + o.x,
halfH - Math.round(this.y) + o.y
);
}
private clamp(): void {
if (!this.bounds) return;
const halfW = this.viewWidth / 2;
const halfH = this.viewHeight / 2;
const b = this.bounds;
if (b.width > this.viewWidth) {
this.x = Math.min(Math.max(this.x, b.x + halfW), b.x + b.width - halfW);
} else {
this.x = b.x + b.width / 2;
}
if (b.height > this.viewHeight) {
this.y = Math.min(Math.max(this.y, b.y + halfH), b.y + b.height - halfH);
} else {
this.y = b.y + b.height / 2;
}
}
}