threejs 第一人称视角,指针锁定控制器PointerLockControls的使用

150 阅读2分钟

threejs 第一人称视角,指针锁定控制器PointerLockControls的使用

官方Demo

官方的使用例子 three.js - pointerlock controls

官方文档 PointerLockControls – three.js docs (threejs.org)

项目中遇到的问题

  1. 在用键盘WASD来控制,视角的移动时候,视角的方向一直是在一个点上。
    问题原因: 是因为页面中有其他的 controls 控制器一直在 update() 引起的。
    解决办法: 找到项目(页面)中所有的 controls.update(),在lock()时候关闭。

代码

官方的的例子在Y轴有重力,下面基于例子修改了一个没有重力的使用情况。

源码:

import * as THREE from 'three';
import { PointerLockControls } from 'three/addons/controls/PointerLockControls.js';

export class FirstPerson {
    constructor(camera, renderer) {

        this.scene = scene;
        this.camera = camera;
        this.renderer = renderer;

        this.moveForward = false;
        this.moveLeft = false;
        this.moveBackward = false;
        this.moveRight = false;
        this.moveUp = false;
        this.moveBottom = false;

        this.prevTime = performance.now();

        this.velocity = new THREE.Vector3();
        this.direction = new THREE.Vector3();

        this.controls = new PointerLockControls(camera, document.body);

        this.attachEvent();
    }

    lock() {
        this.controls.lock();
    }

    attachEvent() {
        let context = this;

        document.addEventListener('click', () => {
            this.lock();
        })

        const onKeyDown = function (event) {
            switch (event.code) {
                case 'ArrowUp':
                case 'KeyW':
                    context.moveForward = true;
                    break;
                case 'ArrowLeft':
                case 'KeyA':
                    context.moveLeft = true;
                    break;
                case 'ArrowDown':
                case 'KeyS':
                    context.moveBackward = true;
                    break;
                case 'ArrowRight':
                case 'KeyD':
                    context.moveRight = true;
                    break;
                case 'KeyR':
                case 'Space':
                    context.moveUp = true;
                    break;
                case 'KeyF':
                case 'KeyB':
                    context.moveBottom = true;
            }
        };

        const onKeyUp = function (event) {
            switch (event.code) {
                case 'ArrowUp':
                case 'KeyW':
                    context.moveForward = false;
                    break;
                case 'ArrowLeft':
                case 'KeyA':
                    context.moveLeft = false;
                    break;
                case 'ArrowDown':
                case 'KeyS':
                    context.moveBackward = false;
                    break;
                case 'ArrowRight':
                case 'KeyD':
                    context.moveRight = false;
                    break;
                case 'KeyR':
                case 'Space':
                    context.moveUp = false;
                    break;
                case 'KeyF':
                case 'KeyB':
                    context.moveBottom = false;
            }
        };

        document.addEventListener('keydown', onKeyDown);
        document.addEventListener('keyup', onKeyUp);
    }

    update() {
        const time = performance.now();
        if (this.controls.isLocked === true) {

            const delta = (time - this.prevTime) / 1000;
            this.velocity.x -= this.velocity.x * 10.0 * delta;
            this.velocity.y -= this.velocity.y * 10.0 * delta;
            this.velocity.z -= this.velocity.z * 10.0 * delta;

            this.direction.z = Number(this.moveForward) - Number(this.moveBackward);
            this.direction.y = Number(this.moveUp) - Number(this.moveBottom);
            this.direction.x = Number(this.moveRight) - Number(this.moveLeft);
            this.direction.normalize();

            if (this.moveForward || this.moveBackward) this.velocity.z -= this.direction.z * 400.0 * delta;
            if (this.moveUp || this.moveBottom) this.velocity.y -= this.direction.y * 400.0 * delta;
            if (this.moveLeft || this.moveRight) this.velocity.x -= this.direction.x * 400.0 * delta;

            this.controls.moveRight(- this.velocity.x * delta);
            this.controls.moveForward(- this.velocity.z * delta);
            this.controls.getObject().position.y -= ( this.velocity.y * delta );
        }

        this.prevTime = time;
    }
}

使用:

// ......

let pfControls = new FirstPerson(camera, renderer);

function animation(time) {
    renderer.render(scene, camera);
    pfControls.update();
}

收获

在鼠标点击页面进入lock状态时候,怎么计算控制camera的旋转。

PointerLockControls里面的实现过程。

function onMouseMove( event ) {
    if ( scope.isLocked === false ) return;

    const movementX = event.movementX || event.mozMovementX || event.webkitMovementX || 0;
    const movementY = event.movementY || event.mozMovementY || event.webkitMovementY || 0;

    _euler.setFromQuaternion( camera.quaternion );

    _euler.y -= movementX * 0.002 * scope.pointerSpeed;
    _euler.x -= movementY * 0.002 * scope.pointerSpeed;

    _euler.x = Math.max( _PI_2 - scope.maxPolarAngle, Math.min( _PI_2 - scope.minPolarAngle, _euler.x ) );
    
    camera.quaternion.setFromEuler( _euler );
    scope.dispatchEvent( _changeEvent );
}
  1. event.movementX 和 event.movementY 表示与上一个mousemove事件之间的距离,根据 movementX 来计算选要旋转的角度值。
  2. Euler欧拉角的使用。
  3. 最后再设置 camera 的旋转值。

简单点的控制camera旋转

document.body.addEventListener( 'mousemove', ( event ) => {
    if ( document.pointerLockElement === document.body ) {
        camera.rotation.y -= event.movementX / 500;
        camera.rotation.x -= event.movementY / 500;
    }
} );

欧拉角是什么?
欧拉角是一种常用的旋转表示方法,用于描述物体在三维空间中的旋转状态。它通过三个角度来表示物体绕三个坐标轴的旋转。
通常,欧拉角使用三个角度来表示旋转,分别称为俯仰角(Pitch)、偏航角(Yaw)和滚转角(Roll)。
俯仰角(Pitch):绕物体的 X 轴旋转。
偏航角(Yaw):绕物体的 Y 轴旋转。
滚转角(Roll):绕物体的 Z 轴旋转。
这三个角度可以以不同的顺序组合,形成不同的旋转顺序。例如,当旋转顺序为 "XYZ" 时,先进行俯仰角旋转,然后是偏航角旋转,最后是滚转角旋转。而当旋转顺序为 "YXZ" 时,先进行偏航角旋转,然后是俯仰角旋转,最后是滚转角旋转。