threejs配合gsap动画库,实现小车跑动

937 阅读3分钟

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

一、前言

接上期raycaster选取物品后,今天带大家浅浅试用下gasp动画库,听说还挺nb的这个库。

二、过程

1、 引入

首先还是老套路

npm install gsap

然后在项目里按需引入

import gsap from "gsap"

2、动画函数

首先我们来完成一下小车的动画函数carAnimate

核心就是gsap.to函数

gsap.to(参数1,参数2) 就是告诉动画对象,最终要达到的运动状态

  • 参数1告知需要绑定哪个动画对象
  • 参数2就是要告知动画最终效果的对象:动画时长、是旋转还是位移变化、或者其它属性的变化

具体函数如下,使用gsap.to函数,确定好动画效果,并绑定了两个函数onComplete和onStart,分别会在动画开始时和动画完成时调用

// 车辆动画
let animateGasp: gsap.core.Tween | null
function carAnimate(): void {
  animateGasp = gsap.to(car.position, {
    z: 4, // 这里是小车最终会停下的位置
    duration: 5, // 开始动画到结束动画一共5秒
    ease: "power1.inOut",
    // 设置重复次数 -1无限次, 0就是1次,1就是2次
    repeat: 0,
    // 往返运动
    yoyo: true,
    // 延迟动画
    delay: 2, // 延迟辆2秒
    onComplete: () => {
      console.log("动画完成了");
      // 碰到了,碰撞函数在下一小节实现
      if (onIntersect()) {
        // 撞倒
        gsap.to(build.rotation, {
          x: -1.57,
          duration: 0,
          ease: 'power1.inOut',
          repeat: 0
        })
      }
    },
    onStart: () => {
      console.log("动画开始了");
    }
  })
}

3、检测碰撞函数

这里的碰撞函数,我是打算实现小车撞倒大楼的效果才加的

下面说到的顶点对象,我也只采取了小车的其中一个点作为检测

car.children[0].geometry.attributes.position

image.png

函数内容如下,其中car对象是小车模型,build对象是大楼模型,其余基本可以直接copy了用

// 碰撞检测函数
function onIntersect() {
  // 声明一个变量用来表示是否碰撞
  let bool = false

  // .position 对象局部位置
  // .clone() 复制一个新的三维向量
  // 网格中心 世界坐标
  const centerCoord = car.position.clone()
  // 获取网格中 几何对象的顶点对象这里我只取了其中一个点作为碰撞检测
  // 而且结构也需要根据实际情况进行调整
  const position = car.children[0].geometry.attributes.position
  // 顶点三维向量
  const vertices = []
  // .count 矢量个数
  for (let i = 0; i < position.count; i++) {
    // .getX() 获取给定索引的矢量的第一维元素
    vertices.push(new THREE.Vector3(position.getX(i), position.getY(i), position.getZ(i)))
  }

  for (let i = 0; i < vertices.length; i++) {
    // .matrixWorld 物体的世界坐标变换 -- 物体旋转、位移 的四维矩阵
    // .applyMatrix4() 将该向量乘以四阶矩阵
    // 获取世界坐标下 网格变换后的坐标
    let vertexWorldCoord = vertices[i].clone().applyMatrix4(car.matrixWorld)

    // .sub(x) 从该向量减去x向量
    // 获得由中心指向顶点的向量
    var dir = vertexWorldCoord.clone().sub(centerCoord)

    // .normalize() 将该向量转换为单位向量
    // 发射光线 centerCoord 为投射的原点向量  dir 向射线提供方向的方向向量
    let raycaster = new THREE.Raycaster(centerCoord, dir.clone().normalize())

    // 放入要检测的 物体build,也就是大楼,返回相交物体
    let intersects = raycaster.intersectObjects([build], true)

    if (intersects.length > 0) {
      // intersects[0].distance:射线起点与交叉点之间的距离(交叉点:射线和模型表面交叉点坐标)
      // dir.length():几何体顶点和几何体中心构成向量的长度
      // intersects[0].distance小于dir.length() 表示物体相交
      if (intersects[0].distance < dir.length()) {
        bool = true
      }
    }
  }
  return bool
}

4、整体逻辑

点击元素,调用carAnimate

<div @click="carAnimate">点击启用车辆动画</div>

延迟2s后,小车开动,5s后停下,触发onComplete函数,检测小车与大楼是否相撞,相撞了触发大楼倒下动画

放个效果图给大家伙看看

hi_0_小车撞大楼.gif

三、总结

由于是刚刚上手threejs,这个检测碰撞的时机还抓得不够准,可以看到小车已经和大楼重合了,但是得等动画结束,才会触发onComplete函数,从而去调用碰撞检测,但是放render函数里每帧刷新又比较耗性能。

后面得想想法子解决一下这个问题。

今天就时间比较仓促,就只带大家体验了gsap.to函数,还有其他函数我们等下期再一起试试吧。

ps:我是地霊殿__三無,希望本文能对你有所帮助。

Snipaste_2022-07-19_15-30-26.jpg