Для работы с шутером от первого лица в 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 предоставляет возможность управления камерой через движение мыши, что критично для 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();
Сглаживание предотвращает резкие рывки при обновлении положения и делает движение плавным.
Object3D.frustumCulled = true уменьшает количество
рендеринга невидимых объектов.Эти подходы формируют полноценную основу FPS-шутера на Three.js с поддержкой движения, столкновений, прыжков, управления оружием и анимации камеры.