PointerLockControls: шутер от первого лица

Для работы с шутером от первого лица в Three.js необходимо создать базовую сцену, камеру и рендерер. Камера обычно используется типа PerspectiveCamera, так как она имитирует восприятие человека.

const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(
    75, 
    window.innerWidth / window.innerHeight, 
    0.1, 
    1000
);

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);

Важно правильно настроить aspect ratio камеры и размеры рендерера, чтобы изображение не искажалось при изменении размера окна.


Подключение PointerLockControls

PointerLockControls предоставляет возможность управления камерой через движение мыши, что критично для FPS-шутеров.

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

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

controls.addEventListener('lock', () => {
    console.log('Курсор заблокирован');
});

controls.addEventListener('unlock', () => {
    console.log('Курсор разблокирован');
});

Блокировка курсора позволяет мыши управлять направлением камеры без ограничения по экрану. Разблокировка возвращает стандартное поведение мыши.


Управление движением

Для перемещения игрока используются события клавиатуры. Обычно реализуются четыре направления: вперед, назад, влево, вправо.

const move = { forward: false, backward: false, left: false, right: false };

document.addEventListener('keydown', (event) => {
    switch(event.code) {
        case 'KeyW': move.forward = true; break;
        case 'KeyS': move.backward = true; break;
        case 'KeyA': move.left = true; break;
        case 'KeyD': move.right = true; break;
    }
});

document.addEventListener('keyup', (event) => {
    switch(event.code) {
        case 'KeyW': move.forward = false; break;
        case 'KeyS': move.backward = false; break;
        case 'KeyA': move.left = false; break;
        case 'KeyD': move.right = false; break;
    }
});

Движение камеры реализуется через обновление позиции в основном цикле анимации.

const velocity = new THREE.Vector3();
const speed = 0.1;

function animate() {
    requestAnimationFrame(animate);

    if (move.forward) velocity.z = -speed;
    if (move.backward) velocity.z = speed;
    if (move.left) velocity.x = -speed;
    if (move.right) velocity.x = speed;

    controls.moveRight(velocity.x);
    controls.moveForward(velocity.z);

    velocity.set(0, 0, 0);

    renderer.render(scene, camera);
}

animate();

Использование controls.moveForward() и controls.moveRight() позволяет двигаться относительно направления камеры, а не мировых координат, что критично для ощущения FPS.


Коллизии с объектами

Для предотвращения прохождения сквозь стены или объекты используется простая проверка пересечения с Raycaster.

const raycaster = new THREE.Raycaster();
const objects = []; // массив объектов сцены для столкновений

function checkCollisions() {
    const origin = controls.getObject().position.clone();
    const directions = [
        new THREE.Vector3(0, 0, -1),
        new THREE.Vector3(0, 0, 1),
        new THREE.Vector3(-1, 0, 0),
        new THREE.Vector3(1, 0, 0)
    ];

    for (let dir of directions) {
        raycaster.set(origin, dir);
        const intersections = raycaster.intersectObjects(objects, true);
        if (intersections.length > 0 && intersections[0].distance < 1) {
            velocity.set(0, 0, 0);
        }
    }
}

Вызывая checkCollisions() перед обновлением позиции, можно обеспечить реалистичное поведение игрока при столкновении с миром.


Прыжки и гравитация

Для имитации физики движения добавляется вертикальная скорость и ускорение от гравитации.

let canJump = false;
let yVelocity = 0;
const gravity = 0.01;

document.addEventListener('keydown', (event) => {
    if (event.code === 'Space' && canJump) {
        yVelocity = 0.2;
        canJump = false;
    }
});

function animate() {
    requestAnimationFrame(animate);

    yVelocity -= gravity;
    controls.getObject().position.y += yVelocity;

    if (controls.getObject().position.y < 1.5) {
        yVelocity = 0;
        controls.getObject().position.y = 1.5;
        canJump = true;
    }

    checkCollisions();
    renderer.render(scene, camera);
}

animate();

Такой подход позволяет игроку прыгать и естественно падать под действием гравитации.


Добавление оружия и взаимодействие

Оружие обычно привязывается к камере и отображается на переднем плане. Используется Object3D или модель из GLTFLoader.

const weapon = new THREE.Mesh(
    new THREE.BoxGeometry(0.2, 0.1, 1),
    new THREE.MeshStandardMaterial({ color: 0x333333 })
);

camera.add(weapon);
weapon.position.set(0.5, -0.5, -1);

Стрельба реализуется через лучи (Raycaster) из позиции камеры в направлении взгляда.

function shoot() {
    const shootRay = new THREE.Raycaster(
        camera.position,
        camera.getWorldDirection(new THREE.Vector3())
    );
    const hits = shootRay.intersectObjects(objects);
    if (hits.length > 0) {
        console.log('Попадание в объект', hits[0].object.name);
    }
}

document.addEventListener('click', shoot);

Это обеспечивает точную имитацию стрельбы и попаданий.


Анимация и сглаживание движения

Для более естественного ощущения FPS рекомендуется использовать lerp для сглаживания позиции и вращения камеры.

const targetPosition = new THREE.Vector3();

function animate() {
    requestAnimationFrame(animate);

    targetPosition.copy(controls.getObject().position);
    controls.getObject().position.lerp(targetPosition, 0.1);

    renderer.render(scene, camera);
}

animate();

Сглаживание предотвращает резкие рывки при обновлении положения и делает движение плавным.


Оптимизация и производительность

  • Использование Frustum Culling через Object3D.frustumCulled = true уменьшает количество рендеринга невидимых объектов.
  • Для больших сцен применяются InstancedMesh для повторяющихся объектов.
  • Настройка requestAnimationFrame вместо таймеров обеспечивает синхронизацию с частотой обновления экрана.

Эти подходы формируют полноценную основу FPS-шутера на Three.js с поддержкой движения, столкновений, прыжков, управления оружием и анимации камеры.