phaserjs / phaser

Phaser is a fun, free and fast 2D game framework for making HTML5 games for desktop and mobile web browsers, supporting Canvas and WebGL rendering.
https://phaser.io
MIT License
36.95k stars 7.08k forks source link

Game gets paused with stage.disableVisibilityChange = true #682

Closed Agnostic closed 10 years ago

Agnostic commented 10 years ago

I am trying to disable the onBlur or pause event, but when the tab is not focused, the game is not updating the player position until gets focused again, I am using socket.io to receive events from server, the events are received correctly in real time.

Here's my code: https://github.com/Agnostic/js-morpg/blob/master/public/js/app.js

photonstorm commented 10 years ago

Edit the Phaser Stage src file and in this function add in a console.log to dump out the value, like so:

Phaser.Stage.prototype.visibilityChange = function (event) {

    console.log(this.disableVisibilityChange);

    if (this.disableVisibilityChange)
    {
        return;
    }

What does it log out?

photonstorm commented 10 years ago

To be more specific, edit phaser/src/core/Stage.js to add the above (should be line 304)

Agnostic commented 10 years ago
  Phaser.Stage.prototype.visibilityChange = function (event) {
      console.log('disableVisibilityChange', this.disableVisibilityChange);
      // ...

Output: disableVisibilityChange true

photonstorm commented 10 years ago

Ok, so move the console.log down a bit, after the return where it checks the value of it. Does it still get output to the console?

Agnostic commented 10 years ago

No, also I tried this:

phaser.stage.visibilityChange = function(){};

But, it's still pausing on blur...

photonstorm commented 10 years ago

Then something else is causing it, surely? Multiple instances of the game running perhaps? If it never gets to execute the code further on in that function then then game literally can't be paused by this - it has to be something else doing it. You could always just redirect the window events to nothing?

    window.onpagehide = this._onChange;
    window.onpageshow = this._onChange;

    window.onblur = this._onChange;
    window.onfocus = this._onChange;

Just re-set all of those to empty functions?

Agnostic commented 10 years ago

Yeah, I've tried this too, and I don't know why but the sprite is moving once window gets focused:

    var noop = function(){};
    phaser.onBlur                        = noop;
    phaser.onFocus                       = noop;
    phaser.onPause                       = noop;
    phaser.onResume                      = noop;
    phaser.stage.checkVisibility         = noop;
    phaser.stage.disableVisibilityChange = true;
    phaser.stage.visibilityChange        = noop;
    phaser.focusLoss                     = noop;
    phaser.focusGain                     = noop;
photonstorm commented 10 years ago

Set them on the window object itself, not on phaser, so you're 100% sure the browser literally can't cause it.

If it still happens then my guess is that the game isn't paused at all. It's still running, it's just not updating, OR that for some reason requestAnimationFrame is set to pause when not 'current' (which it does if you swap tab for example, by design, out of the realms of Phasers control)

photonstorm commented 10 years ago

Just to follow up on that - RAF will always fire if the content is visible. But minimise the browser or swap tab and it halts. To check if the game itself is pausing or if RAF is just idle keep the game visible but swap focus to another window in another app (notepad or something), ensuring you can still see the game. If it's still updating and animating, then raf is still running and the game didn't pause.

Agnostic commented 10 years ago

Yeah, it's the RAF, now I have to refresh the game from server on focus =/

photonstorm commented 10 years ago

Use setTimeOut instead?

photonstorm commented 10 years ago

Closing this as it's a client side / browser issue.

SyraTechnologies commented 5 years ago

Per the documentation, Chrome does not call requestAnimationFrame() when a page is in the background. This behavior has been in place since 2011.

Include this polyfill to allow requestAnimationFrame to run while the tab is not active.

window.addEventListener("message", function(event)
{

    if(event.data == "tick"){
        for(var i in window.timeouts){
            if(new Date().getTime() - window.timeouts[i].started >= window.timeouts[i].delay && window.timeouts[i]){
                window.timeouts[i].func();
                delete window.timeouts[i];
            }
        }
        for(var i in window.intervals){
            var currTime = new Date().getTime();
            if(currTime - window.intervals[i].last >= window.intervals[i].delay && window.intervals[i]){
                window.intervals[i].last = currTime;
                window.intervals[i].func();
            }
        }
        window.postMessage('tick', '*');

    }
}, false);
(function(context) {
  'use strict';
    context.timeouts = [];
    context.intervals = [];
    var lastTime = new Date().getTime();
    var old = {};
    old.setTimeout = context.setTimeout;
    old.setInterval = context.setInterval;
    old.clearTimeout = context.clearTimeout;
    old.clearInterval = context.clearInterval;
    if(typeof(context.postMessage) == 'function'){
        context.setTimeout = function(fn, millis) {
            var timeout = {func: fn, delay: millis,started: new Date().getTime()};
            var l = timeouts.length;
            timeouts[l] = timeout;
            return l;
        };
        context.clearTimeout = function(cancel) {
            for(var i in timeouts){
                if(timeouts[i] == cancel)
                    timeouts.splice(cancel,1);
            }
        };
        context.setInterval = function(fn, delay ) {
            var interval = {func: fn, delay: delay,last: new Date().getTime()};
            var l = intervals.length;
            intervals[l] = interval;
            return interval;
        };
        context.clearInterval = function(cancel) {
            for(var i in intervals){
                if(intervals[i] == cancel)
                    timeouts.splice(cancel,1);
            }
        };
    }
    context.requestAnimationFrame = function( callback, element ) {
        var currTime = new Date().getTime();
        var timeToCall = Math.max( 0, 16 - ( currTime - lastTime ) );
        var id = context.setTimeout( function() {
            callback( currTime + timeToCall );
        }, timeToCall );
        lastTime = currTime + timeToCall;
        return id;
    };
    context.cancelAnimationFrame = function( id ) {
        context.clearTimeout( id );
    };
    context.addEventListener("load",function(){

        if(typeof(context.postMessage) == 'function'){
            context.postMessage('tick', '*');
        }else{
            context.setTimeout = old.setTimeout
            context.setInterval = old.setInterval
            context.clearTimeout = old.clearTimeout
            context.clearInterval = old.clearInterval
            alert("Your browser does not support postMessage. Sorry but you will be forced to default to the standard setInterval and setTimeout functions. This means you may experience pauses in your game when you navigate away from the tab it is playing in.");
        }
    });
})(this);

Next step We must make the tab into a foreground process. To do so we will run a looping white noise file.

There are a number of automatic exemptions from this throttling: Applications playing audio are considered foreground and aren’t throttled. (I use white noise turned down to where I can't hear it). https://script-it.net/assets/noise.ogg

<audio controls loop autoplay>
    <source src="https://script-it.net/assets/noise.ogg" type="audio/ogg">
    <source src="https://script-it.net/assets/noise.mp3" type="audio/mp3">
    Your browser does not support the audio element.
</audio>

Lastly we override events inside of our preload function

function preload(){
    this.game.registry.events._events.blur = [];
    this.game.registry.events._events.focus = [];
    this.game.registry.events._events.hidden = [];
    this.game.onBlur                        = ()=>noop("blur");
    this.game.onFocus                       = ()=>noop("focus");
    this.game.onPause                       = ()=>noop("pause");
    this.focusLoss                     = ()=>noop("focusloss");
    this.focusGain                     = ()=>noop("focusgain");
}