import { Application, Container } from 'pixi.js';
/**
* Рендерер: виртуальное «пиксельное» разрешение, растянутое целым числом на экран
* с image-rendering: pixelated — это и есть pixel-perfect для пиксель-арта.
*/
export interface RendererOptions {
/** Виртуальная ширина (например 480). */
virtualWidth: number;
/** Виртуальная высота (например 270). */
virtualHeight: number;
/** Целочисленный множитель масштаба. */
scale: number;
/** Цвет фона. */
background: number;
/** Родительский элемент для канваса. */
parent: HTMLElement;
}
export class Renderer {
readonly app: Application;
/** Корневой контейнер мира (к нему применяется камера). */
readonly worldRoot: Container;
/** Контейнер UI поверх мира (камерой не двигается). */
readonly uiRoot: Container;
readonly virtualWidth: number;
readonly virtualHeight: number;
private _scale: number;
/** Текущий целочисленный масштаб. */
get scale(): number {
return this._scale;
}
private readonly parentEl: HTMLElement;
private readonly initPromise: Promise<void>;
constructor(options: RendererOptions) {
this.virtualWidth = options.virtualWidth;
this.virtualHeight = options.virtualHeight;
this._scale = options.scale;
this.parentEl = options.parent;
this.app = new Application();
this.initPromise = this.app.init({
width: options.virtualWidth,
height: options.virtualHeight,
backgroundColor: options.background,
antialias: false,
roundPixels: true,
autoStart: false,
sharedTicker: false
});
this.worldRoot = new Container();
this.uiRoot = new Container();
}
/** Дождаться инициализации WebGL и добавить канвас на страницу. */
async setup(): Promise<void> {
await this.initPromise;
this.app.stage.addChild(this.worldRoot, this.uiRoot);
const canvas = this.app.canvas;
canvas.style.imageRendering = 'pixelated';
this.applySize();
this.parentEl.appendChild(canvas);
}
/**
* Сменить целочисленный масштаб (обычно при ресайзе окна).
* Виртуальное разрешение не меняется — меняется только CSS-размер канваса.
*/
resize(scale: number): void {
this._scale = Math.max(1, Math.floor(scale));
this.applySize();
}
private applySize(): void {
const canvas = this.app.canvas;
canvas.style.width = `${this.virtualWidth * this._scale}px`;
canvas.style.height = `${this.virtualHeight * this._scale}px`;
}
/** Рендер кадра (вызывается игровым циклом). */
render(): void {
this.app.renderer.render(this.app.stage);
}
}