Continuando con Pixi.js, vamos a ver como adaptar nuestro proyecto para trabajar con Fixed Logical Resolution y Adaptative Scaling.
Es bastante común que busquemos que nuestro juego o app sea responsive, o que se adapte al menos a los diferentes tamaños de ventana y resizes. Por ello con estas 2 soluciones vamos a trabajar en un sistema donde daremos una resolución "global de referencia", la cual luego adaptaremos al tamaño real de la ventana, calculando para ello el aspect ratio o proporciones, y ajustando al mÃnimo para no excedernos.
Esto es ideal para poder trabajar con coordenadas en pÃxeles, y que sigan funcionando perfectamente en diferentes resoluciones (si escalamos), respetando asà las ubicaciones originales de los objetos (sino tenderÃan a desacomodarse en el reajuste).
Lo primero será declarar variables que van a funcionar como una resolución de referencia. En mi caso elegà 800x1200, pero dependerá también un poco del aspect ratio final deseado.
src/GameHandler.ts
import * as PIXI from 'pixi.js';
const loadAssetsGlobally = async () => {
if ((PIXI.Assets.resolver as any).isInitialized) {
return;
}
await PIXI.Assets.init({ manifest: 'manifest.json' });
await PIXI.Assets.loadBundle('texture-assets');
};
export const assetsReadyPromise = loadAssetsGlobally();
const GAME_WIDTH = 800;
const GAME_HEIGHT = 1200;
Luego crearemos una función que se va a encargar de calcular la escala deseada, según el tamaño de la ventana actual (la cual recibirá como argumentos). Para este cálculo usará una regla de 3 simple, y ajustará basándose en el menor valor de proporción para no excederse de pantalla.
src/GameHandler.ts
export const calculateStageTransform = (rendererWidth: number, rendererHeight: number) => {
const scaleX = rendererWidth / GAME_WIDTH;
const scaleY = rendererHeight / GAME_HEIGHT;
const scale = Math.min(scaleX, scaleY);
const scaledGameWidth = GAME_WIDTH * scale;
const scaledGameHeight = GAME_HEIGHT * scale;
const x = (rendererWidth - scaledGameWidth) / 2;
const y = (rendererHeight - scaledGameHeight) / 2;
return { scale, x, y };
};
Ahora por comodidad creamos una función que va a encargarse de llamar a el cálculo anterior cada vez que la ventana se redimensione (o inclusive inicialmente):
src/GameHandler.ts
export const onResize = (app: PIXI.Application) => {
const rendererWidth = app.renderer.width;
const rendererHeight = app.renderer.height;
const { scale, x, y } = calculateStageTransform(rendererWidth, rendererHeight);
app.stage.scale.set(scale);
app.stage.x = x;
app.stage.y = y;
};
Empezamos pasando en el app.init las dimensiones de referencia:
src/PinballGame.tsx (Parte de initializeGame)
// ... después de await assetsReadyPromise;
const app = new PIXI.Application();
await app.init({
width: 800,
height: 1200,
resizeTo: containerRef.current,
background: '#111111',
autoDensity: true,
});
// ...
Vamos a usar la función de resize en varios bloques, asà que crearemos una referencia a la misma:
const onResizeRef = useRef<() => void>(() => {});
Antes de instanciar el GameHandler, forzamos a un primer resize inicial, para que todo se ajuste correctamente:
src/PinballGame.tsx (Parte de initializeGame)
// ... después de la inicialización de la app
containerRef.current?.appendChild(app.canvas);
appRef.current = app;
const listener = () => onResize(app);
onResizeRef.current = listener;
app.renderer.on('resize', listener);
onResize(app);
const gameHandler = new GameHandler(app);
gameHandler.init();
gameHandlerRef.current = gameHandler;
// ...
No olvidemos de limpiar correctamente en el destroy:
src/PinballGame.tsx (Parte de return)
// ... dentro del return de useEffect
if (appRef.current && onResizeRef.current) {
appRef.current.renderer.off('resize', onResizeRef.current);
}
if (gameHandlerRef.current) {
gameHandlerRef.current.destroy();
gameHandlerRef.current = null;
}
if (appRef.current) {
appRef.current.destroy(true, { children: true, context: true });
appRef.current = null;
}
// ...
Excelente. Esto queda fenomenal para el stage, que es nuestro componente principal. Pero vamos a necesitar algo similar para nuestros Sprites dentro del juego, los cuales he decidido manejar en GameHandler.ts. Asà que allÃ, vamos a empezar declarando nuevamente la resolucion de referencia:
src/GameHandler.ts (Dentro de GameHandler class)
export class GameHandler {
private app: PIXI.Application;
private updateFn: (ticker: PIXI.Ticker) => void;
private readonly GAME_W = 800;
private readonly GAME_H = 1200;
// ...
En este caso lo importante es solo la escala, asà que creamos la siguiente función para obtener el valor según la proporción:
src/GameHandler.ts (Dentro de GameHandler class)
// ...
private calculateAssetScale(asset: PIXI.Sprite): number {
const scaleXToLogical = this.GAME_W / asset.width;
const scaleYToLogical = this.GAME_H / asset.height;
return Math.min(scaleXToLogical, scaleYToLogical);
}
// ...
Acá un ejemplo de como lo aplicarÃamos:
src/GameHandler.ts (Dentro de setupStage method)
// ...
private setupStage() {
const background = PIXI.Sprite.from('board1_texture');
const scaleToFit = this.calculateAssetScale(background);
background.scale.set(scaleToFit);
background.anchor.set(0.5);
background.x = this.GAME_W / 2;
background.y = this.GAME_H / 2;
this.app.stage.addChild(background);
}
// ...
Codigo descargable aquÃ:

0 Comentarios