No key up when focus lost (Character keeps moving)

hey everyone,

I am working on a small project in which I open a link when the character is inside a trigger box and the space bar is pressed. This causes the window to lose focus. The odd thing is that apparently no key up event is sent? and even when the focus is regained the character keeps on jumping.

How can I prevent this? can I manually reset the isPressed / wasPressed values? Does using the onkeydown on keyup events solve this better? what is the right approach here?

link to isolated example problem: https://playcanvas.com/project/705812/overview/character-move-when-focus-lost

thanks for your help

Hi @fleity and welcome,

For the mouse, there is a mouseout event which you can use (vanilla JS event), though for keys and anything else it’s better to just detect when the window loses focus.

But in your specific case maybe you can just use wasPressed, instead of isPressed:

    if (app.keyboard.wasPressed(pc.KEY_SPACE))
        {
            window.open("https://forum.playcanvas.com/", '_blank', 'noopener,noreferrer');
        }

This event by design will be triggered only once on key up.

Let me know if this works out for you.

argh i wrote copied that example over too quickly sorry, I am using wasPressed for opening of the link in the actual project (and changed it now in the example).
That is not the issue. The issue is that isPressed on WASD / Arrows and space for jump keep their true state when the focus is lost and keep being true even when the window is in focus again. They stay true until there is another keyup event for them.

Or I could phrase it differently, play canvas misses the up-event when the window is not focused. Which kind of makes sense. But I am having trouble using isPressed due to this because my characters keeps running off.

The only workaround I see is subscribing to the keyup/down events and for every button toggle a bool to indicate the current status and set all of those to false when window focus is lost…

Ah I see, I think the reason behind this is that WebGL will stop any rendering/update cycles as soon as the window loses focus.

This is a browser thing to avoid high energy consumption.

You are better detecting when the window loses focus to stop any input in place.

A little side question if that is okay, I’ve read that multiple times but when I open two instances of my game (multiplayer) on each of my monitors I can see myself moving around in both instances. The one out of focus has a much lower framerate sure but it is rendering new frames. How is that possible?

Actually I wasn’t fully accurate: a minimized or fully hidden (like when you open another tab) window will stop rendering.

Two windows side by side will continue rendering if they aren’t minimised.

Internally what stops running is the browser’s request animation frame method which is responsible for the Playcanvas update loop.

Any custom window timeout or interval method of yours will continue running, though sometimes with a less frequent/precise timer.

I see, thanks a lot for the insights :slight_smile:

1 Like

As Leonidas suggested, you’d want to listen for ‘onblur’ event. However, you should consider if you really want to send your users away from your app. Usually, the idea is to keep them using it as long as possible. Unless, they would use it for the purpose of leaving it (perhaps some sort of search app, like google?). If you catch the event of lost focus, you would want to reset the state of your player, like its movement and rotations. However, I don’t know if that event is reliably fired by all browsers.

I appreciate your advice and I agree however in this case it is what I intend to do. I am just sending them to another part of the experience not entirely of website.

However I can not get it to work, please look at the project link. How do I catch that event correctly? I added console.logs to monitor whats happening. On a regular focus loss I get my “Reset Input” message but it does not stop the character from jumping -.-

I would prefer doing it this way instead of calling ResetInput() right before opening the link because in the actual project I have the link opening call in a script on a trigger not on the movement class

I might be losing it. I tried calling ResetInput from another class by using FindByTag, and I do find the object and get feedback in the console but the boolean stays true.

It turns out the only way of doing this is triggering the link opener on keyup.
That way I can set the movement bools to false. On keydown it does not work.
I updated the setup in the example project for anyone finding this later (change KEYUP to KEYDOWN in triggerOpenUrlOnButton to see it not working).

I meant resetting the states on the event of window losing the focus (onblur). Something like this:

var Script = pc.createScript('script');

Script.prototype.initialize = function() {
    this.keyPressed = false;
    this.isGrounded = true;
    
    var self = this;
    window.onblur = function() { self.keyPressed = false; };
    
    this.app.keyboard.on(pc.EVENT_KEYDOWN, this.onKeyDown, this);
};

Script.prototype.onKeyDown = function() {
    this.keyPressed = true;
};

Script.prototype.update = function(dt) {
    if (Math.abs(this.entity.rigidbody.linearVelocity.y) <= 0.0001)
        this.isGrounded = true;
    
    if (this.keyPressed)
        window.open('', '_blank');
    
    if (this.keyPressed && this.isGrounded) {
        this.entity.rigidbody.applyImpulse(0, 4, 0);
        this.isGrounded = false;
    }
};

Since the physics world is being simulated while the jump key was pressed, the entity’s rigidbody will have a velocity larger than zero. When your app returns the focus, the entity will continue its original move started by the impulse. Once the restitution slows it down and the linearVelocity on Y axis drops below threshold, the entity will stop. Since the keyPressed has been falsed, then no more jumps will take place. If you don’t want the entity to have a residual movement after your app regains the focus, you can not only false the keyPressed, but also reset the rigidbody.linearVelocity and rigidbody.angularVelocity to 0 and teleport the entity to ground with rigidbody.teleport( pc.Vec3 position ).

1 Like