This is my attempt: https://mini-game-frontend-psi.vercel.app/ My photo is 8000x4000 pixels, and my problem is that I don’t understand how to adapt it to different screen sizes.
I thought setting the initial zoom above 1 was a bad idea, but even with that, on a large screen, the photo can’t fill the entire screen space, and the background will be visible.
Ideally, the camera should be able to scroll horizontally but not vertically.
So my question is: How do I properly scale the image so that the zoom level looks approximately the same across all devices?
this is game code:
import Phaser, { Scene } from 'phaser';
import Pinch from 'phaser3-rex-plugins/plugins/input/gestures/pinch/Pinch';
import { EventBus } from '../event-bus';
import { setupAudioToggleListener } from '../events/events-toggle-audio';
import { handlePointerMove } from '../handlers/handle-pointer-move';
import { handleWheel, setupZoomControlsListener } from '../handlers/handle-wheel';
export class Game extends Scene {
chunkWidth: number;
chunkHeight: number;
chunksInRow: number;
chunksInCol: number;
loadedChunks: Record<string, boolean>;
previousCameraState: { scrollX: number; scrollY: number; zoom: number };
rexGestures: unknown;
backgroundMusic: Phaser.Sound.BaseSound | undefined;
promoClickSound: Phaser.Sound.BaseSound | undefined;
promoMissClickSound: Phaser.Sound.BaseSound | undefined;
constructor() {
super('Game');
const aspectRatio = 1;
this.chunkHeight = window.innerHeight / 5;
this.chunkWidth = this.chunkHeight * aspectRatio;
this.chunksInRow = 10;
this.chunksInCol = 5;
this.loadedChunks = {};
this.previousCameraState = { scrollX: 0, scrollY: 0, zoom: 1 };
this.backgroundMusic = undefined;
this.promoClickSound = undefined;
this.promoMissClickSound = undefined;
console.log(this.chunkHeight);
console.log(this.chunkWidth);
}
preload() {
this.load.audio('overture', ['assets/game/audio/Overture.ogg']);
this.load.audio('promo-clicked', ['assets/game/audio/promo-click.mp3']);
this.load.audio('promo-miss-clicked', ['assets/game/audio/promo-miss.mp3']);
}
create() {
const camera = this.cameras.main;
camera.setZoom(1.4);
camera.setBounds(0, 0, this.chunkWidth * this.chunksInRow, this.chunkHeight * this.chunksInCol);
camera.setZoom(Phaser.Math.Clamp(camera.zoom, 0.2, 5));
this.input.on(
'pointermove',
(pointer: Phaser.Input.Pointer) => {
console.log(pointer.event);
handlePointerMove(camera, pointer);
},
this
);
this.input.on('wheel', (pointer, gameObject, deltaX: number, deltaY: number) => {
handleWheel(camera, deltaX, deltaY);
});
this.loadVisibleChunks();
this.backgroundMusic = this.sound.add('overture', { loop: true, volume: 0.1 });
this.promoClickSound = this.sound.add('promo-clicked', { volume: 1 });
this.promoMissClickSound = this.sound.add('promo-miss-clicked', { volume: 1 });
this.backgroundMusic.play();
setupAudioToggleListener(this);
setupZoomControlsListener(camera);
const pinch = this.rexGestures.add.pinch();
pinch.on(
'pinch',
(pinch: Pinch) => {
const scaleFactor = pinch.scaleFactor;
camera.zoom *= scaleFactor;
},
this
);
this.createPromoCodes();
EventBus.emit('current-scene-ready', this);
}
update() {
const camera = this.cameras.main;
if (
camera.scrollX !== this.previousCameraState.scrollX ||
camera.scrollY !== this.previousCameraState.scrollY ||
camera.zoom !== this.previousCameraState.zoom
) {
this.previousCameraState.scrollX = camera.scrollX;
this.previousCameraState.scrollY = camera.scrollY;
this.previousCameraState.zoom = camera.zoom;
this.loadVisibleChunks();
}
}
loadVisibleChunks() {
this.load.setPath('assets/game/map-chunks');
for (let y = 0; y < this.chunksInCol; y++) {
for (let x = 0; x < this.chunksInRow; x++) {
const chunkKey = `chunk_${y}_${x}`;
this.load.image(chunkKey, `${chunkKey}.png`);
}
}
this.load.once('complete', () => {
for (let y = 0; y < this.chunksInCol; y++) {
for (let x = 0; x < this.chunksInRow; x++) {
const chunkKey = `chunk_${y}_${x}`;
const image = this.add
.image(x * this.chunkWidth, y * this.chunkHeight, chunkKey)
.setOrigin(0);
image.setScale(this.chunkWidth / image.width, this.chunkHeight / image.height);
}
}
});
this.load.start();
}
createPromoCodes() {
this.promocodes = this.add.group();
const promoLayer = this.add.layer();
const background = this.add.rectangle(
0,
0,
this.chunkWidth * this.chunksInRow,
this.chunkHeight * this.chunksInCol,
0x000000
);
background.setOrigin(0, 0);
background.setAlpha(0.5);
background.setInteractive();
background.on('pointerdown', (pointer) => {
console.log(pointer.event);
pointer.event.stopPropagation();
this.promoMissClickSound?.play();
});
for (let i = 0; i < 512; i++) {
const x = Phaser.Math.Between(0, this.chunkWidth * this.chunksInRow);
const y = Phaser.Math.Between(0, this.chunkHeight * this.chunksInCol);
const width = Phaser.Math.Between(10, 50);
const height = Phaser.Math.Between(5, 25);
const promoCode = this.add.circle(x, y, width / 10, 0x00ff00);
promoLayer.add(promoCode);
promoCode.setInteractive();
promoCode.on('pointerdown', () => {
this.promoClickSound?.play();
const code = 'PROMO' + Phaser.Math.Between(1000, 9999);
EventBus.emit('promo-code-clicked', code);
});
}
promoLayer.setDepth(1);
}
}