vue+threejs写物体事件:键盘事件

506 阅读5分钟

持续创作,加速成长!这是我参与「掘金日新计划 · 10 月更文挑战」的第12天,点击查看活动详情


写在前面

本文用vue+threejs写物体事件:键盘事件,键盘按下WASD,物体前后左右移动。

下面是演示gif:

20221010_115753.gif

代码说明

  1. html

重要的是<div id="THREE65"></div>,要插入threejs的渲染器节点

<template>
  <div class="item">
    <div id="THREE65"></div>
  </div>
</template>
  1. 引入threejs和需要额外引入的模块

OrbitControls轨道控制器,用于鼠标控制界面操作

EffectComposer后期处理

RenderPass后期处理渲染器

OutlinePass勾边处理

import * as THREE from "three";

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

import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { OutlinePass } from "three/examples/jsm/postprocessing/OutlinePass.js";
  1. mounted()中调用initThreejs()

由于vue的生命周期,因为需要根据id获取元素,所以在mounted()中调用initThreejs()

mounted() {
  this.initThreejs();
},
  1. initThreejs()的完整代码和思路

思路

(1)创建场景scene,相机camera,灯光light,渲染器renderer,轨道控制器controls

(2)创建地面ground,物体redMesh, greenMesh, blueMesh(红色立方体,绿色立方体,蓝色立方体)

(3)创建光线投射raycaster,用于选择要移动的物体,objects数组存放响应鼠标拾取的对象,将物体redMesh, greenMesh, blueMesh存放进去,即这三个物体可以选择

(4)创建后期处理composer,添加后期处理渲染器renderPass,添加勾边处理outlinePass

(5)监听鼠标点击事件document.addEventListener("mousedown", onMousedown);,鼠标点击物体后给moveMesh赋值为鼠标点击的物体,并将该物体加到勾边处理对象中

(6)监听键盘落下事件document.addEventListener("keydown", onKeyDown);,按下W则将moveForward(向前)赋值true,按下S则将moveBackward(向后)赋值true,按下A则将moveLeft(向左)赋值true,按下D则将moveRight(向右)赋值true

(7)监听键盘松开事件document.addEventListener("keyup", onKeyUp);,松开W则将moveForward(向前)赋值false,松开S则将moveBackward(向后)赋值false,松开A则将moveLeft(向左)赋值false,松开D则将moveRight(向右)赋值false

(8)帧动画requestAnimationFrame(animate);:通过performance.now();获取两帧之间的时间差,通过direction控制要移动的方向,10 * delta控制移动的距离,可以将10改成其他任意数字,移动的距离就会改变,通过velocity决定要移到的位置,通过moveMesh.position.copy(velocity);将物体的位置移到velocity的位置,至此,就实现了通过键盘上的WASD控制物体前后左右移动

完整代码

initThreejs() {
  let camera, scene, renderer, controls;

  let redMesh, greenMesh, blueMesh, ground; // 所有物体

  let moveMesh; // 要拖拽的物体

  let composer, outlinePass; // 进行勾边的后期处理

  let raycaster, // 光线投射,用于鼠标拾取,返回鼠标在的位置的物体
    pointer, // 二维向量,用于存放鼠标位置
    objects = []; // 用于存放响应鼠标拾取的对象

  let moveForward = false; // 向前
  let moveBackward = false; // 向后
  let moveLeft = false; // 向左
  let moveRight = false; // 向右

  let prevTime = performance.now(); // 返回一个表示从性能测量时刻开始经过的毫秒数
  const velocity = new THREE.Vector3(); // 一个三维向量,用来存放移动的距离
  const direction = new THREE.Vector3(); // 一个三维向量,用来存放移动的方向向量

  init();

  function init() {
    // 创建场景
    scene = new THREE.Scene();
    scene.background = new THREE.Color(0x000000); // 设置场景背景颜色

    // 创建灯光
    const light = new THREE.DirectionalLight(0xffffff); // 平行光
    light.position.set(0.5, 1.0, 0.5).normalize(); // 设置平行光的方向,从(0.5, 1.0, 0.5)->target一般(0, 0, 0)
    scene.add(light); // 将灯光添加到场景中

    // 创建相机
    camera = new THREE.PerspectiveCamera(
      35,
      (window.innerWidth - 201) / window.innerHeight,
      1,
      500
    ); // 透视相机
    camera.position.x = 36;
    camera.position.y = 67; // 设置相机的位置
    camera.position.z = 67;
    scene.add(camera); // 将相机添加到场景中

    // 创建渲染器
    renderer = new THREE.WebGLRenderer({ antialias: true });
    renderer.outputEncoding = THREE.sRGBEncoding;
    renderer.setPixelRatio(window.devicePixelRatio);
    renderer.setSize(window.innerWidth - 201, window.innerHeight);
    document.getElementById("THREE65").appendChild(renderer.domElement);

    document.addEventListener("mousedown", onMousedown); // 监听鼠标落下事件
    document.addEventListener("keydown", onKeyDown); // 监听键盘落下事件
    document.addEventListener("keyup", onKeyUp); // 监听键盘松开事件

    // 光线投射用于进行鼠标拾取(在三维空间中计算出鼠标移过了什么物体),返回值就是鼠标在的物体的集合
    raycaster = new THREE.Raycaster();
    pointer = new THREE.Vector2(); // 定义一个二维向量

    // 创建轨道控制器
    controls = new OrbitControls(camera, renderer.domElement);
    controls.addEventListener("change", render);
    controls.update();

    /**
     * 后期处理
     * OutlinePass就是勾边功能的主要模块
     * 这在文章:用vue+threejs写物体效果:勾边中进行了详细说明,这里就不展开了
     */
    composer = new EffectComposer(renderer);
    const renderPass = new RenderPass(scene, camera);
    composer.addPass(renderPass);
    outlinePass = new OutlinePass(
      new THREE.Vector2(window.innerWidth - 201, window.innerHeight),
      scene,
      camera
    );
    outlinePass.visibleEdgeColor.set(new THREE.Color(0xffff00));
    outlinePass.edgeStrength = 3;
    outlinePass.edgeThickness = 1;
    composer.addPass(outlinePass);

    // 创建地面
    ground = new THREE.Mesh(
      new THREE.BoxGeometry(50, 0.15, 50),
      new THREE.MeshPhongMaterial({
        color: 0x999999,
        depthWrite: false,
        transparent: true,
        opacity: 1,
      })
    );
    ground.receiveShadow = true;
    scene.add(ground);

    // 创建红色立方体
    redMesh = new THREE.Mesh(
      new THREE.BoxGeometry(2, 2, 2),
      new THREE.MeshBasicMaterial({ color: 0xff0000 })
    ); // 网格模型
    redMesh.position.y = 1;
    scene.add(redMesh); // 将这个立方体添加到场景中

    // 创建绿色立方体
    greenMesh = new THREE.Mesh(
      new THREE.BoxGeometry(2, 2, 2),
      new THREE.MeshBasicMaterial({ color: 0x00ff00 })
    ); // 网格模型
    greenMesh.position.y = 1;
    greenMesh.position.x = 5;
    scene.add(greenMesh); // 将这个立方体添加到场景中

    // 创建蓝色立方体
    blueMesh = new THREE.Mesh(
      new THREE.BoxGeometry(2, 2, 2),
      new THREE.MeshBasicMaterial({ color: 0x0000ff })
    ); // 网格模型
    blueMesh.position.y = 1;
    blueMesh.position.x = 6;
    blueMesh.position.z = 9;
    scene.add(blueMesh); // 将这个立方体添加到场景中

    objects.push(redMesh, greenMesh, blueMesh); // 将要拖拽的物体添加到光线投射响应的对象中

    animate();
  }

  function onMousedown(event) {
    if (moveMesh) {
      outlinePass.selectedObjects = [];
      moveMesh = null;
      render();
    } else {
      pointer.set(
        (event.offsetX / (window.innerWidth - 201)) * 2 - 1,
        -(event.offsetY / window.innerHeight) * 2 + 1
      ); // 将鼠标位置归一化为设备坐标。x 和 y 方向的取值范围是 (-1 to +1)

      raycaster.setFromCamera(pointer, camera); // 通过相机和鼠标位置更新射线

      const intersects = raycaster.intersectObjects(objects, false); // 获取光线投射在地面上的物体
      if (intersects.length > 0) {
        const intersect = intersects[0];
        moveMesh = intersect.object;
        outlinePass.selectedObjects = [moveMesh];
        render();
      }
    }
  }

  function onKeyDown(event) {
    switch (event.code) {
      case "ArrowUp": // 按向上箭头
        moveForward = true;
        break;
      case "KeyW": // 按W
        moveForward = true;
        break;

      case "ArrowLeft": // 按向左箭头
        moveLeft = true;
        break;
      case "KeyA": // 按A
        moveLeft = true;
        break;

      case "ArrowDown": // 按向下箭头
        moveBackward = true;
        break;
      case "KeyS": // 按S
        moveBackward = true;
        break;

      case "ArrowRight": // 按向右箭头
        moveRight = true;
        break;
      case "KeyD": // 按D
        moveRight = true;
        break;
    }
  }

  function onKeyUp(event) {
    switch (event.code) {
      case "ArrowUp":
        moveForward = false;
        break;
      case "KeyW":
        moveForward = false;
        break;

      case "ArrowLeft":
        moveLeft = false;
        break;
      case "KeyA":
        moveLeft = false;
        break;

      case "ArrowDown":
        moveBackward = false;
        break;
      case "KeyS":
        moveBackward = false;
        break;

      case "ArrowRight":
        moveRight = false;
        break;
      case "KeyD":
        moveRight = false;
        break;
    }
  }

  function animate() {
    requestAnimationFrame(animate);
    const time = performance.now(); // 返回一个表示从性能测量时刻开始经过的毫秒数

    if (moveMesh) {
      const delta = (time - prevTime) / 1000;

      direction.z = Number(moveForward) - Number(moveBackward); // z轴的方法,正为向前,负为向后
      direction.x = Number(moveLeft) - Number(moveRight); // x轴的方向,正为向左,负为向右
      direction.normalize(); // 转成方向向量

      if (moveForward || moveBackward) {
        velocity.z -= direction.z * 10 * delta;
      }
      if (moveLeft || moveRight) {
        velocity.x -= direction.x * 10 * delta;
      }
      velocity.y = moveMesh.position.y; // y方向不变

      moveMesh.position.copy(velocity); // 将物体的位置移到velocity的位置
    }

    prevTime = time;

    render();
  }

  function render() {
    composer.render();
  }
},

写在最后

以上就是所有的代码和说明。