简介
Pointer Lock API 有助于在浏览器游戏中正确实现第一人称射击游戏控件。例如,如果没有相对鼠标移动,玩家的光标可能会撞到屏幕右侧,而向右的任何进一步移动都会被忽略 - 视图不会继续向右平移,玩家也无法追捕坏人并用机关枪扫射他们。玩家会变得紧张和沮丧。使用指针锁定功能,这种次优行为就不会发生。
借助 Pointer Lock API,您的应用可以执行以下操作:
- 获取原始鼠标数据,包括相对鼠标移动
- 将所有鼠标事件转送到特定元素
启用指针锁定的副作用是,系统会隐藏鼠标光标,以便您根据需要选择绘制特定于应用的指针,或让鼠标指针保持隐藏状态,以便用户使用鼠标移动框架。相对鼠标移动是指鼠标指针位置相对于上一帧的增量(无论绝对位置如何)。例如,如果鼠标指针从 (640, 480) 移动到 (520, 490),则相对移动为 (-120, 10)。请参阅下方显示原始鼠标位置增量值的交互式示例。
本教程将介绍以下两个主题:启用和处理指针锁定事件的要点,以及实现第一人称射击游戏控制方案。没错,阅读完本文后,您将了解如何使用指针锁定功能,以及如何为您自己的浏览器游戏实现 Quake 风格的控件!
浏览器兼容性
指针锁定机制
特征检测
如需确定用户的浏览器是否支持指针锁定,您需要在文档对象中检查 pointerLockElement
或供应商前缀版本。在代码中:
var havePointerLock = 'pointerLockElement' in document ||
'mozPointerLockElement' in document ||
'webkitPointerLockElement' in document;
目前,指针锁定功能仅适用于 Firefox 和 Chrome。Opera 和 IE 尚不支持此功能。
正在激活
启用指针锁定的过程分为两个步骤。首先,您的应用会请求为特定元素启用指针锁定,并且在用户授予权限后立即触发 pointerlockchange
事件。用户可以随时按下 Esc 键取消指针锁定。您的应用还可以通过编程方式退出指针锁定。取消指针锁定时,会触发 pointerlockchange
事件。
element.requestPointerLock = element.requestPointerLock ||
element.mozRequestPointerLock ||
element.webkitRequestPointerLock;
// Ask the browser to lock the pointer
element.requestPointerLock();
// Ask the browser to release the pointer
document.exitPointerLock = document.exitPointerLock ||
document.mozExitPointerLock ||
document.webkitExitPointerLock;
document.exitPointerLock();
只需使用上述代码即可。当浏览器锁定指针时,系统会弹出一个气泡,告知用户您的应用已锁定指针,并指示用户可以通过按“Esc”键取消锁定。
事件处理
您的应用必须为以下两个事件添加监听器。第一种是 pointerlockchange
,每当指针锁定状态发生变化时,都会触发。第二个是 mousemove
,每当鼠标移动时都会触发。
// Hook pointer lock state change events
document.addEventListener('pointerlockchange', changeCallback, false);
document.addEventListener('mozpointerlockchange', changeCallback, false);
document.addEventListener('webkitpointerlockchange', changeCallback, false);
// Hook mouse move events
document.addEventListener("mousemove", this.moveCallback, false);
在 pointerlockchange
回调内,您必须检查指针是否刚刚锁定或解锁。确定是否启用了指针锁定很简单:检查 document.pointerLockElement 是否等于请求指针锁定的元素。如果是,则表示您的应用成功锁定了指针;如果不是,则表示指针已被用户或您自己的代码解锁。
if (document.pointerLockElement === requestedElement ||
document.mozPointerLockElement === requestedElement ||
document.webkitPointerLockElement === requestedElement) {
// Pointer was just locked
// Enable the mousemove listener
document.addEventListener("mousemove", this.moveCallback, false);
} else {
// Pointer was just unlocked
// Disable the mousemove listener
document.removeEventListener("mousemove", this.moveCallback, false);
this.unlockHook(this.element);
}
启用指针锁定后,clientX
、clientY
、screenX
和 screenY
保持不变。movementX
和 movementY
会更新为指针自上次传送事件以来移动的像素数。在伪代码中:
event.movementX = currentCursorPositionX - previousCursorPositionX;
event.movementY = currentCursorPositionY - previousCursorPositionY;
在 mousemove
回调中,可以从事件的 movementX
和 movementY
字段中提取相对鼠标移动数据。
function moveCallback(e) {
var movementX = e.movementX ||
e.mozMovementX ||
e.webkitMovementX ||
0,
movementY = e.movementY ||
e.mozMovementY ||
e.webkitMovementY ||
0;
}
捕获错误
如果在进入或退出指针锁定时引发错误,系统会触发 pointerlockerror
事件。此事件未附加任何数据。
document.addEventListener('pointerlockerror', errorCallback, false);
document.addEventListener('mozpointerlockerror', errorCallback, false);
document.addEventListener('webkitpointerlockerror', errorCallback, false);
是否需要全屏?
最初,指针锁定与 FullScreen API 绑定。也就是说,元素必须处于全屏模式,才能将指针锁定到该元素。但这种情况已不复存在,指针锁定既可用于应用中的所有元素,也可用于全屏或不全屏显示。
第一人称射击游戏控件示例
现在,我们已启用指针锁定并接收事件,接下来我们来看看一个实际示例。您是否曾想了解 Quake 中的控件是如何运作的?请系好安全带,我将通过代码来解释这些概念!
第一人称射击游戏控制机制以四大核心机制为基础:
- 沿当前视线矢量前后移动
- 沿当前 strafing 矢量左右移动
- 旋转视图偏航(向左和向右)
- 旋转视图倾斜度(向上和向下)
实现此控制方案的游戏只需三项数据:相机位置、相机视线矢量和常量向上矢量。上矢量始终为 (0, 1, 0)。上述所有四种机制只是以不同的方式操纵镜头位置和镜头外观矢量。
移动
首先介绍的是移动。在以下演示中,移动操作已映射到标准的 W、A、S 和 D 键。W 键和 S 键用于控制摄像头向前和向后移动。而 A 键和 D 键用于左右摄像头。向前和向后移动摄像头非常简单:
// Forward direction
var forwardDirection = vec3.create(cameraLookVector);
// Speed
var forwardSpeed = dt * cameraSpeed;
// Forward or backward depending on keys held
var forwardScale = 0.0;
forwardScale += keyState.W ? 1.0 : 0.0;
forwardScale -= keyState.S ? 1.0 : 0.0;
// Scale movement
vec3.scale(forwardDirection, forwardScale * forwardSpeed);
// Add scaled movement to camera position
vec3.add(cameraPosition, forwardDirection);
向左和向右侧滑需要指定侧滑方向。可以使用叉乘计算侧移方向:
// Strafe direction
var strafeDirection = vec3.create();
vec3.cross(cameraLookVector, cameraUpVector, strafeDirection);
确定平移方向后,实现平移移动与前进或后退相同。
接下来是旋转视图。
偏摆
相机视图的偏摆或水平旋转只是绕恒定向上矢量旋转。以下是用于围绕任意轴旋转相机视线矢量的通用代码。其工作原理是构建一个四元数,表示围绕 axis
旋转 deltaAngle
弧度,然后使用四元数旋转相机视线矢量:
// Extract camera look vector
var frontDirection = vec3.create();
vec3.subtract(this.lookAtPoint, this.eyePoint, frontDirection);
vec3.normalize(frontDirection);
var q = quat4.create();
// Construct quaternion
quat4.fromAngleAxis(deltaAngle, axis, q);
// Rotate camera look vector
quat4.multiplyVec3(q, frontDirection);
// Update camera look vector
this.lookAtPoint = vec3.create(this.eyePoint);
vec3.add(this.lookAtPoint, frontDirection);
推介
实现相机视图的俯仰或垂直旋转的方式类似,但您需要围绕平移矢量旋转,而不是围绕向上矢量旋转。第一步是计算 strafing 矢量,然后围绕该轴旋转相机视线矢量。
摘要
您可以使用 Pointer Lock API 控制鼠标光标。如果您在制作 Web 游戏,当玩家因为兴奋地将鼠标移出窗口而导致游戏停止接收鼠标更新,而不再被击杀时,他们会非常开心。使用方法很简单:
- 添加了
pointerlockchange
事件监听器,以跟踪指针锁定的状态 - 请求对特定元素的指针锁定
- 添加了
mousemove
事件监听器以获取更新