Skip to content

PointerLockControls

Video Lecture

PointerLock Controls PointerLockControls

Description

The PointerLockControls implements the inbuilt browsers Pointer Lock API. It provides input methods based on the movement of the mouse over time (i.e., deltas), not just the absolute position of the mouse cursor in the viewport. It gives you access to raw mouse movement, locks the target of mouse events to a single element, eliminates limits on how far mouse movement can go in a single direction, and removes the cursor from view. It is ideal for first person 3D games, for example.

<>

Start Scripts

./dist/client/index.html

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1" />
        <title>
            Three.js TypeScript Tutorials by Sean Bradley :
            https://sbcode.net/threejs
        </title>
        <style>
            body {
                overflow: hidden;
                margin: 0px;
            }

            #menuPanel {
                position: absolute;
                background-color: rgba(255, 255, 255, 0.5);
                top: 0px;
                left: 0px;
                width: 100%;
                height: 100%;
            }

            #startButton {
                height: 50px;
                width: 200px;
                margin: -25px -100px;
                position: relative;
                top: 50%;
                left: 50%;
                font-size: 32px;
            }
        </style>
    </head>

    <body>
        <div id="menuPanel">
            <button id="startButton">Click to Start</button>
        </div>
        <script type="module" src="bundle.js"></script>
    </body>
</html>

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import * as THREE from 'three'
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls'
import Stats from 'three/examples/jsm/libs/stats.module'

const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))

const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
)
camera.position.y = 1
camera.position.z = 2

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const menuPanel = document.getElementById('menuPanel') as HTMLDivElement
// const startButton = document.getElementById('startButton') as HTMLInputElement
// startButton.addEventListener(
//     'click',
//     function () {
//         controls.lock()
//     },
//     false
// )

const controls = new PointerLockControls(camera, renderer.domElement)
// controls.addEventListener('change', () => console.log("Controls Change"))
// controls.addEventListener('lock', () => menuPanel.style.display = 'none')
// controls.addEventListener('unlock', () => menuPanel.style.display = 'block')

const planeGeometry = new THREE.PlaneGeometry(100, 100, 50, 50)
const material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: true,
})
const plane = new THREE.Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)

const cubes: THREE.Mesh[] = []
for (let i = 0; i < 100; i++) {
    const geo = new THREE.BoxGeometry(
        Math.random() * 4,
        Math.random() * 16,
        Math.random() * 4
    )
    const mat = new THREE.MeshBasicMaterial({ wireframe: true })
    switch (i % 3) {
        case 0:
            mat.color = new THREE.Color(0xff0000)
            break
        case 1:
            mat.color = new THREE.Color(0xffff00)
            break
        case 2:
            mat.color = new THREE.Color(0x0000ff)
            break
    }
    const cube = new THREE.Mesh(geo, mat)
    cubes.push(cube)
}
cubes.forEach((c) => {
    c.position.x = Math.random() * 100 - 50
    c.position.z = Math.random() * 100 - 50
    c.geometry.computeBoundingBox()
    c.position.y =
        ((c.geometry.boundingBox as THREE.Box3).max.y -
            (c.geometry.boundingBox as THREE.Box3).min.y) /
        2
    scene.add(c)
})

// const onKeyDown = function (event: KeyboardEvent) {
//     switch (event.code) {
//         case "KeyW":
//             controls.moveForward(.25)
//             break
//         case "KeyA":
//             controls.moveRight(-.25)
//             break
//         case "KeyS":
//             controls.moveForward(-.25)
//             break
//         case "KeyD":
//             controls.moveRight(.25)
//             break
//     }
// }
// document.addEventListener('keydown', onKeyDown, false)

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const stats = new Stats()
document.body.appendChild(stats.dom)

function animate() {
    requestAnimationFrame(animate)

    //controls.update()

    render()

    stats.update()
}

function render() {
    renderer.render(scene, camera)
}

animate()

Final Script

./src/client/client.ts

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import * as THREE from 'three'
import { PointerLockControls } from 'three/examples/jsm/controls/PointerLockControls'
import Stats from 'three/examples/jsm/libs/stats.module'

const scene = new THREE.Scene()
scene.add(new THREE.AxesHelper(5))

const camera = new THREE.PerspectiveCamera(
    75,
    window.innerWidth / window.innerHeight,
    0.1,
    1000
)
camera.position.y = 1
camera.position.z = 2

const renderer = new THREE.WebGLRenderer()
renderer.setSize(window.innerWidth, window.innerHeight)
document.body.appendChild(renderer.domElement)

const menuPanel = document.getElementById('menuPanel') as HTMLDivElement
const startButton = document.getElementById('startButton') as HTMLInputElement
startButton.addEventListener(
    'click',
    function () {
        controls.lock()
    },
    false
)

const controls = new PointerLockControls(camera, renderer.domElement)
//controls.addEventListener('change', () => console.log("Controls Change"))
controls.addEventListener('lock', () => (menuPanel.style.display = 'none'))
controls.addEventListener('unlock', () => (menuPanel.style.display = 'block'))

const planeGeometry = new THREE.PlaneGeometry(100, 100, 50, 50)
const material = new THREE.MeshBasicMaterial({
    color: 0x00ff00,
    wireframe: true,
})
const plane = new THREE.Mesh(planeGeometry, material)
plane.rotateX(-Math.PI / 2)
scene.add(plane)

const cubes: THREE.Mesh[] = []
for (let i = 0; i < 100; i++) {
    const geo = new THREE.BoxGeometry(
        Math.random() * 4,
        Math.random() * 16,
        Math.random() * 4
    )
    const mat = new THREE.MeshBasicMaterial({ wireframe: true })
    switch (i % 3) {
        case 0:
            mat.color = new THREE.Color(0xff0000)
            break
        case 1:
            mat.color = new THREE.Color(0xffff00)
            break
        case 2:
            mat.color = new THREE.Color(0x0000ff)
            break
    }
    const cube = new THREE.Mesh(geo, mat)
    cubes.push(cube)
}
cubes.forEach((c) => {
    c.position.x = Math.random() * 100 - 50
    c.position.z = Math.random() * 100 - 50
    c.geometry.computeBoundingBox()
    c.position.y =
        ((c.geometry.boundingBox as THREE.Box3).max.y -
            (c.geometry.boundingBox as THREE.Box3).min.y) /
        2
    scene.add(c)
})

const onKeyDown = function (event: KeyboardEvent) {
    switch (event.code) {
        case 'KeyW':
            controls.moveForward(0.25)
            break
        case 'KeyA':
            controls.moveRight(-0.25)
            break
        case 'KeyS':
            controls.moveForward(-0.25)
            break
        case 'KeyD':
            controls.moveRight(0.25)
            break
    }
}
document.addEventListener('keydown', onKeyDown, false)

window.addEventListener('resize', onWindowResize, false)
function onWindowResize() {
    camera.aspect = window.innerWidth / window.innerHeight
    camera.updateProjectionMatrix()
    renderer.setSize(window.innerWidth, window.innerHeight)
    render()
}

const stats = new Stats()
document.body.appendChild(stats.dom)

function animate() {
    requestAnimationFrame(animate)

    //controls.update()

    render()

    stats.update()
}

function render() {
    renderer.render(scene, camera)
}

animate()

PointerLockControls (Official Documentation)

Pointer Lock API

KeyboardEvent.code

Comments