В библиотеке A-Frame камера представлена сущностью
<a-entity> с компонентом camera. По
умолчанию сцена автоматически создаёт активную камеру, если она не
определена вручную. Однако при разработке сложных VR/3D-приложений
требуется полный контроль над положением, поворотом и поведением камеры
через JavaScript.
Компонент camera в A-Frame является обёрткой над
объектом THREE.PerspectiveCamera из библиотеки Three.js.
Доступ к нативному объекту осуществляется через:
const cameraEl = document.querySelector('[camera]');
const threeCamera = cameraEl.getObject3D('camera');
Это даёт возможность управлять как атрибутами A-Frame, так и низкоуровневыми параметрами Three.js.
Положение камеры определяется компонентом position. Его
можно изменять несколькими способами.
cameraEl.setAttribute('position', { x: 0, y: 2, z: 5 });
Либо строковым форматом:
cameraEl.setAttribute('position', '0 2 5');
cameraEl.object3D.position.set(0, 2, 5);
Изменение через object3D предпочтительно при частых
обновлениях (например, в анимации), поскольку позволяет избежать
повторного парсинга атрибутов.
Поворот в A-Frame задаётся через компонент rotation (в
градусах), однако внутри Three.js используются радианы.
cameraEl.setAttribute('rotation', { x: 0, y: 90, z: 0 });
cameraEl.object3D.rotation.y = Math.PI / 2;
Важно учитывать порядок вращения (по умолчанию XYZ). Его
можно изменить:
cameraEl.object3D.rotation.order = 'YXZ';
Это особенно актуально при реализации свободного обзора.
Для динамического управления используется метод tick()
внутри кастомного компонента.
AFRAME.registerComponent('manual-camera-control', {
tick: function (time, deltaTime) {
const camera = this.el;
camera.object3D.position.z -= 0.01;
}
});
Подключение:
<a-entity camera manual-camera-control></a-entity>
time — общее время с запуска сцены,
deltaTime — время между кадрами (в миллисекундах).
Для корректной скорости движения следует использовать
deltaTime:
const speed = 0.002;
camera.object3D.position.z -= speed * deltaTime;
Для ручного управления часто реализуется система WASD.
const keys = {};
window.addEventListener('keydown', e => keys[e.code] = true);
window.addEventListener('keyup', e => keys[e.code] = false);
В компоненте:
AFRAME.registerComponent('keyboard-camera', {
tick: function (time, deltaTime) {
const camera = this.el;
const speed = 0.003 * deltaTime;
if (keys['KeyW']) camera.object3D.position.z -= speed;
if (keys['KeyS']) camera.object3D.position.z += speed;
if (keys['KeyA']) camera.object3D.position.x -= speed;
if (keys['KeyD']) camera.object3D.position.x += speed;
}
});
Для перемещения относительно направления взгляда требуется использовать вектор направления:
const direction = new THREE.Vector3();
camera.object3D.getWorldDirection(direction);
camera.object3D.position.addScaledVector(direction, speed);
A-Frame по умолчанию использует компонент look-controls,
который связывает камеру с движением мыши или VR-гарнитуры. Для полного
ручного контроля его необходимо отключить:
<a-entity camera look-controls="enabled: false"></a-entity>
Реализация собственного вращения:
let isDragging = false;
let previousMousePosition = { x: 0, y: 0 };
window.addEventListener('mousedown', () => isDragging = true);
window.addEventListener('mouseup', () => isDragging = false);
window.addEventListener('mousemove', e => {
if (!isDragging) return;
const deltaMove = {
x: e.offsetX - previousMousePosition.x,
y: e.offsetY - previousMousePosition.y
};
cameraEl.object3D.rotation.y -= deltaMove.x * 0.002;
cameraEl.object3D.rotation.x -= deltaMove.y * 0.002;
previousMousePosition = {
x: e.offsetX,
y: e.offsetY
};
});
Для ограничения вертикального угла:
cameraEl.object3D.rotation.x = Math.max(
-Math.PI / 2,
Math.min(Math.PI / 2, cameraEl.object3D.rotation.x)
);
При сложных сценариях вращения (например, свободная камера без
гимбал-лок) применяется quaternion.
const quaternion = new THREE.Quaternion();
quaternion.setFromAxisAngle(
new THREE.Vector3(0, 1, 0),
Math.PI / 4
);
cameraEl.object3D.quaternion.multiply(quaternion);
Кватернионы позволяют выполнять плавные интерполяции:
THREE.Quaternion.slerp(
startQuat,
endQuat,
cameraEl.object3D.quaternion,
0.1
);
Компонент camera поддерживает параметры:
fov — угол обзораnear — ближняя плоскость отсеченияfar — дальняя плоскостьИзменение через A-Frame:
cameraEl.setAttribute('camera', {
fov: 100,
near: 0.1,
far: 2000
});
Через Three.js:
const cam = cameraEl.getObject3D('camera');
cam.fov = 100;
cam.updateProjectionMatrix();
После изменения fov, near, far
обязательно вызывается updateProjectionMatrix().
Камеру удобно помещать в контейнер:
<a-entity id="rig">
<a-entity camera></a-entity>
</a-entity>
Теперь движение rig перемещает камеру вместе с дочерними
объектами. Это стандартный подход для реализации персонажа.
const rig = document.querySelector('#rig');
rig.object3D.position.z -= 0.05;
Разделение логики:
rig — перемещениеcamera — поворотДля ограничения выхода за пределы сцены можно проверять координаты:
const pos = cameraEl.object3D.position;
if (pos.x > 10) pos.x = 10;
if (pos.x < -10) pos.x = -10;
При использовании физического движка (например, ammo.js или cannon.js) камера может быть связана с физическим телом, однако при ручном управлении важно отключать автоматические контроллеры.
Для плавного перемещения используется линейная интерполяция:
cameraEl.object3D.position.lerp(targetPosition, 0.1);
Для сглаженного вращения:
cameraEl.object3D.quaternion.slerp(targetQuaternion, 0.1);
Такая техника используется при:
Работа с камерой должна начинаться после полной загрузки сцены:
document.querySelector('a-scene').addEventListener('loaded', () => {
const camera = document.querySelector('[camera]');
});
События:
enter-vrexit-vrrenderstartПример:
const scene = document.querySelector('a-scene');
scene.addEventListener('enter-vr', () => {
console.log('VR режим активирован');
});
Ключевые рекомендации:
setAttribute в
tickobject3DdeltaTimeVector3,
Quaternion) внутри циклаНежелательно:
tick() {
const dir = new THREE.Vector3(); // создаётся каждый кадр
}
Лучше вынести в init():
init() {
this.direction = new THREE.Vector3();
}
AFRAME.registerComponent('custom-fps-camera', {
init: function () {
this.keys = {};
this.speed = 0.005;
this.direction = new THREE.Vector3();
window.addEventListener('keydown', e => this.keys[e.code] = true);
window.addEventListener('keyup', e => this.keys[e.code] = false);
},
tick: function (time, deltaTime) {
const camera = this.el;
const velocity = this.speed * deltaTime;
if (this.keys['KeyW']) {
camera.object3D.getWorldDirection(this.direction);
camera.object3D.position.addScaledVector(this.direction, velocity);
}
if (this.keys['KeyS']) {
camera.object3D.getWorldDirection(this.direction);
camera.object3D.position.addScaledVector(this.direction, -velocity);
}
}
});
Такой подход позволяет полностью отказаться от стандартных
wasd-controls и look-controls, обеспечивая
детальный контроль над логикой движения и вращения камеры.