Improved pointer lock api

Some improvements over v2:
- You can access movementXY on pointer without needing to perform a manual `resetMovement()`
- Fixes bug where `releasePointerLock` would unregister event listeners before a final `pointerLockChange` could be called. Results in mouse.locked not having the right state and a final 'POINTER_LOCK_CHANGE_EVENT' not firing.
This commit is contained in:
Michael Hadley 2017-12-08 17:05:05 -06:00
parent b5e8a60530
commit d563cabadd
5 changed files with 117 additions and 1 deletions

View file

@ -65,6 +65,18 @@ var Pointer = new Class({
this.justDown = false;
this.justUp = false;
this.justMoved = false;
/**
* @property {number} movementX - If the mouse is locked, the horizontal relative movement
* of the Pointer in pixels since last frame.
*/
this.movementX = 0;
/**
* @property {number} movementY - If the mouse is locked, the vertical relative movement of
* the Pointer in pixels since last frame.
*/
this.movementY = 0;
},
positionToCamera: function (camera, output)
@ -109,6 +121,8 @@ var Pointer = new Class({
this.justDown = false;
this.justUp = false;
this.justMoved = false;
this.movementX = 0;
this.movementY = 0;
},
touchmove: function (event, time)
@ -135,6 +149,13 @@ var Pointer = new Class({
this.x = this.manager.transformX(event.pageX);
this.y = this.manager.transformY(event.pageY);
if (this.manager.mouse.locked)
{
// Potentially multiple DOM events within one frame, but only one Phaser event will fire
this.movementX += event.movementX || event.mozMovementX || event.webkitMovementX || 0;
this.movementY += event.movementY || event.mozMovementY || event.webkitMovementY || 0;
}
this.justMoved = true;
this.dirty = true;

View file

@ -113,6 +113,11 @@ var GlobalInputManager = new Class({
this.events.dispatch(new MouseEvent.UP(event));
break;
case 'pointerlockchange':
this.events.dispatch(new MouseEvent.POINTER_LOCK_CHANGE(event, this.mouse.locked));
break;
case 'touchmove':
pointer.touchmove(event, time);

View file

@ -1,4 +1,6 @@
var Class = require('../../utils/Class');
var Features = require('../../device/Features');
var MouseEvents = require('./events');
// https://developer.mozilla.org/en-US/docs/Web/API/MouseEvent
// https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
@ -19,6 +21,12 @@ var MouseManager = new Class({
this.target;
this.handler;
/**
* @property {boolean} locked - If the mouse has been pointer locked successfully this will
* be set to true.
*/
this.locked = false;
},
boot: function ()
@ -55,6 +63,52 @@ var MouseManager = new Class({
return this;
},
/**
* If the browser supports it, you can request that the pointer be locked to the browser window.
* This is classically known as 'FPS controls', where the pointer can't leave the browser until
* the user presses an exit key. If the browser successfully enters a locked state, a
* 'POINTER_LOCK_CHANGE_EVENT' will be dispatched - from the game's input manager - with an
* `isPointerLocked` property.
* It is important to note that pointer lock can only be enabled after an 'engagement gesture',
* see: https://w3c.github.io/pointerlock/#dfn-engagement-gesture.
*/
requestPointerLock: function ()
{
if (Features.pointerLock)
{
var element = this.target;
element.requestPointerLock = element.requestPointerLock || element.mozRequestPointerLock || element.webkitRequestPointerLock;
element.requestPointerLock();
}
},
/**
* Internal pointerLockChange handler.
*
* @param {Event} event - The native event from the browser.
*/
pointerLockChange: function (event)
{
var element = this.target;
this.locked = document.pointerLockElement === element || document.mozPointerLockElement === element || document.webkitPointerLockElement === element
? true : false;
this.manager.queue.push(event);
},
/**
* If the browser supports pointer lock, this will request that the pointer lock is released. If
* the browser successfully enters a locked state, a 'POINTER_LOCK_CHANGE_EVENT' will be
* dispatched - from the game's input manager - with an `isPointerLocked` property.
*/
releasePointerLock: function ()
{
if (Features.pointerLock)
{
document.exitPointerLock = document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock;
document.exitPointerLock();
}
},
startListeners: function ()
{
var queue = this.manager.queue;
@ -82,6 +136,14 @@ var MouseManager = new Class({
this.target.addEventListener('mousemove', handler, false);
this.target.addEventListener('mousedown', handler, false);
this.target.addEventListener('mouseup', handler, false);
if (Features.pointerLock)
{
this.pointerLockChange = this.pointerLockChange.bind(this);
document.addEventListener('pointerlockchange', this.pointerLockChange, true);
document.addEventListener('mozpointerlockchange', this.pointerLockChange, true);
document.addEventListener('webkitpointerlockchange', this.pointerLockChange, true);
}
},
stopListeners: function ()
@ -89,6 +151,13 @@ var MouseManager = new Class({
this.target.removeEventListener('mousemove', this.handler);
this.target.removeEventListener('mousedown', this.handler);
this.target.removeEventListener('mouseup', this.handler);
if (Features.pointerLock)
{
document.removeEventListener('pointerlockchange', this.pointerLockChange, true);
document.removeEventListener('mozpointerlockchange', this.pointerLockChange, true);
document.removeEventListener('webkitpointerlockchange', this.pointerLockChange, true);
}
}
});

View file

@ -0,0 +1,20 @@
var Class = require('../../../utils/Class');
var Event = require('../../../events/Event');
var PointerLockChangeEvent = new Class({
Extends: Event,
initialize:
function PointerLockChangeEvent (nativeEvent, isPointerLocked)
{
Event.call(this, 'POINTER_LOCK_CHANGE_EVENT');
this.data = nativeEvent;
this.isPointerLocked = isPointerLocked;
}
});
module.exports = PointerLockChangeEvent;

View file

@ -3,5 +3,6 @@
module.exports = {
DOWN: require('./MouseDownEvent'),
UP: require('./MouseUpEvent'),
MOVE: require('./MouseMoveEvent')
MOVE: require('./MouseMoveEvent'),
POINTER_LOCK_CHANGE: require('./PointerLockChangeEvent')
};