1
\$\begingroup\$

I'm using Phaser to create a small web based game, 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 described.

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:

enter image description here

Do you know a way to setup the camera to scroll until the end of the overflowing area ?

Thanks a lot!

EDIT:

I tried to change the size of the camera to make it equal to the size of the canvas' parent container, like this:

this.cameras.main.setSize(
    this.game.canvas.parentElement.offsetWidth, 
    this.game.canvas.parentElement.offsetHeight
);

The result is:

enter image description here

In this example the size of the window is 1280x700 and the size of the container is 1024x560.

So setting the size of the camera to 1024x560 give the result above.

I'm getting confused.

Any idea?

\$\endgroup\$
4
  • \$\begingroup\$ Have you tried re-centering your view and re-initializing your camera follow after a scale change? \$\endgroup\$
    – DMGregory
    Commented Jul 8, 2020 at 11:09
  • \$\begingroup\$ Hi, I've tried (for testing purposes) to call startFollow and setBounds on each update but it didn't change anything. I don't understand what you mean by "re-centering the view", can you explain? Thanks for your help. \$\endgroup\$
    – Stnaire
    Commented Jul 8, 2020 at 11:17
  • \$\begingroup\$ Re-centering the view would mean adjusting your camera position so that it's centered on the player while respecting the walls, based on the new viewport. The problem seems to creep in when your viewport crops in so that the player is off-center even though the edge of the world is out of view. \$\endgroup\$
    – DMGregory
    Commented Jul 8, 2020 at 11:21
  • \$\begingroup\$ The problem is that I don't control the position of the camera, I let this to the framework using the startFollow method on the camera. Doing what you suggest would mean re-implementing what the framework does after calling startFollow. It may not be that difficult but the whole point of using a framework is to avoid re-implementing basic stuff like this. I wonder what I did wrong to have this behavior. \$\endgroup\$
    – Stnaire
    Commented Jul 8, 2020 at 11:41

1 Answer 1

0
\$\begingroup\$

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:

enter image description here

The camera's setBounds and startFollow stay the same.

\$\endgroup\$

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .