Three.js - PointerLock Controls



The PointerLockControls implements the inbuilt browsers Pointer Lock API. It allows you to control the camera just like in a first-person in 3D games.

const controls = new PointerLockControls(camera, document.body)

Example

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>

Output

Advertisements