体积地形
Terrain 每个位置只有一个高度,所以表现不了洞穴、悬垂和孔洞。VoxelTerrain 在三维网格的每个点上存一个有符号距离,所以可以。适合地面本身就是玩法的游戏:采矿、生存、可破坏世界。它和 Terrain 并存,不替换 Terrain;它是可选模块(esengine/voxel),不用它的游戏不会带上它。
给实体加上 VoxelTerrain。实体的位置决定体积的位置,旋转和缩放不起作用。什么都不设置时,你会得到带隧道的丘陵,土壤在岩石之上,岩石里有矿脉。
| 字段 | 作用 |
|---|---|
cellSize |
格子边长,也是地面能表现的最小尺度。边长减半,格子数变成八倍。 |
chunkCells |
一个块每边的格子数。越小,每次编辑重建得越少,绘制的网格越多。 |
minY、maxY |
体积从实体向下、向上延伸多远。两端封闭:挖不穿底,也建不过顶。 |
seed、surfaceHeight、hillHeight、hillScale |
默认地面的丘陵。 |
caveWidth、caveScale |
隧道的宽度和间距。caveWidth 为 0 时没有隧道。 |
soilDepth、oreAmount |
土壤深度,以及岩石里矿的比例。 |
drawDistance、castShadows |
离视点多远内绘制,以及是否投射阴影。 |
collision、collisionDistance、collisionLayer、friction |
3D 刚体和角色是否与它碰撞,以及围绕它们构建碰撞的范围。 |
color0..color3、map0..map3、tile |
每种材质的颜色和贴图,以及贴图在世界中一次重复的长度。贴图按世界位置从三个方向投射,因为事后提取的表面没有 uv。 |
material |
使用 builtin:voxel-ground 着色器的材质,用来替换默认材质。 |
四种材质是 VoxelMaterial.Topsoil、Soil、Rock 和 Ore。
所有操作都通过 VoxelTerrains 资源,位置都是世界坐标。
import { VoxelTerrain, VoxelTerrainsRes, VoxelMaterial } from 'esengine/voxel';
const voxels = app.getResource(VoxelTerrainsRes);
// 玩家看着的地方。const hit = voxels.raycast(eye, forward, 8);if (hit) { const dug = voxels.carve(hit.entity, { kind: 'sphere', center: hit.point, radius: 1.5 }, { keep: (material) => material === VoxelMaterial.Ore && !hasPick, }); inventory.add('stone', dug.removed[VoxelMaterial.Rock]);}carve 返回每种材质被挖走多少,单位是立方世界单位,也就是矿工能带走的量。形状有 sphere、box 和 capsule,用 capsule 一次调用就能挖出一条隧道。
| 调用 | 作用 |
|---|---|
carve(entity, shape, options) |
挖掉一个形状。 |
fill(entity, shape, material, options) |
用某种材质填充一个形状。 |
edit(entity, shape, 'paint', { material }) |
改变形状内的材质,不改变外形。 |
raycast(origin, direction, maxDistance) |
射线最先碰到哪个体积:点、法线、材质和实体。 |
distanceAt(entity, at)、materialAt(entity, at) |
某个位置是什么。小于零是实心。 |
onEdit(listener) |
监听每一次产生变化的编辑,用来同步导航网格或任务。 |
collisionReadyAt(entity, x, z) |
那里的碰撞是否已构建,放东西之前可以等它。 |
status(entity) |
绘制的块数和三角形数、持有和编辑过的块数,以及上次重建的耗时。 |
选项里的 smooth 让形状边缘融入相邻部分,单位是世界单位;keep 指定这次编辑不能改变的材质。
只有编辑波及的块和它们的碰撞会重建;新网格就绪前旧网格保持不变,每帧至少重建一块。原本压在被挖部分上的物体会被唤醒并掉落。
setSource 用一个关于位置的函数替换默认丘陵。相同位置必须返回相同结果,因为没有改动过的块会被释放,之后再重新询问。
voxels.setSource(entity, (x, y, z, out) => { out.distance = y - Math.sin(x * 0.1) * 4; // 小于零是实心;靠近表面时应大致等于真实距离 out.material = VoxelMaterial.Rock;});heightVoxelSource 能给任意高度场一个实体,所以 heightVoxelSource((x, z) => terrains.heightAt(x, z)) 可以把 Terrain 自己的地面变成体积。
存档只包含编辑过的块,是普通 JSON。无尽世界的存档大小只取决于玩家改了多少。
const whole = voxels.serialize(entity); // 所有编辑过的块const delta = voxels.serialize(entity, true); // 只有上次这样调用之后改动的块voxels.restore(entity, whole); // 恢复:其余部分回到生成源的样子voxels.restore(entity, delta, true); // 把这些块叠加到现有内容上种子或生成源必须和存档时相同。来自其他网格(cellSize 或 chunkCells 不同)的存档会被拒绝且不做任何改动,restore 返回 false。读不了的块保持原样并给出警告。
- 没有 LOD。
drawDistance内的块都是全精度绘制。默认地面在drawDistance96、cellSize1 时约 170 块、24 万三角形,远景开销比 Terrain 高。 - 编辑器里没有笔刷。 布置场景时体积会被绘制,但改动只能在运行时用代码完成。
- 不能在 Terrain 上开洞。 两者是各自独立的地面。想挖 Terrain 的形状,用
heightVoxelSource生成体积,并改为绘制体积。 - 编辑过的块完整保存在内存里, 默认 24 格时每块约 35 KB,游戏运行期间一直保留。
- 导航不会自动更新。 用
onEdit重建游戏需要的部分。 - 贴图没有法线贴图, 一个体积最多四种材质。自定义
material必须使用 Voxel Ground 着色器,否则表示材质权重的顶点色会被当成染色。 - 没有在真机上测过。 在桌面上,一次波及三块且带碰撞的编辑约占帧时间 4 ms,最高 8 ms。如果是第一次编辑还没读取过的地面,还要加上生成开销:每块约 3 ms。