Three.js - PointerLock 控件
PointerLockControls 实现了内置浏览器 Pointer Lock API。它允许您像在 3D 游戏中的第一人称视角一样控制相机。
const controls = new PointerLockControls(camera, document.body)
示例
pointerlock-controls.html
<!DOCTYPE html> <html lang="en"> <head> <title>Three.js - Pointerlock controls</title> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, user-scalable=no, minimumscale=1.0, maximum-scale=1.0" /> <link type="text/css" rel="stylesheet" href="main.css" /> <style> * { margin: 0; padding: 0; box-sizing: border-box; } #blocker { position: absolute; width: 100%; height: 100%; background-color: rgba(0, 0, 0, 0.5); } #instructions { width: 100%; height: 100%; display: -webkit-box; display: -moz-box; display: box; -webkit-box-orient: horizontal; -moz-box-orient: horizontal; box-orient: horizontal; -webkit-box-pack: center; -moz-box-pack: center; box-pack: center; -webkit-box-align: center; -moz-box-align: center; box-align: center; color: #ffffff; text-align: center; font-family: Arial; font-size: 14px; line-height: 24px; cursor: pointer; } </style> <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script> </head> <body> <div id="blocker"> <div id="instructions"> <span style="font-size: 36px">Click to play</span> <br /><br /> Move: WASD<br /> Jump: SPACE<br /> Look: MOUSE </div> </div> <script type="module"> // Adding pointer lock controls to Three.js // You can move around the scene using mouse and keyboard import { PointerLockControls } from 'https://threejs.org/examples/jsm/ controls/PointerLockControls.js' let camera, scene, renderer, controls const objects = [] let raycaster let moveForward = false let moveBackward = false let moveLeft = false let moveRight = false let canJump = false let prevTime = performance.now() const velocity = new THREE.Vector3() const direction = new THREE.Vector3() const vertex = new THREE.Vector3() const color = new THREE.Color() init() animate() function init() { camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 1, 1000) camera.position.y = 10 scene = new THREE.Scene() scene.background = new THREE.Color(0xffffff) scene.fog = new THREE.Fog(0xffffff, 0, 750) const light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 0.75) light.position.set(0.5, 1, 0.75) scene.add(light) controls = new PointerLockControls(camera, document.body) const blocker = document.getElementById('blocker') const instructions = document.getElementById('instructions') instructions.addEventListener('click', function () { controls.lock() }) controls.addEventListener('lock', function () { instructions.style.display = 'none' blocker.style.display = 'none' }) controls.addEventListener('unlock', function () { blocker.style.display = 'block' instructions.style.display = '' }) scene.add(controls.getObject()) const onKeyDown = function (event) { switch (event.code) { case 'ArrowUp': case 'KeyW': moveForward = true break case 'ArrowLeft': case 'KeyA': moveLeft = true break case 'ArrowDown': case 'KeyS': moveBackward = true break case 'ArrowRight': case 'KeyD': moveRight = true break case 'Space': if (canJump === true) velocity.y += 350 canJump = false break } } const onKeyUp = function (event) { switch (event.code) { case 'ArrowUp': case 'KeyW': moveForward = false break case 'ArrowLeft': case 'KeyA': moveLeft = false break case 'ArrowDown': case 'KeyS': moveBackward = false break case 'ArrowRight': case 'KeyD': moveRight = false break } } document.addEventListener('keydown', onKeyDown) document.addEventListener('keyup', onKeyUp) raycaster = new THREE.Raycaster(new THREE.Vector3(), new THREE.Vector3(0, -1, 0), 0, 10) // floor let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100) floorGeometry.rotateX(-Math.PI / 2) // vertex displacement let position = floorGeometry.attributes.position for (let i = 0, l = position.count; i < l; i++) { vertex.fromBufferAttribute(position, i) vertex.x += Math.random() * 20 - 10 vertex.y += Math.random() * 2 vertex.z += Math.random() * 20 - 10 position.setXYZ(i, vertex.x, vertex.y, vertex.z) } floorGeometry = floorGeometry.toNonIndexed() // ensure each face has unique vertices position = floorGeometry.attributes.position const colorsFloor = [] for (let i = 0, l = position.count; i < l; i++) { color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75) colorsFloor.push(color.r, color.g, color.b) } floorGeometry.setAttribute('color', new THREE.Float32BufferAttribute (colorsFloor, 3)) const floorMaterial = new THREE.MeshBasicMaterial({ vertexColors: true }) const floor = new THREE.Mesh(floorGeometry, floorMaterial) scene.add(floor) // objects const boxGeometry = new THREE.BoxGeometry(20, 20, 20).toNonIndexed() position = boxGeometry.attributes.position const colorsBox = [] for (let i = 0, l = position.count; i < l; i++) { color.setHSL(Math.random() * 0.3 + 0.5, 0.75, Math.random() * 0.25 + 0.75) colorsBox.push(color.r, color.g, color.b) } boxGeometry.setAttribute('color', new THREE.Float32BufferAttribute(colorsBox, 3)) for (let i = 0; i < 500; i++) { const boxMaterial = new THREE.MeshPhongMaterial({ specular: 0xffffff, flatShading: true, vertexColors: true }) boxMaterial.color.setHSL(Math.random() * 0.2 + 0.5, 0.75, Math.random() * 0.25 + 0.75) const box = new THREE.Mesh(boxGeometry, boxMaterial) box.position.x = Math.floor(Math.random() * 20 - 10) * 20 box.position.y = Math.floor(Math.random() * 20) * 20 + 10 box.position.z = Math.floor(Math.random() * 20 - 10) * 20 scene.add(box) objects.push(box) } // renderer = new THREE.WebGLRenderer({ antialias: true }) renderer.setPixelRatio(window.devicePixelRatio) renderer.setSize(window.innerWidth, window.innerHeight) document.body.appendChild(renderer.domElement) // window.addEventListener('resize', onWindowResize) } function onWindowResize() { camera.aspect = window.innerWidth / window.innerHeight camera.updateProjectionMatrix() renderer.setSize(window.innerWidth, window.innerHeight) } function animate() { requestAnimationFrame(animate) const time = performance.now() if (controls.isLocked === true) { raycaster.ray.origin.copy(controls.getObject().position) raycaster.ray.origin.y -= 10 const intersections = raycaster.intersectObjects(objects) const onObject = intersections.length > 0 const delta = (time - prevTime) / 1000 velocity.x -= velocity.x * 10.0 * delta velocity.z -= velocity.z * 10.0 * delta velocity.y -= 9.8 * 100.0 * delta // 100.0 = mass direction.z = Number(moveForward) - Number(moveBackward) direction.x = Number(moveRight) - Number(moveLeft) direction.normalize() // this ensures consistent movements in all directions if (moveForward || moveBackward) velocity.z -= direction.z * 400.0 * delta if (moveLeft || moveRight) velocity.x -= direction.x * 400.0 * delta if (onObject === true) { velocity.y = Math.max(0, velocity.y) canJump = true } controls.moveRight(-velocity.x * delta) controls.moveForward(-velocity.z * delta) controls.getObject().position.y += velocity.y * delta // new behavior if (controls.getObject().position.y < 10) { velocity.y = 0 controls.getObject().position.y = 10 canJump = true } } prevTime = time renderer.render(scene, camera) } </script> </body> </html>