专栏名称: 稀土掘金技术社区
掘金,一个帮助开发者成长的技术社区
目录
相关文章推荐
程序猿  ·  “把 if 往上提,for 往下放!” ·  21 小时前  
伯乐在线  ·  年薪 7000 万!扎克伯格大撒币,挖 AI 大牛 ·  19 小时前  
伯乐在线  ·  年薪 7000 万!扎克伯格大撒币,挖 AI 大牛 ·  19 小时前  
稀土掘金技术社区  ·  【万字总结】2025 前端+大前端+全栈 ... ·  昨天  
OSC开源社区  ·  全球首个基于Web的“液态玻璃(Liquid ... ·  3 天前  
51好读  ›  专栏  ›  稀土掘金技术社区

我又写出了被 Three.js 官推转发的项目?!(源码分享)

稀土掘金技术社区  · 公众号  · 程序员  · 2025-06-10 08:30

正文

请到「今天看啥」查看全文


  • 然后每个行上都有着相应的物体,"草地" 上会出现高矮不一的 "树木",而 "柏油路" 上会出现行驶方向向左或者向右的汽车
  • 那么 metadata 应该如何更好的囊括这些信息呢?我是这样做的:

    现在我们先采用静态 metadata 来构建初始游戏场景,通过模块化的设计实现草地、道路、树木和车辆的动态生成。

    静态 metadata 结构

    const metadata = [  // 第一行   {    type'forest',    trees: [      { tileIndex: -7type'tree01' },      { tileIndex: -3type'tree02' },    ],  },  // 第二行   {    type'road',    direction: true,    speed: 1,    vehicles: [      {        initialTileIndex: 12,        type'car04',      },      {        initialTileIndex: 2,        type'car08',      },      {        initialTileIndex: -2,        type'car01',      },    ],  },]

    地形生成

    而铺设路面的函数就较为简单,就是将传入的 mesh scene 中排成一排,随后根据当前行数为 metadata 中的所属行的数组下标对其位置在进行调整。

    export default class Grass {  constructor(scene, object3d, rowIndex = 0) {    this.scene = scene    this.object3d = object3d    this.rowIndex = rowIndex    this.tiles = []    this.createGrassRow()  }
      // 生成一行草地  createGrassRow() {    // 获取 tile 资源(假设资源名为 'grass',如有不同请调整)    const tileResource = this.object3d    tileResource.scene.updateMatrixWorld()    if (!tileResource) {      console.warn('未找到 grass 资源')      return    }   // 生成16个连续的草地瓦片    for (let i = 0; i 16; i++) {      // 计算当前tile的地图下标      const tileIndex = MIN_TILE_INDEX + i      // 克隆tile模型      const tileMesh = tileResource.scene.clone()      // 设置tile在世界坐标中的位置      tileMesh.position.set(tileIndex, 0this.rowIndex) // 将 tileMesh 沿着 X 轴排成一排,随后根据 this.rowIndex 调整 Z 轴位置      // 添加到场景      this.scene.add(tileMesh)      // 存储tile对象      this.tiles.push(tileMesh)    }  }}

    路面行生成也是同理,这里就不反复贴类似功能的代码了。随后在场景中根据 metadata 生成对应类实例

    this.metadata.forEach((rowData) => {      this.rowIndex++      // 如果是森林行,添加树      if (rowData && rowData.type === 'forest') {        // 先生成草地        this.addGrassRow(this.rowIndex)      }      if (rowData && rowData.type === 'road') {        this.addRoadRow(this.rowIndex)      }})

      // 添加一行草地  addGrassRow(rowIndex = 0) {    const grass = new Grass(this.scene, this.resources.items.grass, rowIndex)    this.grassRows.push(grass)    this.tiles.push(...grass.tiles)  }
      // 添加一行道路  addRoadRow(rowIndex = 0) {    const road = new Road(this.scene, this.resources, rowIndex)    this.roadRows.push(road)  }

    我们就能得到场景如图 (行上的数字对应了当前行对应的 TileIndex)

    动态元素生成

    现在我们需要向森林行和道路行上添加对应的物体,这些物体并不是固定的某一行有多少多少个,而是根据在 metadata 相对应的物体数组决定,森林行根据 tree 数组添加对应的树木,道路类根据 vehicles 添加对应的车辆。

    树木生成

    就比如树木数组

        trees: [      { tileIndex: -7, type: 'tree01' },      { tileIndex: -3, type: 'tree02' },    ],

    他就分别代表

    • 模型名为 tree01 的树木模型在 tileIndex 位置为 -7 的位置。
    • 模型名为 tree02 的树木模型在 tileIndex 位置为 -3 的位置。

    (ps: 我对单个路面块再建模软件中进行过预处理,确保他们引入后长度大小刚好为 1m,所以后续 tileIndex 会和 position 的 X 轴对应)

    export default class Tree {  /**   * @param {THREE.Scene} scene - threejs场景   * @param {object} resources - 资源加载器实例   * @param {Array} trees - 当前行的树木数组,每项包含tileIndex和type   * @param {number} rowIndex - 当前行的z坐标   */  constructor(scene, resources, trees, rowIndex = 0) {    this.scene = scene    this.resources = resources    this.trees = trees    this.rowIndex = rowIndex    this.treeMeshes = []    this.addTrees()  }
      // 添加所有树木到当前行  addTrees() {    this.trees.forEach((treeData) => {      const { tileIndex, type } = treeData      // 获取对应类型的树模型      const treeResource = this.resources.items[type]      // 克隆树模型      const treeMesh = treeResource.scene.clone()      // 设置树的位置(x轴为tileIndex,z轴为rowIndex)      treeMesh.position.set(tileIndex, 0.2this.rowIndex)      // 添加到场景      this.scene.add(treeMesh)      // 存储树对象,便于后续移除      this.treeMeshes.push(treeMesh)    })  }}

    车辆生成

    车辆类相比树木类需要多一层 “调整车辆方向逻辑”,这不仅需要代码配合,还需要对静态资源进行预处理,确保所有车辆朝向一致。

      {    type'road',    direction: true,    speed: 1,    vehicles: [      {        initialTileIndex: 12,        type'car04',      },      {        initialTileIndex: 2,        type'car08',      },      {        initialTileIndex: -2,        type'car01',      },    ],  },
    export default class Car {  /**   * @param {THREE.Scene} scene - threejs场景   * @param {object} resources - 资源加载器实例   * @param {Array} vehicles - 当前行的车辆数组,每项包含 initialTileIndex 和 type   * @param {number} rowIndex - 当前行的z坐标   * @param {boolean} direction - 车辆方向,true 向右,false 向左   * @param {number} speed - 车辆速度   */  constructor(scene, resources, vehicles, rowIndex = 0, direction = false, speed = 1) {    this.experience = new Experience()    this.scene = scene    this.resources = resources    this.time = this.experience.time    this.vehicles = vehicles    this.rowIndex = rowIndex    this.direction = direction    this.speed = speed    this.timeMultiplier = 1    this.carMeshes = []    this.addCars()  }
      // 添加所有车辆到当前行  addCars() {    this.vehicles.forEach((carData, _idx) => {      const { initialTileIndex, type } = carData      // 获取对应类型的车辆模型      const carResource = this.resources.items[type]      if (!carResource) {        console.warn(`未找到资源: ${type}`)        return      }      // 克隆车辆模型      const carMesh = carResource.scene.clone()      carMesh.scale.set(0.50.50.5)      // 递归设置所有 mesh 可投射阴影      carMesh.traverse((child) => {        if (child.isMesh) {          child.castShadow = true // 车辆产生阴影        }      })      // 设置车辆位置(x轴为tileIndex*4,z轴为rowIndex)      carMesh.position.set(initialTileIndex, 0.35this.rowIndex)      // 设置车辆朝向      if (this.direction) {        carMesh.rotation.y = 0 // 向右      }      else {        carMesh.rotation.y = Math.PI // 向左      }      // 添加到场景      this.scene.add(carMesh)      // 存储车辆对象,便于后续移除和动画      this.carMeshes.push(carMesh)    })  }}

    场景组装搭建

    随后在前面遍历 metadata 的地方将 tree & car 的生成函数以同样方式调用

        this.metadata.forEach((rowData) => {      this.rowIndex++      // 如果是森林行,添加树      if (rowData && rowData.type === 'forest') {        // 先生成草地        this.addGrassRow(this.rowIndex)        this.addTreeRow(rowData.trees, this.rowIndex)      }      if (rowData && rowData.type === 'road') {        this.addRoadRow(this.rowIndex)        this.addCarRow(rowData.vehicles, this.rowIndex, rowData.direction, rowData.speed)      }    })  }
      // 添加一行树  addTreeRow(trees, rowIndex) {    const treeRow = new Tree(this.scene, this.resources, trees, rowIndex)    this.treeRows.push(treeRow)  }
      // 添加一行车辆  addCarRow(vehicles, rowIndex = 0, direction = false, speed = 1) {    const carRow = new Car(this.scene, this.resources, vehicles, rowIndex, direction, speed)    this.carRows.push(carRow)    // 新增:记录每行车辆mesh    this.carMeshDict[rowIndex] = carRow.getCarMeshes()  }

    最后我们只需要在给车辆增加移动效果,让车辆随着 requestAnimationFrame 更新不断更新 mesh







    请到「今天看啥」查看全文