javascriptreactjsphaser-frameworkphaserjs

Phaser 3, pause on minimize


I am creating an animation with phaser which contains a timer.

The issue occurs when I Alt-Tab or minimize the page as the timer keeps on running but the animation stops, so the GameObjects are not in sync with the timers.

I've looked through the config class of the phaser object and couldn't find any parameter related to this.

I also tried using this.game.loop.wake() when changing visibility but it doesn't seem to be working also (it still stops the animation while I'm not on the page)

create()
{
   //[...]
   this.setupVisibilityChangeListener();
}

setupVisibilityChangeListener() {
  document.addEventListener('visibilitychange', () => {
    this.game.loop.wake();
  });
}

EDIT 1

As requested here are some additional details

The project is a react project and initialize the game inside the useEffect of a certain page with the below config

useEffect(() => {
  //[...]
  const config: Phaser.Types.Core.GameConfig = {
    type: Phaser.AUTO,
    parent: 'phaser-container',
    width: width,
    height: height,
    transparent: true,
    scene: [new RaceScene(data, isMobile)],
    physics: {
      default: 'arcade',
      arcade: {
        debug: false,
        fps: 30,
      },
    },
    fps: {
      target: 30,
      forceSetTimeOut: true,
    },
    input: {
      touch: {
        capture: false,
      },
    },
  }

  const game = new Phaser.Game(config)
  //[...]
})

I'm using Phaser 3.80.1 that I installed with npm

I use the time from the time parameter in the update function and emit the event with a React Event Emitter like seen below

import animationEventEmitter from '<path>/animation.event'

update(time: number, delta: number): void {
  //[...]
  animationEventEmitter.emit('timeUpdate', time)
  //[...]
}

And here is the simple event emitter

import { EventEmitter } from 'events'

const animationEventEmitter = new EventEmitter()

export default animationEventEmitter 

No timeout or delay is used anywhere

As suggested I tried to add the below inside the create of the scene

this.game.events.on('visible', () =>{ 
  this.game.resume() 
});

this.game.events.on('hidden', () => {
  this.game.pause()
});

But the timer kept on moving even thought I alt-tab ed while the animation stopped...


Solution

  • If you use phaser Timer functions, this should not happen since, it uses the game clock. (link to documentation)

    Are you maybe using setTimeout, setInterval or using timestamps? If so you could rewrite your code to use phaser time/timer functions.

    Short Demo [Updated]:
    (ShowCasing the timer, loosely based on official example)

    class DemoScene extends Phaser.Scene {
      timedEvent;
        text;
        image;
        pauseCount;
        
        create () {
            this.text = this.add.text(32, 32);
            this.pauseCount = 0;
    
            this.timedEvent = this.time.addEvent({ delay: 2000, callback: this.onEvent, callbackScope: this, repeat: 4 });
            
             this.game.events.on('visible', () =>{  
                 console.info('PAUSE END fired!');
                 this.pause = false;
             });
             
             this.game.events.on('hidden', () => {  
                 console.info('PAUSE START fired!');
                 this.pause = true;
                 this.pauseCount++;
             });
             
        }
    
        update (time) {
          if(this.pause){
              console.info(time)
              return
          }
          
            this.text.setText(`Event.progress: ${this.timedEvent.getProgress().toString().substr(0, 4)}\nEvent.repeatCount: ${this.timedEvent.repeatCount}\nPause Counter: ${this.pauseCount}`);
        }
    
        onEvent () {
          console.info('EVENT fired!');
        }
    }
    
    var config = {
        width: 540,
        height: 180,
        scene: DemoScene,
    }; 
    
    new Phaser.Game(config);
    
    console.clear();
    document.body.style = 'margin:0;';
    <script src="//cdn.jsdelivr.net/npm/phaser/dist/phaser.min.js"></script>

    Tip: If possible always opt for phaser function / features, before turning to vanilla javascript.

    Update 2:

    If your timer relies on the time parameter of the update function, time wll not stand still since as mentioned before, it starts with the start of the application. A "quick" solution could be to sum the pause-time and subtract it, from the time parameter:

    create () {
        //[...]
        this.pauseTimeSum = 0; 
        this.game.events.on('visible', () =>{  
            this.pause = false;
            if ( this.pauseStart > 0 ){
                this.pauseTimeSum += Date.now() - this.pauseStart;
            }
        });
         
         this.game.events.on('hidden', () => {  
             this.pause = true;
             this.pauseStart = Date.now();
         });         
    }
    
    update(time: number, delta: number): void {
      //[...]
      animationEventEmitter.emit('timeUpdate', time - this.pauseTimeSum)
      //[...]
    }
    

    It's not nice but it is a quick & easy solution, if you need to use the time parameter.