How to create a responsive game for any screen size with Phaser 3?
Asked Answered
M

1

17

I've been looking for a solution to be able to make my game fully responsive to any screen resolution using Phaser 3, for example:

Resize screen

This example was made with Construct 2, where by the way it is very simple to do this effect.

Does anyone know what is the best approach to achieve this using Phaser 3?

Murcia answered 14/2, 2021 at 0:5 Comment(0)
M
16

Doing research I was able to find a solution to my problem:

The key is use a parent scene to control all other child scenes, This scene is going to measure the same as the device's screen size. It will also resize the child scenes when the screen size changes, but always keeping the aspect ratio.

HandlerScene.js

export default class Handler extends Phaser.Scene {

    // Vars
    sceneRunning = null

    constructor() {
        super('handler')
    }

    create() {
        this.cameras.main.setBackgroundColor('#FFF')
        this.launchScene('preload')
    }

    launchScene(scene, data) {
        this.scene.launch(scene, data)
        this.gameScene = this.scene.get(scene)
    }

    updateResize(scene) {
        scene.scale.on('resize', this.resize, scene)

        const scaleWidth = scene.scale.gameSize.width
        const scaleHeight = scene.scale.gameSize.height

        scene.parent = new Phaser.Structs.Size(scaleWidth, scaleHeight)
        scene.sizer = new Phaser.Structs.Size(scene.width, scene.height, Phaser.Structs.Size.FIT, scene.parent)

        scene.parent.setSize(scaleWidth, scaleHeight)
        scene.sizer.setSize(scaleWidth, scaleHeight)

        this.updateCamera(scene)
    }

    resize(gameSize) {
        // 'this' means to the current scene that is running
        if (!this.sceneStopped) {
            const width = gameSize.width
            const height = gameSize.height

            this.parent.setSize(width, height)
            this.sizer.setSize(width, height)

            const camera = this.cameras.main
            const scaleX = this.sizer.width / this.game.screenBaseSize.width
            const scaleY = this.sizer.height / this.game.screenBaseSize.height

            camera.setZoom(Math.max(scaleX, scaleY))
            camera.centerOn(this.game.screenBaseSize.width / 2, this.game.screenBaseSize.height / 2)
        }
    }

    updateCamera(scene) {
        const camera = scene.cameras.main
        const scaleX = scene.sizer.width / this.game.screenBaseSize.width
        const scaleY = scene.sizer.height / this.game.screenBaseSize.height

        camera.setZoom(Math.max(scaleX, scaleY))
        camera.centerOn(this.game.screenBaseSize.width / 2, this.game.screenBaseSize.height / 2)
    }

}

In this way we can start other scenes in parallel within the parent scene.

PreloadScene.js

export default class Preload extends Phaser.Scene {

    handlerScene = null
    sceneStopped = false

    constructor() {
        super({ key: 'preload' })
    }

    preload() {
        // Images
        this.load.image('logo', 'assets/images/logo.png')   

        this.width = this.game.screenBaseSize.width
        this.height = this.game.screenBaseSize.height

        this.handlerScene = this.scene.get('handler')
        this.handlerScene.sceneRunning = 'preload'
        this.sceneStopped = false

        ...
    }

    create() {
        const { width, height } = this
        // CONFIG SCENE         
        this.handlerScene.updateResize(this)
        // CONFIG SCENE  

        // GAME OBJECTS  
        this.add.image(width / 2, height / 2, 'logo').setOrigin(.5)
        // GAME OBJECTS
    }
}

In the child scenes, the updateResize function of the parent scene must be called from the create function of each scene.

ConfigGame.js

import Handler from './scenes/handler.js'
import Preload from './scenes/preload.js'

// Aspect Ratio 16:9 - Portrait
const MAX_SIZE_WIDTH_SCREEN = 1920
const MAX_SIZE_HEIGHT_SCREEN = 1080
const MIN_SIZE_WIDTH_SCREEN = 270
const MIN_SIZE_HEIGHT_SCREEN = 480
const SIZE_WIDTH_SCREEN = 540
const SIZE_HEIGHT_SCREEN = 960

const config = {
    type: Phaser.AUTO,
    scale: {
        mode: Phaser.Scale.RESIZE,
        parent: 'game',
        width: SIZE_WIDTH_SCREEN,
        height: SIZE_HEIGHT_SCREEN,
        min: {
            width: MIN_SIZE_WIDTH_SCREEN,
            height: MIN_SIZE_HEIGHT_SCREEN
        },
        max: {
            width: MAX_SIZE_WIDTH_SCREEN,
            height: MAX_SIZE_HEIGHT_SCREEN
        }
    },
    dom: {
        createContainer: true
    },
    scene: [Handler, Preload]

}

const game = new Phaser.Game(config)

// Global

game.screenBaseSize = {
    maxWidth: MAX_SIZE_WIDTH_SCREEN,
    maxHeight: MAX_SIZE_HEIGHT_SCREEN,
    minWidth: MIN_SIZE_WIDTH_SCREEN,
    minHeight: MIN_SIZE_HEIGHT_SCREEN,
    width: SIZE_WIDTH_SCREEN,
    height: SIZE_HEIGHT_SCREEN
}

The mode: Phaser.Scale.RESIZE is very important and also a maximum and a minimum for the screen size.

My complete solution is here:

https://github.com/shimozurdo/mobile-game-base-phaser3

Explanation:

https://labs.phaser.io/edit.html?src=src/scalemanager/mobile%20game%20example.js

Murcia answered 14/2, 2021 at 16:28 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.