正文
然后每个行上都有着相应的物体,"草地" 上会出现高矮不一的 "树木",而 "柏油路" 上会出现行驶方向向左或者向右的汽车
那么
metadata
应该如何更好的囊括这些信息呢?我是这样做的:
现在我们先采用静态
metadata
来构建初始游戏场景,通过模块化的设计实现草地、道路、树木和车辆的动态生成。
静态 metadata 结构
const metadata = [
{
type: 'forest',
trees: [
{ tileIndex: -7, type: 'tree01' },
{ tileIndex: -3, type: '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() {
const tileResource = this.object3d
tileResource.scene.updateMatrixWorld()
if (!tileResource) {
console.warn('未找到 grass 资源')
return
}
for (let i = 0; i 16; i++) {
const tileIndex = MIN_TILE_INDEX + i
const tileMesh = tileResource.scene.clone()
tileMesh.position.set(tileIndex, 0, this.rowIndex)
this.scene.add(tileMesh)
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()
treeMesh.position.set(tileIndex, 0.2, this.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.5, 0.5, 0.5)
carMesh.traverse((child) => {
if (child.isMesh) {
child.castShadow = true
}
})
carMesh.position.set(initialTileIndex, 0.35, this.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)
this.carMeshDict[rowIndex] = carRow.getCarMeshes()
}
最后我们只需要在给车辆增加移动效果,让车辆随着
requestAnimationFrame
更新不断更新
mesh