vue写three.js例子-控制-指针锁定控制器

361 阅读3分钟

携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第4天,点击查看活动详情


今天的例子是three.js指针锁定控制器的使用,用于第一人称漫游的实现,以下是演示gif

20220816_105414.gif

先写一个容器

一个放3d的容器THREE13

一个遮罩层blocker、instructions,里面显示一些提示信息,点击遮罩后,遮罩隐藏,可在3d视图上进行键盘鼠标互动。

<div id="THREE13"></div>
<div id="blocker">
  <div id="instructions">
    <p style="font-size: 36px">Click to play</p>
    <p>
      Move: WASD<br />
      Jump: SPACE<br />
      Look: MOUSE
    </p>
  </div>
</div>

引入要用的指针锁定控制器

import { PointerLockControls } from "three/examples/jsm/controls/PointerLockControls.js";

然后一个mounted方法

mounted() {
  this.initThreejs();
},

下面是initThreejs的代码

定义需要的变量

camera:相机

scene:场景

renderer:渲染器

controls:控制器

objects:存放场景中的物体

raycaster:光线投射,用于鼠标拾取

moveForward:前

moveBackward:后

moveLeft:左

moveRight:右

canJump:跳

performance.now():返回一个精确到毫秒的时间戳

velocity:移动的速度,每时间戳差移动的距离

direction:移动的方向

let camera, scene, renderer, controls;

const objects = [];

let raycaster;

let moveForward = false;
let moveBackward = false;
let moveLeft = false;
let moveRight = false;
let canJump = false;

let prevTime = performance.now();

const velocity = new THREE.Vector3();
const direction = new THREE.Vector3();

init();
animate();

下面是init的代码

相机

透视相机:PerspectiveCamera(相机视锥体垂直视野角度,视锥体长宽比,视锥体近端面,视锥体远端面)

camera = new THREE.PerspectiveCamera(
  75,
  (window.innerWidth - 201) / window.innerHeight,
  1,
  1000
);
camera.position.y = 10;

场景

设置背景颜色和雾的效果

scene = new THREE.Scene();
scene.background = new THREE.Color(0xffffff);
scene.fog = new THREE.Fog(0xffffff, 0, 750);

光线

半球光:HemisphereLight(天空中发出光线的颜色, 地面发出光线的颜色,光照强度)

const light = new THREE.HemisphereLight(0xeeeeff, 0x777788, 0.75);
light.position.set(0.5, 1, 0.75);
scene.add(light);

指针锁定控制器

指针锁定控制器:PointerLockControls(渲染场景的相机,用于事件监听的html元素)

controls.lock():激活指针锁定

指针锁定被激活时触发事件,被锁定后instructions和blocker元素不显示

指针锁定退出时触发事件,退出后blocker和instructions显示

controls.getObject():获取相机

并将相机添加到场景

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

const blocker = document.getElementById("blocker");
const instructions = document.getElementById("instructions");

instructions.addEventListener("click", function () {
  controls.lock();
});

controls.addEventListener("lock", function () {
  instructions.style.display = "none";
  blocker.style.display = "none";
});

controls.addEventListener("unlock", function () {
  blocker.style.display = "block";
  instructions.style.display = "";
});

scene.add(controls.getObject());

键盘按键监听

w:前,a:左,s:后,d:右

const onKeyDown = function (event) {
  switch (event.code) {
    case "KeyW":
      moveForward = true;
      break;
    case "KeyA":
      moveLeft = true;
      break;
    case "KeyS":
      moveBackward = true;
      break;
    case "KeyD":
      moveRight = true;
      break;
    case "Space":
      if (canJump === true) velocity.y += 350;
      canJump = false;
      break;
  }
};

键盘松掉监听

const onKeyUp = function (event) {
  switch (event.code) {
    case "KeyW":
      moveForward = false;
      break;
    case "KeyA":
      moveLeft = false;
      break;
    case "KeyS":
      moveBackward = false;
      break;
    case "KeyD":
      moveRight = false;
      break;
  }
};

document.addEventListener("keydown", onKeyDown);
document.addEventListener("keyup", onKeyUp);

光线投射

光线投射:Raycaster(光线投射的原点向量,光线投射的方向向量,返回的所有结果比near远,返回的所有结果都比far近)

raycaster = new THREE.Raycaster(
  new THREE.Vector3(),
  new THREE.Vector3(0, -1, 0),
  0,
  10
);

创建平面

平面:PlaneGeometry(宽,高,宽度分段数,高度分段数)

平面绕x轴旋转PI/2

做出地面的效果

let floorGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100);
floorGeometry.rotateX(-Math.PI / 2);

const floorMaterial = new THREE.MeshBasicMaterial({
  color: new THREE.Color("green"),
});

const floor = new THREE.Mesh(floorGeometry, floorMaterial);
scene.add(floor);

创建物体

用于具有镜面高光的光泽表面的材质:MeshPhongMaterial()

  • specular:材质的高光颜色
  • flatShading:定义材质是否使用平面着色进行渲染

创建500个正方体

const boxGeometry = new THREE.BoxGeometry(20, 20, 20).toNonIndexed();

for (let i = 0; i < 500; i++) {
  const boxMaterial = new THREE.MeshPhongMaterial({
    specular: 0xffffff,
    flatShading: true,
    color: new THREE.Color("skyblue"),
  });

  const box = new THREE.Mesh(boxGeometry, boxMaterial);
  box.position.x = Math.floor(Math.random() * 20 - 10) * 20;
  box.position.y = Math.floor(Math.random() * 20) * 20 + 10;
  box.position.z = Math.floor(Math.random() * 20 - 10) * 20;

  scene.add(box);
  objects.push(box);
}

渲染器

renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(window.devicePixelRatio);
renderer.setSize(window.innerWidth - 201, window.innerHeight);
document.getElementById("THREE13").appendChild(renderer.domElement);

下面是animate的代码:

performance.now():当前时间戳

target.copy():复制所传入属性到target上

moveForward:平行于xz平面,向前移动摄像机,有正负

moveRight:平行于xz平面,向侧面移动摄像机,有正负

function animate() {
    requestAnimationFrame(animate);

    const time = performance.now();

    /**
     * 如果指针锁定被激活
     */
    if (controls.isLocked === true) {

      raycaster.ray.origin.copy(controls.getObject().position); // 设置射线的原点位置为相机的位置
      raycaster.ray.origin.y -= 10; // 原点的y一直变化

      const intersections = raycaster.intersectObjects(objects, false); // 检查相交的物体

      const onObject = intersections.length > 0; // 判断是否有相交的物体

      const delta = (time - prevTime) / 1000;

      velocity.x -= velocity.x * 10.0 * delta;
      velocity.z -= velocity.z * 10.0 * delta;

      direction.z = Number(moveForward) - Number(moveBackward); // 往前=1,往后=-1
      direction.x = Number(moveRight) - Number(moveLeft); // 往右=1,往左=-1
      direction.normalize(); // 确保了所有方向上的一致运动

      if (moveForward || moveBackward) {
        velocity.z -= direction.z * 400.0 * delta;
      }

      if (moveLeft || moveRight) {
        velocity.x -= direction.x * 400.0 * delta;
      }

      velocity.y -= 9.8 * 100.0 * delta; // 100.0 = mass
      if (onObject === true) {
        velocity.y = Math.max(0, velocity.y);
        canJump = true;
      }

      controls.moveRight(-velocity.x * delta);
      controls.moveForward(-velocity.z * delta);

      controls.getObject().position.y += velocity.y * delta;
      if (controls.getObject().position.y < 10) {
        velocity.y = 0;
        controls.getObject().position.y = 10;

        canJump = true;
      }
    }

    prevTime = time;

    renderer.render(scene, camera);
 }