Threejs 传送阵(魔法阵)效果实现


传送阵效果

一、创建底部自转的法阵

单纯创建个圆再把贴图加上就行,贴图如下,下载后记得修改贴图名字
在这里插入图片描述

	this.circleTexturePath = 'assets/images/magic.png'

    this.circleTexture = this.textureLoader.load(this.circleTexturePath)
    
	let circleGeo = new CircleBufferGeometry(1, 32)
    let circleMat = new MeshBasicMaterial({
      map: this.circleTexture,
      transparent: true,
      side: DoubleSide,
      depthWrite: false
    })
    let circle = new Mesh(circleGeo, circleMat)
    circle.rotateX(-Math.PI / 2)
    this.circles.push(circle)
    teleporter.add(circle)

在这里插入图片描述

然后让它转起来,下面这个函数需要每帧更新

/**
   * 更新传送阵底部的圆
   */
  updateCircles() {
    for (var i = 0; i < this.circles.length; i++) {
      this.circles[i].rotateZ(0.02)
    }
  }

在这里插入图片描述

二、添加一圈立体的光晕

这个也简单,创建个没有顶部和底部的圆柱,附上贴图就行,贴图如下
在这里插入图片描述

  this.aroundTexturePath = 'assets/images/guangyun.png'
  this.aroundTexture = this.textureLoader.load(this.aroundTexturePath)
    
  //getCylinderGeo函数是自己写的一个方法,具体代码可看下面的完整代码部分
  let aroundGeo = this.getCylinderGeo(0.5, 2)
  let aroundMat = new MeshBasicMaterial({
      map: this.aroundTexture,
      transparent: true,
      side: DoubleSide,
      wireframe: false,
      depthWrite: false
  })
  let around = new Mesh(aroundGeo, aroundMat)
  this.arounds.push(around)
  teleporter.add(around)

下图为不加贴图并显示线框的圆柱体
创建的圆柱体(不加贴图)
下图为正常显示的加了贴图的效果
圆柱体加上贴图
然后让这个圆柱也旋转起来,下面这个函数也需要每帧更新

 /**
   * 更新传送阵四周的光壁
   */
  updateArounds() {
    for (var i = 0; i < this.arounds.length; i++) {
      this.arounds[i].rotateY(0.01)
    }
  }

此时效果如下
在这里插入图片描述
看上面的gif,有那么点感觉了哈,不过还不够酷炫,那就再加个光柱,反方向旋转,分别修改上面的代码如下

  this.aroundTexturePath = 'assets/images/guangyun.png'
  this.aroundTexture = this.textureLoader.load(this.aroundTexturePath)
    
  //getCylinderGeo函数是自己写的一个方法,具体代码可看下面的完整代码部分
  let aroundGeo = this.getCylinderGeo(0.5, 2)
  let aroundMat = new MeshBasicMaterial({
      map: this.aroundTexture,
      transparent: true,
      side: DoubleSide,
      wireframe: false,
      depthWrite: false
  })
  let around = new Mesh(aroundGeo, aroundMat)
  this.arounds.push(around)
  teleporter.add(around)

  //复制出来第二个光柱
  let around2 = around.clone()
  around2.userData.aroundScaleOffset = 0.01 //下一步使用
  teleporter.add(around2)
  this.arounds2.push(around2)

更新函数修改为如下

 /**
   * 更新传送阵四周的光壁
   */
   updateArounds() {
    for (var i = 0; i < this.arounds.length; i++) {
      this.arounds[i].rotateY(0.01)
    }
    for (var i = 0; i < this.arounds2.length; i++) {
      this.arounds2[i].rotateY(-0.01)
    }
  }

在这里插入图片描述
还可以再进一步修改下缩放,将更新函数修改为如下

/**
   * 更新传送阵四周的光壁
   */
  updateArounds() {
    for (var i = 0; i < this.arounds.length; i++) {
      this.arounds[i].rotateY(0.01)
    }
    for (var i = 0; i < this.arounds2.length; i++) {
      this.arounds2[i].rotateY(-0.01)
      if (this.arounds2[i].scale.x < 0.9 || this.arounds2[i].scale.x > 1.4) {
        this.arounds2[i].userData.aroundScaleOffset *= -1
      }
      this.arounds2[i].scale.x -= this.arounds2[i].userData.aroundScaleOffset
      this.arounds2[i].scale.z -= this.arounds2[i].userData.aroundScaleOffset
    }
  }

在这里插入图片描述

三、添加粒子效果

不加粒子效果总感觉不够酷炫,所以再加少许粒子,下面几张图是用PS制作的,有点粗糙了。。
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

代码如下:

  createTeleporter() {

	....
	
	//创建 10 * 4 个粒子
	for (var j = 0; j < 10; j++) {
      for (var i = 0; i < this.pointTexture.length; i++) {
        let sprite = this.getPoints(this.params.pointRangeRadius, this.params.height, this.pointTexture[i])
        this.particles.push(sprite)
        teleporter.add(sprite)
      }
    }
  }

  /**
   * 获取点云效果   为了能控制每一个粒子的大小和位置,这里的每一个Points对象只包含一个点,要是粒子放太多了可能有性能问题
   * @param {*} radius
   * @param {*} height
   * @param {*} texturePath
   * @returns
   */
  getPoints(radius, height, texture) {
    const geometry = new BufferGeometry()
    const vertices = [0, 0, 0]

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))

    const material = new PointsMaterial({
      size: Math.random() * (0.15 - 0.04) + 0.04,
      map: texture,
      blending: AdditiveBlending,
      depthTest: false,
      transparent: true,
      opacity: 0.2 + Math.random() * 0.8
    })

    const particle = new Points(geometry, material)
    //粒子上升速度
    particle.userData.floatSpeed = 0.001 + Math.random() * 0.01
    particle.userData.radius = radius
    particle.position.x = Math.random() * radius * 2 - radius
    particle.position.y = Math.random() * height
    particle.position.z = Math.random() * radius * 2 - radius
    return particle
  }

在这里插入图片描述
再让这些粒子按不同速度上升,到一定高度时又回到最低点,循环往复,更新函数如下

  /**
   * 更新光点效果
   */
  updatePatical() {
    for (var i = 0; i < this.particles.length; i++) {
      this.particles[i].position.y += this.particles[i].userData.floatSpeed
      
      if (this.particles[i].position.y >= 2) {
        //更新位置,y=0,x,z随机
        this.particles[i].position.y = 0
        this.particles[i].position.x =
          Math.random() * this.particles[i].userData.radius * 2 - this.particles[i].userData.radius
        this.particles[i].position.z =
          Math.random() * this.particles[i].userData.radius * 2 - this.particles[i].userData.radius

        //随机上升速度
        this.particles[i].userData.floatSpeed = 0.001 + Math.random() * this.params.pointFloatSpeed
      }
    }
  }

在这里插入图片描述

四、完整代码

贴图资源也可从这获取
1、引入库 import {TeleporterManager } from “./TeleporterManager.js”
2、创建实例 let teleporterManager = new TeleporterManager(this.scene)
3、创建传送阵 teleporterManager.createTeleporter()
4、在 requestAnimationFrame 更新的函数里调用传送阵更新函数 teleporterManager.update()

import {
  CircleBufferGeometry,
  TextureLoader,
  Mesh,
  MeshBasicMaterial,
  DoubleSide,
  BufferAttribute,
  BufferGeometry,
  Float32BufferAttribute,
  PointsMaterial,
  AdditiveBlending,
  Points,
  Object3D,
  Vector3
} from 'three'

export class TeleporterManager {
  constructor(scene) {
	this.init(scene)
  }

  init(scene) {
    this.scene = scene

    this.params = {
      segment: 32,
      circleRadius: 1,
      circleRotateSpeed: 0.02,
      aroundRadius: 0.5,
      aroundRotateSpeed: 0.01,
      aroundScaleOffset: 0.01,
      height: 2,
      pointRangeRadius: 0.8,
      pointMinSize: 0.04,
      pointMaxSize: 0.15,
      pointFloatSpeed: 0.01,
      pointTexturePath: [
        'assets/images/point1.png',
        'assets/images/point2.png',
        'assets/images/point3.png',
        'assets/images/point4.png'
      ]
    }

    this.textureLoader = new TextureLoader()
    this.circleTexturePath = 'assets/images/magic.png'
    this.aroundTexturePath = 'assets/images/guangyun.png'
    this.textureLoader.load(this.circleTexturePath, texture => {
      this.circleTexture = texture
    })
    this.textureLoader.load(this.aroundTexturePath, texture => {
      this.aroundTexture = texture
    })
    this.pointTexture = []
    for (var i = 0; i < this.params.pointTexturePath.length; i++) {
      this.textureLoader.load(this.params.pointTexturePath[i], texture => {
        this.pointTexture.push(texture)
      })
    }

    this.circles = []
    this.arounds = []
    this.arounds2 = []
    this.particles = []
    this.teleporters = []
  }

  update() {
    this.updateCircles()
    this.updateArounds()
    this.updatePatical()
  }

  clear() {

    for (var i = 0; i < this.teleporters.length; i++) {
      this.clearModel(this.teleporters[i])
    }

    this.scene = null
    this.teleporters = []
    this.circles = []
    this.arounds = []
    this.arounds2 = []
    this.particles = []
    this.textureLoader = null
    this.circleTexture = null
    this.aroundTexture = null
    this.pointTexture = []
  }
  
  clearModel(model) {
    if (!model) return
    // dispose geometry
    model.traverse(node => {
      if (!node.isMesh) return
      node.geometry.dispose()
    })
  }

  /**
   * 创建一个传送阵
   */
  createTeleporter() {
    let teleporter = new Object3D()
    teleporter._type = 'TeleporterHelper'
    this.scene.add(teleporter)
    this.teleporters.push(teleporter)

    let circleGeo = new CircleBufferGeometry(this.params.circleRadius, this.params.segment)
    let circleMat = new MeshBasicMaterial({
      map: this.circleTexture,
      transparent: true,
      side: DoubleSide,
      depthWrite: false
    })
    let circle = new Mesh(circleGeo, circleMat)
    circle.rotateX(-Math.PI / 2)
    this.circles.push(circle)
    teleporter.add(circle)

    let aroundGeo = this.getCylinderGeo(this.params.aroundRadius, this.params.height)
    let aroundMat = new MeshBasicMaterial({
      map: this.aroundTexture,
      transparent: true,
      side: DoubleSide,
      wireframe: false,
      depthWrite: false
    })
    let around = new Mesh(aroundGeo, aroundMat)
    this.arounds.push(around)
    teleporter.add(around)

    let around2 = around.clone()
    around2.userData.aroundScaleOffset = this.params.aroundScaleOffset
    around2.userData._type = around2._type
    teleporter.add(around2)
    this.arounds2.push(around2)

    for (var j = 0; j < 10; j++) {
      for (var i = 0; i < this.pointTexture.length; i++) {
        let sprite = this.getPoints(this.params.pointRangeRadius, this.params.height, this.pointTexture[i])
        this.particles.push(sprite)
        teleporter.add(sprite)
      }
    }
  }

  /**
   * 获取点云效果
   * @param {*} radius
   * @param {*} height
   * @param {*} texturePath
   * @returns
   */
  getPoints(radius, height, texture) {
    const geometry = new BufferGeometry()
    const vertices = [0, 0, 0]

    geometry.setAttribute('position', new Float32BufferAttribute(vertices, 3))

    const material = new PointsMaterial({
      size: Math.random() * (this.params.pointMaxSize - this.params.pointMinSize) + this.params.pointMinSize,
      map: texture,
      blending: AdditiveBlending,
      depthTest: false,
      transparent: true,
      opacity: 0.2 + Math.random() * 0.8
    })

    const particle = new Points(geometry, material)
    particle.userData.floatSpeed = 0.001 + Math.random() * this.params.pointFloatSpeed
    particle.userData.radius = radius
    particle.position.x = Math.random() * radius * 2 - radius
    particle.position.y = Math.random() * height
    particle.position.z = Math.random() * radius * 2 - radius
    return particle
  }

  /**
   * 获取圆柱几何体
   * @param {*} radius 半径
   * @param {*} height 高度
   * @param {*} segment 分段数
   * @returns
   */
  getCylinderGeo(radius = 1, height = 1, segment = 32) {
    let bottomPos = []
    let topPos = []
    let bottomUvs = []
    let topUvs = []
    let angleOffset = (Math.PI * 2) / segment
    let uvOffset = 1 / (segment - 1)
    for (var i = 0; i < segment; i++) {
      let x = Math.cos(angleOffset * i) * radius
      let z = Math.sin(angleOffset * i) * radius
      bottomPos.push(x, 0, z)
      bottomUvs.push(i * uvOffset, 0)
      topPos.push(x, height, z)
      topUvs.push(i * uvOffset, 1)
    }
    bottomPos = bottomPos.concat(topPos)
    bottomUvs = bottomUvs.concat(topUvs)

    let face = []
    for (var i = 0; i < segment; i++) {
      if (i != segment - 1) {
        face.push(i + segment + 1, i, i + segment)
        face.push(i, i + segment + 1, i + 1)
      } else {
        face.push(segment, i, i + segment)
        face.push(i, segment, 0)
      }
    }

    let geo = new BufferGeometry()
    geo.setAttribute('position', new BufferAttribute(new Float32Array(bottomPos), 3))
    geo.setAttribute('uv', new BufferAttribute(new Float32Array(bottomUvs), 2))
    geo.setIndex(new BufferAttribute(new Uint16Array(face), 1))
    return geo
  }

  /**
   * 更新传送阵底部的圆
   */
  updateCircles() {
    for (var i = 0; i < this.circles.length; i++) {
      this.circles[i].rotateZ(this.params.circleRotateSpeed)
    }
  }

  /**
   * 更新传送阵四周的光壁
   */
  updateArounds() {
    for (var i = 0; i < this.arounds.length; i++) {
      this.arounds[i].rotateY(this.params.aroundRotateSpeed)
    }
    for (var i = 0; i < this.arounds2.length; i++) {
      this.arounds2[i].rotateY(-this.params.aroundRotateSpeed)
      if (this.arounds2[i].scale.x < 0.9 || this.arounds2[i].scale.x > 1.4) {
        this.arounds2[i].userData.aroundScaleOffset *= -1
      }
      this.arounds2[i].scale.x -= this.arounds2[i].userData.aroundScaleOffset
      this.arounds2[i].scale.z -= this.arounds2[i].userData.aroundScaleOffset
    }
  }

  /**
   * 更新光点效果
   */
  updatePatical() {
    for (var i = 0; i < this.particles.length; i++) {
      this.particles[i].position.y += this.particles[i].userData.floatSpeed
      if (this.particles[i].position.y >= this.params.height) {
        //更新位置,y=0,x,z随机
        this.particles[i].position.y = 0
        this.particles[i].position.x =
          Math.random() * this.particles[i].userData.radius * 2 - this.particles[i].userData.radius
        this.particles[i].position.z =
          Math.random() * this.particles[i].userData.radius * 2 - this.particles[i].userData.radius

        //随机上升速度
        this.particles[i].userData.floatSpeed = 0.001 + Math.random() * this.params.pointFloatSpeed
      }
    }
  }

  • 8
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 7
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 7
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值