0

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.

Jon
  • 305
  • 3
  • 20
  • 45
  • The problem also seems to appear on desktop when devtools are open, until they're closed or the browser is resized. – Jon Feb 26 '22 at 18:28
  • I also added the resize function to postBoot in the gameConfig and it still has the same issue. The issue also occurs on my physical phone (Note 20 Ultra - Android 12) – Jon Feb 27 '22 at 21:34
  • could you already checkout my answer? does it work for you, or am I missing something? – winner_joiner Mar 04 '22 at 08:06
  • @winner_joiner sorry I've been swamped, I'll get to it by Sunday, sorry! – Jon Mar 05 '22 at 01:14
  • No worries, I understand that. I just checkin some days later on most answers, to see if more help or details are needed. :) – winner_joiner Mar 05 '22 at 12:35

2 Answers2

1

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+, and Android Emulator Device Pixel 4 API level 30 )

Here is the part of the code which should be adapted:

    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 you need to reset the world-bound you could, just 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 reseting of the physics world-bounds are needed more the once, game.scale.once('resize',...), would have to be changed to game.scale.on('resize',...).

Side note, from the comments:
This line game.scene.scenes[0].physics.world.setBounds(0, 0, displaySize.width, displaySize.height);, works for me, and only will work if the first scene in the game is the one you want to use (and has a physics object). If you have multiple scenes and/or want to be more precise, you can access the scene with the key of the scene.
Like this: game.scene.keys['scene-key'].physics.world.setBounds(0, 0, displaySize.width, displaySize.height);

winner_joiner
  • 12,173
  • 4
  • 36
  • 61
  • It doesn't update the physics bound, and I can't find a way to do that as that's a scene property and looking at the console..log of the game variable, there's no scene.physics, physics, or ... Well, it doesn't seem to exist anywhere in the game variable. I've found another way to resize however and will be posting it as an answer. If you can find a way to make your method call physics.world.setBounds(0, 0, width, height) I'd love to use your method instead, though. For now I'll reward you the bounty when the time is up since you provided a nice solution to my exact issue described. – Jon Mar 06 '22 at 17:12
  • @Jon thank you for your feedback, I updated my answer, I'm not sure if it can work with your application. – winner_joiner Mar 06 '22 at 18:27
  • physics. world is null for that scene for me. – Jon Mar 06 '22 at 18:42
  • @jon, yes that is the problem, of this solution. In the `scenes` property all loaded scenes of the `game`, should be found, but if you load/start it later, it won't be in there. In my demo I just have one scene so it works(since `0` ist the first and only scene, and has a physics object). If you have multiple scenes, you would have to find it with the key (https://photonstorm.github.io/phaser3-docs/Phaser.Scenes.SceneManager.html#keys__anchor), or knowing the right index. – winner_joiner Mar 06 '22 at 19:00
0

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

Jon
  • 305
  • 3
  • 20
  • 45