Home > OS >  Phaser in Capacitor on Android is not resizing the canvas to fit the screen with Phaser.Scale.RESIZE
Phaser in Capacitor on Android is not resizing the canvas to fit the screen with Phaser.Scale.RESIZE

Time:03-07

I'm using Ionic, React, and Capacitor to make something with Phaser - it all works perfectly in the browser on desktop, but when I try to run the Android project with an emulator - Ionic and React load just fine, but the Phaser canvas is just a thin black (The background color for the canvas) bar, about 20px tall, with the Phaser logo bouncing up and down (The default scene).

However! When I use Scale: { mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT }, the Phaser canvas fits the screen! ...Well, and then some. It fits the width properly as far as I can tell, but the canvas goes well off the screen on the bottom, so the Phaser logo bounces off the screen briefly then returns when viewing the default scene.

Game component:

import React, { useState, useRef, useEffect } from 'react';
import {
    IonContent, IonFab, IonFabButton,
    IonHeader,
    IonMenuButton,
    IonPage,
    IonTitle,
    IonToolbar,
} from '@ionic/react';
import { IonPhaser } from '@ion-phaser/react';

import playGame from '../phaser/scene';

const DEFAULT_HEIGHT = window.innerHeight;
const DEFAULT_WIDTH = window.innerWidth;

const gameContentWrapper = {
    width: '100%',
    height: '100%',
    margin: 'auto',
    padding: 0,
    overflow: 'hidden',
};

const gameCanvas = {
    width: '100%',
    height: '100%',
    margin: 'auto',
    imageRendering: 'pixelated',
};

const gameConfig = {
    type: Phaser.AUTO,
    width: DEFAULT_WIDTH,
    height: DEFAULT_HEIGHT,
    orientation: Phaser.Scale.LANDSCAPE,
    autoRound: true,
    autoFocus: true,
    // disableContextMenu: true,
    render: {
        pixelArt: true,
    },
    scale: {
        autoCenter: Phaser.Scale.CENTER_BOTH,
        mode: Phaser.Scale.RESIZE,
        // mode: Phaser.Scale.WIDTH_CONTROLS_HEIGHT,
    },
    backgroundColor: '#000',
    scene: playGame,
};

function Game() {
    const gameRef = useRef(null);
    const [initialize, setInitialize] = useState(false);
    
    const destroy = () => {
        if (gameRef.current) {
            gameRef.current.game.destroy();
        }
        setInitialize(false);
    };
    
    function resize() {
        console.log(gameRef.current);
        if (!gameRef.current) {
            return;
        }
        
        const {game} = gameRef.current;
        const canvas = gameRef.current.children[0];
        
        game.width = window.innerWidth;
        canvas.style.width = window.innerWidth;
        game.height = window.innerHeight;
        canvas.style.height = window.innerHeight;
    }
    
    useEffect(() => {
        setInitialize(true);
        window.addEventListener('resize', resize);
        window.addEventListener('load', resize);
        return () => {
            destroy();
        };
    }, []);
    
    return (
        <IonPage>
            <IonFab vertical="top" horizontal="start" slot="fixed">
                <IonFabButton>
                    <IonMenuButton auto-hide="false" menu="mainMenu" mode="md" />
                </IonFabButton>
            </IonFab>
            
            <IonContent fullscreen>
                <div style={gameContentWrapper}>
                    <IonPhaser ref={gameRef} game={gameConfig} initialize={initialize} style={gameCanvas} />
                </div>
            </IonContent>
        </IonPage>
    );
}

export default Game;

scene.js

import Phaser from 'phaser';
import logoImg from '../assets/logo.png';

class playGame extends Phaser.Scene {
    constructor() {
        super('PlayGame');
    }

    preload() {
        this.load.image('logo', logoImg);
    }

    create() {
        const logo = this.add.image(400, 150, 'logo');

        this.tweens.add({
            targets: logo,
            y: 450,
            duration: 2000,
            ease: 'Power2',
            yoyo: true,
            loop: -1,
        });
    }
}

export default playGame;

I'm just using the basic Ionic menu React starter template, with the following route added:

                    <Route path="/game" exact>
                        <Game />
                    </Route>

Is there any way to get Phaser.Scale.RESIZE to work properly on Android? WIDTH_CONTROLS_HEIGHT seems problematic and I'd really just like the canvas to fill the screen width and height regardless of aspect ratio.

I'm using the latest versions of Ionic, Capacitor, and Phaser.

If you're curious what IonPhaser is, check here - I updated it to work with the latest version of React, but other than that it's exactly the same. It's just a simple wrapper to make Phaser more React friendly.

The gameRef is a reference to the IonPhaser container, which has the game object and the canvas (children[0]) in an object - everything else is exactly the same as normal Phaser.

CodePudding user response:

I can't really explain why the initial canvas is resized to 1024 x 18 pixel, especially with all the used libraries/modules.

Nevertheless after many tests, and a few code/documentation deepdives. I found a possible Solution / workaround. ( Tested only on win10 chrome 98 , Update: also on Android Emulator Device Pixel 4 API level 30 )

Here is the part of the code which you would have to adapt:

    useEffect(() => {
        setInitialize(true);
        window.addEventListener('resize', resize);
        window.addEventListener('load',  () => {
            gameRef.current.getInstance().then( game => game.scale.refresh());
        });
    });

In the window load-event, just call the refresh function of the ScaleManager, of the Phaser.Game Object.

Main code difference explained:

and this seems to do the trick.

Disclaimer: Due to my basiclly none existing knowledge of ionic (apart what I learned for solving this problem) and my basic react know-how. I can't guarantee, that this is the best solution. But since it will be called/used only on the first load, it should be okay. I hope it works/helps.

Update:
I tested this solution on Android Emulator Device Pixel 4 API level 30, and it also works.

Update 2:

To reset the world bound you could, alter the code to, the example below. So that, when the phaser resize function is called (documentation), the world bounds are reset (with the scaled size / displaySize of):

useEffect(() => {
    setInitialize(true);
    window.addEventListener('resize', resize);
    window.addEventListener('load', async () => {
        let game = await gameRef.current.getInstance();
        game.scale.once('resize', function(gameSize, baseSize, displaySize){ 
            game.scene.scenes[0].physics.world.setBounds(0, 0, displaySize.width, displaySize.height);
        });
        game.scale.refresh();
    });

    return () => {
        destroy();
    };
}, []);

If the reseting of the world bounds are needed more the once, game.scale.once('resize',...), would have to changed to game.scale.on('resize',...).

CodePudding user response:

I've found a different way to resize the game that includes updating the physics bounds - so physics work properly with the new size.

In the game config change scale to:

scale: {
    mode: Phaser.Scale.NONE,
}

Also add your scenes:

scene: [Handler, Preload, Game],

Create a new scene, I called mine handler but you can name it whatever you want:

import Phaser from 'phaser';

export default class Handler extends Phaser.Scene {
    sceneRunning = null;
    sceneName = null;
    
    constructor() {
        super('handler');
    }
    
    create() {
        this.cameras.main.setBackgroundColor('#FFF');
        this.launchScene('preload');
    }
    
    launchScene(scene, data) {
        this.scene.start(scene, data);
        this.sceneName = scene;
        this.gameScene = this.scene.get(scene);
    }
    
    resizerListener(scene) {
        window.removeEventListener('resize', () => { this.resize(scene); });
        window.addEventListener('resize', () => { this.resize(scene); });
    }
    
    resize(scene) {
        if (scene.sceneStopped === false) {
            scene.scale.resize(window.innerWidth, window.innerHeight);
            scene.physics.world.setBounds(0, 0, window.innerWidth, window.innerHeight);
            scene.scale.updateScale();
        }
    }
}

In every scene you want to automatically resize, add the following:

In the class member definitions (Inside the top of the class):

handlerScene = null;

in preload():

        this.handlerScene = this.scene.get('handler');
        this.handlerScene.sceneRunning = 'SCENE NAME';
        this.sceneStopped = false;
        this.handlerScene.resize(this);
        this.handlerScene.resizerListener(this);

To launch a new scene:

this.sceneStopped = true;
this.scene.stop('preload');
this.handlerScene.launchScene('demo');

I based my code off of this answer

  • Related