Как правило, для камеры от третьего лица вы постоянно нацеливаете камеру на игрока (или на какое-то смещение от игрока, или на какой-либо объект, прикрепленный к плееру)
В таком случае камера будет следить за игроком с течением времени.
В приведенном ниже коде есть cameraRig
, который следует за игроком. Там есть камера, поэтому нам не нужно ничего делать, чтобы держать камеру над землей. Точно так же есть camTarget
, который присоединен к игроку. Так выглядит камера примерно на уровне плеч.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const keys = {};
const scene = new THREE.Scene();
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 500;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// put the camera 5 units above the rig
const cameraRig = new THREE.Object3D();
cameraRig.add(camera);
camera.position.y = 5;
scene.add(cameraRig);
cameraRig.position.z = 5;
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const size = 200;
const divisions = 100;
const gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);
}
const boxWidth = 0.5;
const boxHeight = 2;
const boxDepth = 0.5;
const geometry = new THREE.CylinderGeometry(boxWidth, boxDepth, boxHeight);
const material = new THREE.MeshPhongMaterial({color:'red'});
const cube = new THREE.Mesh(geometry, material);
const player = new THREE.Object3D();
const camTarget = new THREE.Object3D();
cube.position.y = boxHeight / 2; // move cube above ground
player.add(cube);
camTarget.position.y = boxHeight * 3 / 2; // target 2/3ds up the player
player.add(camTarget);
scene.add(player);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const moveDir = new THREE.Vector3();
const camTargetPos = new THREE.Vector3();
let then = 0;
function render(now) {
now *= 0.001;
deltaTime = now - then;
then = now;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
// left, right, a, d
const dx = ((keys[37] || keys[65]) ? 1 : 0) +
((keys[39] || keys[68]) ? -1 : 0);
// up, down, w, s
const dy = ((keys[38] || keys[87]) ? 1 : 0) +
((keys[40] || keys[83]) ? -1 : 0);
const playerMoveSpeed = 10; // units per second
camera.getWorldDirection(moveDir);
moveDir.y = 0; // no up down movement
moveDir.normalize();
// move player forward/back
player.position.addScaledVector(moveDir, dy * playerMoveSpeed * deltaTime);
// rotate direction 90 degrees
const t = moveDir.x;
moveDir.x = moveDir.z;
moveDir.z = -t;
// move player left/right
player.position.addScaledVector(moveDir, dx * playerMoveSpeed * deltaTime);
// if the cameraRig is too far from
// player then move it
const maxDistance = 6;
const maxCamMoveSpeed = 0.015;
const distance = cameraRig.position.distanceTo(player.position);
if (distance > maxDistance) {
const amount = maxCamMoveSpeed;
cameraRig.position.lerp(player.position, amount);
}
camTarget.getWorldPosition(camTargetPos);
camera.lookAt(camTargetPos);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('keydown', (e) => {
e.preventDefault();
keys[e.keyCode] = true;
});
window.addEventListener('keyup', (e) => {
e.preventDefault();
keys[e.keyCode] = false;
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.min.js"></script>
<canvas id="c" tabindex="0"></canvas>
Камеры от третьего лица могут быть трудными. Тот, что выше, слишком прост. Вы можете думать об этом так, как будто cameraRig перетаскивается на строку позади проигрывателя. Если вы делаете резервную копию, камера не двигается. Он перемещается только в том случае, если камера находится на максимальном расстоянии.
Обычно вместо этого помещают камеру на палку, поэтому, если вы идете назад, палка отталкивает камеру назад.
function main() {
const canvas = document.querySelector('#c');
const renderer = new THREE.WebGLRenderer({canvas});
const keys = {};
const scene = new THREE.Scene();
const fov = 75;
const aspect = 2; // the canvas default
const near = 0.1;
const far = 500;
const camera = new THREE.PerspectiveCamera(fov, aspect, near, far);
// put the camera 5 units above the rig
const cameraRig = new THREE.Object3D();
cameraRig.add(camera);
camera.position.y = 5;
scene.add(cameraRig);
cameraRig.position.z = 5;
{
const color = 0xFFFFFF;
const intensity = 1;
const light = new THREE.DirectionalLight(color, intensity);
light.position.set(-1, 2, 4);
scene.add(light);
}
{
const size = 200;
const divisions = 100;
const gridHelper = new THREE.GridHelper(size, divisions);
scene.add(gridHelper);
}
const boxWidth = 0.5;
const boxHeight = 2;
const boxDepth = 0.5;
const geometry = new THREE.CylinderGeometry(boxWidth, boxDepth, boxHeight);
const material = new THREE.MeshPhongMaterial({color:'red'});
const cube = new THREE.Mesh(geometry, material);
const player = new THREE.Object3D();
const camTarget = new THREE.Object3D();
cube.position.y = boxHeight / 2; // move cube above ground
player.add(cube);
camTarget.position.y = boxHeight * 3 / 2; // target 2/3ds up the player
player.add(camTarget);
scene.add(player);
function resizeRendererToDisplaySize(renderer) {
const canvas = renderer.domElement;
const width = canvas.clientWidth;
const height = canvas.clientHeight;
const needResize = canvas.width !== width || canvas.height !== height;
if (needResize) {
renderer.setSize(width, height, false);
}
return needResize;
}
const moveDir = new THREE.Vector3();
const camTargetPos = new THREE.Vector3();
const rigTargetPos = new THREE.Vector3();
function moveCameraToDistance(desiredDistance) {
const maxCamMoveSpeed = 0.02;
// delta from player to rig
rigTargetPos.subVectors(cameraRig.position, player.position);
// remove up/down
rigTargetPos.y = 0; // no up/down
// make unit vector
rigTargetPos.normalize();
// make desiredDistance long
rigTargetPos.multiplyScalar(desiredDistance);
// add player position
rigTargetPos.add(player.position);
// move rig toward that position
const curDistance = cameraRig.position.distanceTo(player.position);
cameraRig.position.lerp(
rigTargetPos,
THREE.MathUtils.lerp(
maxCamMoveSpeed,
1,
THREE.MathUtils.clamp(1 - curDistance / desiredDistance, 0, 1)));
}
let then = 0;
function render(now) {
now *= 0.001;
deltaTime = now - then;
then = now;
if (resizeRendererToDisplaySize(renderer)) {
const canvas = renderer.domElement;
camera.aspect = canvas.clientWidth / canvas.clientHeight;
camera.updateProjectionMatrix();
}
// left, right, a, d
const dx = ((keys[37] || keys[65]) ? 1 : 0) +
((keys[39] || keys[68]) ? -1 : 0);
// up, down, w, s
const dy = ((keys[38] || keys[87]) ? 1 : 0) +
((keys[40] || keys[83]) ? -1 : 0);
const playerMoveSpeed = 10; // units per second
camera.getWorldDirection(moveDir);
moveDir.y = 0; // no up down movement
moveDir.normalize();
// move player forward/back
player.position.addScaledVector(moveDir, dy * playerMoveSpeed * deltaTime);
// rotate direction 90 degrees
const t = moveDir.x;
moveDir.x = moveDir.z;
moveDir.z = -t;
// move player left/right
player.position.addScaledVector(moveDir, dx * playerMoveSpeed * deltaTime);
// keep camera at distance
const minDistance = 4;
const maxDistance = 6;
const distance = cameraRig.position.distanceTo(player.position);
if (distance > maxDistance) {
moveCameraToDistance(maxDistance);
} else if (distance < minDistance) {
moveCameraToDistance(minDistance);
}
// compute point from player in direction of rig
// at desired distance
camTarget.getWorldPosition(camTargetPos);
camera.lookAt(camTargetPos);
renderer.render(scene, camera);
requestAnimationFrame(render);
}
requestAnimationFrame(render);
window.addEventListener('keydown', (e) => {
e.preventDefault();
keys[e.keyCode] = true;
});
window.addEventListener('keyup', (e) => {
e.preventDefault();
keys[e.keyCode] = false;
});
}
main();
body {
margin: 0;
}
#c {
width: 100vw;
height: 100vh;
display: block;
}
<script src="https://threejsfundamentals.org/threejs/resources/threejs/r113/build/three.js"></script>
<canvas id="c" tabindex="0"></canvas>
Затем вам нужно разобраться с такими вещами, как вещи, попадающие между камерой и плеером. (некоторые игры постепенно исчезают) Вы также должны иметь дело с, скажем, ходить через дверь. Какое-то время игрок будет находиться на одной стороне двери, а камера - на другой (или, скорее, на противоположных сторонах дверного проема, поэтому камера опускается, чтобы она могла видеть сквозь дверь? Стена исчезает? Камера прыгает? через дверь и начните смотреть с некоторых других позиций. Ходят слухи, что в Mario 64 один программист работал над камерой целый год и ничего больше.)
Обратите внимание, что часть приведенного выше кода работает только в особом случае, когда someObject.position
- это положение в мировом пространстве (у объекта нет родительских объектов, или, если это так, все родители имеют положение = 0,0,0 вращения = 0,0,0, масштаб = 1,1,1). Если у объекта есть родители, то вам нужно получить мировую позицию с помощью
const wp = new THREE.Vector3();
someObject.getWorldPosition(wp);
. И если вы хотите применить мировую позицию, вам нужно будет проделать больше работы, чтобы сделать положение относительно родитель. А пока, для простоты, я просто использовал объекты, чье мировое положение - это их положение.