头图

原文参考我的公众号文章 threejs第X人称视角

3D世界,像机的位置和旋转情况决定了我们能看到什么,这篇文章主要讲讲如何简单的实现第一人称/第三人称视角跟随。

1.给物体添加跟随像机(Object3D)

原理

创建一个 Object3D 对象 - 「跟随像机」,并把它添加到移动的物体上,这样「跟随像机」的位置(position)和旋转状态(quaternion)就会跟着移动物体同步更新。
然后再实时把「跟随像机」的位置和旋转状态应用到主camera上。

应用

键盘控制器或者 joyStick 控制器控制如:小车、人物角色在场景中移动、漫游!

实现如下

1.【跟随像机】的创建与位置初始化
let targetMesh = "PlayerMesh";
let slerpDamping = 0.01; //镜头变化动画速度

// 创建跟随像机
let followCamera = new THREE.Object3D();

// 初始化跟随像机位置
followCamera.position.copy(camera.position);
// 或者直接设定为要跟随的物体的位置 followCamera.position.copy(chassisBody.position);
followCamera.position.y = 6;
followCamera.position.z = 16;

// 添加跟随像机-方式1
// scene.add(followCamera);
// followCamera.parent = targetMesh;

// 添加跟随像机-方式2
targetMesh.add(followCamera);
2.【跟随像机】的更新
camera.position.lerp(
  followCamera.getWorldPosition(new THREE.Vector3()),
  slerpDamping
);
camera.quaternion.slerp(
  followCamera.getWorldQuaternion(new THREE.Quaternion()),
  slerpDamping
);

// 或者让camera看向targetMesh的位置
// camera.lookAt(targetMesh.position);
3.切换跟随模式:第一人称 or 第三人称

实现模式切换,只需要动态改变跟随像机的位置即可!

let view = "1"; // 当前模式

document.addEventListener("keyup", (e) => {
  if (e.key == "c") {
    onViewChangeCallback();
  }
});

const onViewChangeCallback = () => {
  if (view == "1") {
    view = "3";
    slerpDamping = 0.01;
    followCamera.position.y = 6;
    followCamera.position.z = 16;
  }

  if (view == "3") {
    view = "1";
    slerpDamping = 1;
    followCamera.position.x = 0;
    followCamera.position.y = 1.5;
    followCamera.position.z = 1;
  }
};

2.将主像机挂载到移动物体上

看代码注释,主要思路是让 camera 挂载到在被控制的对象上,与1类似。

class FollowCamera {
  cameraControl = null; //像机控制器,让像机跟随
  cameraTarget = null; //像机观测点

  construct() {}

  /**初始化像机控制器 */
  setupCameraControl() {
    this.cameraControl = new THREE.Object3D();
    this.cameraControl.add(this.camera);
    this.cameraTarget = new THREE.Vector3(0, 0, 6);

    this.scene.add(this.cameraControl);
  }

  /**让物体和相机保持相对位置 */
  updateCameraControl() {
    if (this.cameraControl && this.cameraTarget) {
      //将像机控制器移动到物体的位置
      this.cameraControl.position.copy(this.playerMesh.position);
      //将像机控制器放在Y轴0的位置,因为物体会上上下下
      this.cameraControl.position.y = 0;
      //把控制器的观测目标点移动到物体的位置
      this.cameraTarget.copy(this.playerMesh.position);
      //再把把控制器的观测目标点沿着Z轴移动6米,总是保持在物体前6米的位置
      this.cameraTarget.z += 6; //保持注视着物体的前6米处(保持相对位置)
      //像机观测着控制器的观测目标点
      this.camera.lookAt(this.cameraTarget);
    }
  }

  handlePlayerMove(elapsedTime) {
    this.playerMesh.rotation.set(0, 0, Math.sin(elapsedTime) * 0.5, "XYZ");

    if (!this.isActive) {
      this.playerMesh.position.y = Math.cos(elapsedTime) * 0.3; //待机时的动画
    } else {
      this.playerMesh.position.z -= 0.02 * elapsedTime; //前进

      if (this.isSpace) {
        this.playerMesh.position.y += 0.01; //攀升
      } else {
        this.playerMesh.position.y -= 0.01; //坠落
      }
    }
  }

  annimate(elapsedTime, deltaTime) {
    if (this.playerMesh) {
      this.handlePlayerMove(elapsedTime);
      this.updateCameraControl();
    }
  }
}

3.像机与物体相对静止

仅处理像机位置变化,根据不同场景使用。物体移动的底层原理就是做矩阵变化,那么想要让相机和物体的距离不变,我们只需要让相机和物体做相同的变化。

const animate = () => {
  const relativeCameraOffset = new THREE.Vector3(0, 5, 10);
  const cameraOffset = relativeCameraOffset.applyMatrix4(
    playerMesh.matrixWorld
  );

  // 更新像机位置
  camera.position.copy(cameraOffset);
  // 始终让相机看向物体
  controls.target = playerMesh.position;
};

Believer
47 声望5 粉丝

无法忍受尘世间的丑 便看不到尘世间的美