Hi everyone,
I’m a webdev trying to make a small game on my freetime.
I find Phaser pretty neat until now, but I would like some advice on how to handle multiple resolutions and scrolling.
Basically what I would like to achieve is the following :
- The game view must fit in a container (maybe smaller than the browser window) of any size and still cover it all
- The aspect ratio must be maintained
- The camera must follow the player and stop scrolling when the world’s bounds are reached
The world has a fixed size that never changes (pretty small).
The framework seems to already have everything required to do what I discribed.
For the game config I use:
{
width: 1280,
height: 700,
scale: {
mode: Phaser.Scale.ENVELOP
},
...
}
Then for the camera:
this.cameras.main.startFollow(this.player);
this.cameras.main.setBounds(0, 0, this.gridSizeX * this.gridCellSize, this.gridSizeY * this.gridCellSize);
Very basic stuff, but the scale mode make the game area overflow its container.
That’s exactly what I want but the camera bounds become incorrect as demonstrated here:
Do you know a way to setup the camera to scroll until the end of the overflowing area ?
Thanks a lot!
I’ve finally found a workaround. I share it in the hope it could help someone else.
I think the scaleManager
is bugged, so I’ve set the scale mode to Phaser.Scale.NONE
and do it manually.
I’ve created a GameObject
named CanvasScaler
, that I add to my scene like this:
export default class PlayScene extends Scene {
[...]
create () {
const scaler = new CanvasScaler(this, 'CanvasScaler');
this.add.existing(scaler);
}
}
The CanvasScaler
listen for the resize
event of the ScaleManager
and re-calculate the size of the canvas and the position and size of the camera :
import * as Phaser from 'phaser';
import { proxy } from "essentials/utils/utils";
export class CanvasScaler extends Phaser.GameObjects.GameObject {
public constructor(scene: Phaser.Scene, type: string) {
super(scene, type);
this.bind();
}
private bind(): void {
this.scene.scale.on('resize', proxy(this.resizeCanvas, this));
this.resizeCanvas();
}
private resizeCanvas() {
const containerWidth = this.scene.scale.canvas.parentElement.offsetWidth + 1; // + 1 to ensure the size of the camera is not 1px short after rounding
const containerHeight = this.scene.scale.canvas.parentElement.offsetHeight + 1; // same
const gameWidth = this.scene.scale.gameSize.width;
const gameHeight = this.scene.scale.gameSize.height;
const gameRatio = gameWidth / gameHeight;
let newWidth = containerWidth;
let newHeight = containerWidth / gameRatio;
if (newHeight < containerHeight) {
newHeight = containerHeight;
newWidth = containerHeight * gameRatio;
}
const marginTop = (containerHeight - newHeight) / 2;
const marginLeft = (containerWidth - newWidth) / 2;
// Scale the canvas
this.scene.scale.canvas.style.width = Math.round(newWidth) + 'px';
this.scene.scale.canvas.style.height = Math.round(newHeight) + 'px';
// Center it into view
this.scene.scale.canvas.style.marginTop = Math.round(marginTop) + 'px';
this.scene.scale.canvas.style.marginLeft = Math.round(marginLeft) + 'px';
const scaleRatioX = marginTop ? containerWidth / gameWidth : 1;
const scaleRatioY = marginLeft ? containerHeight / gameHeight : 1;
const offsetX = Math.floor((-marginLeft) / scaleRatioY);
const offsetY = Math.floor((-marginTop) / scaleRatioX);
// Update the camera size and position
this.scene.cameras.main.setViewport(
offsetX,
offsetY,
marginLeft ? (gameWidth - (offsetX * 2)) : gameWidth,
marginTop ? (gameHeight - (offsetY * 2)) : gameHeight
);
}
}
Here is the result:
The camera’s setBounds
and startFollow
stay the same.