跳到主要内容
AI教程

在 Three.js 中构建程序化动画动物

学习如何安装 Procedural Animals、创建带种子的动物、控制移动和动作、处理事件、配置工作线程、烘焙资产以实现快速加载,并为从电影级特写到大型群体的各种场景选择质量设置。

在 Three.js 中构建程序化动画动物

什么是 three.js Procedural Animals?

three.js Procedural Animals 是一个包含 24 种运行时生成动物的 three.js 库。无需下载模型、纹理、绑定骨骼或动画片段,只需调用 createAnimal(),该库就会通过代码构建出完整的动画动物。

每种动物都基于有符号距离场雕刻而成,并经过网格化、绑定骨骼、蒙皮处理,覆盖毛发、羽毛或鳞片,还配备基于逆向运动学的步态和动作动画。可用的身体类型包括有爪动物、有蹄动物、鸟类、水生动物、蛇类、蜘蛛和跳跃动物。

你可以在在线展示中探索所有物种、变体、动作和调试视图,也可以在 GitHub 仓库中查看源代码。

主要特性

  • 24 种物种:从狼、猫、马和兔子,到鸟类、鱼类、蛇类和蜘蛛,选择丰富。
  • 基于种子的个体:种子可以生成可重复的体型、比例、毛被、性别、年龄和变体选择,除非你手动覆盖这些设置。
  • 运行时动画:动物会自动选择合适的步态,并提供坐下、睡觉、进食、攻击和跳跃等动作。
  • 五种质量等级:可选择 hero、high、medium、low 和 crowd。
  • 基于 Worker 的构建:默认在模块 Web Worker 中执行构建,也支持回退到调用线程。
  • 离线烘焙:将个体预先构建为 .animal 文件,加载时间约为 20 毫秒。
  • 面向游戏的控制:控制动物转向、移动到目标位置、跟随物理刚体、追踪目标、播放动作并接收游戏事件。
  • TypeScript 支持:该软件包使用 ES 模块,并包含 TypeScript 类型声明。

安装

该库唯一的对等依赖是 three.js r160 或更高版本。使用 npm 安装软件包和 three.js:

npm install procedural-animals three

如果该软件包尚未发布到 npm,可以直接从 GitHub 安装:

npm install github:majidmanzarpour/threejs-procedural-animals three

创建你的第一只动物

首先导入 three.js 和 createAnimal。由于构建网格、骨架、覆盖层及相关数据需要一定时间,该函数是异步的。

import * as THREE from 'three';
import { createAnimal } from 'procedural-animals';

const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
document.body.appendChild(renderer.domElement);

const scene = new THREE.Scene();
scene.background = new THREE.Color(0xa9c1e0);

const camera = new THREE.PerspectiveCamera(
  40,
  innerWidth / innerHeight,
  0.1,
  500
);
camera.position.set(6, 3, 8);

const sun = new THREE.DirectionalLight(0xfff3e4, 2.7);
sun.position.set(6, 10, 4);
sun.castShadow = true;

scene.add(
  sun,
  new THREE.HemisphereLight(0xdde6f2, 0x5f5a52, 1.15)
);

const floor = new THREE.Mesh(
  new THREE.PlaneGeometry(200, 200).rotateX(-Math.PI / 2),
  new THREE.MeshStandardMaterial({ color: 0x77736a })
);
floor.receiveShadow = true;
scene.add(floor);

async function main() {
  const wolf = await createAnimal('wolf', {
    seed: 3,
    quality: 'high'
  });

  scene.add(wolf.object);

  let last = performance.now();

  renderer.setAnimationLoop((now) => {
    const dt = (now - last) / 1000;
    last = now;

    wolf.move({
      speed: wolf.gears.walk,
      heading: now / 1000 * 0.3
    });

    wolf.update(dt);
    camera.lookAt(wolf.position);
    renderer.render(scene, camera);
  });
}

main();

这个示例包含几条重要规则:

  1. 在 async 函数中创建动物,并等待其结果。
  2. 将 animal.object 添加到场景中,但保持该对象的变换为单位变换。动物会自行管理移动。
  3. 使用 move()、moveTo() 或 follow() 等方法控制动物。
  4. 每帧准确调用一次 update(dt),其中 dt 以秒为单位。
  5. 使用 animal.gears 中的速度,因为可用的档位名称可能因物种而异。

避免在入口模块中使用顶层 await。根据项目文档,它可能导致 Vite 生产构建死锁。

移动与导航

直接控制

move() 方法接收以米/秒为单位的速度和以弧度为单位的朝向角。朝向为 0 时,动物面向正 Z 轴。

wolf.move({
  speed: wolf.gears.walk,
  heading: Math.PI / 2
});

需要停止动物移动时,调用 stop():

wolf.stop();

移动到目标位置

使用 moveTo() 让动物前往世界坐标中的某个点。到达目标位置后,它会触发 arrive 事件。

wolf.moveTo(
  new THREE.Vector3(10, 0, 4),
  { speed: 3 }
);

wolf.on('arrive', () => {
  console.log('The wolf arrived.');
});

跟随物理刚体

如果另一个系统控制某个刚体,请在每帧将其当前位置和速度传递给 follow():

wolf.follow(body.position, body.velocity);

追踪目标

使用 lookAt() 让动物将头部转向目标。传入 null 可停止追踪。

wolf.lookAt(player.position);

// Later:
wolf.lookAt(null);

播放动作

每个物种都支持常见动作 jump、sit、lie、sleep、eat、drink、attack、hit、death 和 stand。物种还可以提供专属动作。例如,蛇可以盘绕和突袭,鸟可以起飞和降落,兔子可以跳跃和跺脚。

play() 方法返回一个 Promise,其结果可以是 done、interrupted、stopped 或 refused。

const result = await wolf.play('attack');
console.log(result);

请检查 animal.actions,不要假设某个物种支持专属动作。你可以使用 stopAction() 停止指定动作:

wolf.stopAction('attack');

响应动物事件

事件 API 让游戏系统能够响应动画和移动。可用事件包括 footstep、attackHit、actionStart、actionEnd、arrive、takeoff、land、death、vocalize 和 wingbeat。

const onFootstep = (event) => {
  sfx.play('step', event.position, event.strength);
};

wolf.on('footstep', onFootstep);

wolf.on('attackHit', (event) => {
  damage(event.position, event.direction);
});

// Remove a listener when it is no longer needed.
wolf.off('footstep', onFootstep);

配置地形和水域

ground 选项是一个函数,用于返回 X 和 Z 坐标处的地形高度。其坐标必须位于 animal.object.parent 的坐标空间中,返回的高度必须与场景中渲染的地形一致。

const deer = await createAnimal('deer', {
  ground: (x, z) => terrainHeightAt(x, z),
  position: new THREE.Vector3(4, 0, 8),
  heading: Math.PI
});

游泳动物需要 water 函数以及一个位于水面下方的起始位置。该函数返回水面高度;没有水的地方返回 null。

const fish = await createAnimal('fish', {
  water: (x, z) => 2,
  position: new THREE.Vector3(0, 1, 0)
});

scene.add(fish.object);

陆地动物也可以使用水域定义来饮水和涉水。鸟类可以接收一个 perches 数组,其中包含它们可以飞往的 THREE.Vector3 点。

选择物种、变体和个体

该库包含狼、狗、狐狸、猫、狮子、猎豹、熊、鼠、兔子、马、鹿、牛、猪、羊、山羊、野猪、青蛙、鸡、乌鸦、鹰、鱼、鲨鱼、蛇和蜘蛛。

使用 seed 创建可重复生成的个体。你也可以显式选择其变体、性别或年龄:

const cat = await createAnimal('cat', {
  seed: 12,
  quality: 'high',
  variant: 'tuxedo',
  sex: 'female',
  age: 'adult'
});

字符串形式的物种标识符会按需加载。如果应用只需要一个物种,可以直接导入该物种,以保持包内容精简:

import wolfSpecies from 'procedural-animals/species/wolf';
import { createAnimal } from 'procedural-animals';

const wolf = await createAnimal(wolfSpecies, {
  seed: 3
});

选择合适的质量等级

  • hero:约 110,000 至 130,000 个顶点,适用于电影级特写镜头。
  • high:约 65,000 至 80,000 个顶点,适用于玩家控制的动物。这是默认值。
  • medium:约 25,000 至 32,000 个顶点,适用于中距离视图和手机设备。
  • low:约 11,000 至 15,000 个顶点,适用于约 10 至 20 只动物的兽群。
  • crowd:约 5,000 至 8,000 个顶点,适用于约 100 只动物的群体,并使用一次绘制调用。

你可以使用 setQuality() 更改现有网格的毛发细节。对于大型群体,将 crowd 等级与 setLod(2) 结合使用,可以减少动作细节:

const sheep = await createAnimal('sheep', {
  seed: 5,
  quality: 'crowd'
});

sheep.setLod(2);

构建结果会按物种、种子、质量和变体进行缓存。因此,一个使用六个种子的 100 只动物兽群只需要六个不同的构建结果。文档记录的 CPU 成本约为每只动物每帧 0.1 毫秒。

配置 Web Worker

动物构建大约需要 0.4 至 6 秒。默认情况下,构建会在模块 Web Worker 中运行。如果 Worker 不可用,库会回退到调用线程。

Vite 配置

// vite.config.js
export default {
  worker: { format: 'es' },
  build: { target: 'es2022' },
  optimizeDeps: {
    exclude: ['procedural-animals']
  }
};

webpack 5 和 Parcel 2 不需要额外的 Worker 配置。

其他打包工具

对于其他打包工具,请手动打包 Worker:

npx esbuild node_modules/procedural-animals/src/worker.js --bundle --format=iife --minify --outfile=public/animal-worker.js

然后注册一个用于创建 Worker 的工厂函数:

import { setWorkerFactory } from 'procedural-animals';

setWorkerFactory(
  () => new Worker('/animal-worker.js')
);

你可以为某个动物禁用 Worker 构建,但这样做会在构建期间阻塞调用线程:

const wolf = await createAnimal('wolf', {
  worker: false
});

预烘焙动物以加快加载

对于已知的个体,可以提前将其烘焙为 .animal 文件。这样可将运行时构建延迟替换为加载延迟,README 估计加载大约需要 20 毫秒。

npx procedural-animals-bake wolf --seed 3 --quality high -o public/wolf-3.animal

将生成的文件获取为 ArrayBuffer,并通过 baked 选项传入:

const wolf = await createAnimal('wolf', {
  baked: fetch('/wolf-3.animal')
    .then((response) => response.arrayBuffer())
});

烘焙出的个体应与命令准备的物种和配置相对应。

高级技巧

使用只读运动状态

读取 position、heading、speed、velocity 和 state,用于协调摄像机、游戏逻辑或界面。不要通过操作动物的根对象来产生移动。

将道具附加到命名点

attachments 集合提供了 mouth、head 和 back 等附加点。鸟类还提供足部附加点,而马提供包括 seat、马镫和嚼子在内的骑乘相关附加点。请将道具设为这些附加对象的子对象,而不是手动近似其动画位置。

使用调试和覆盖层控制

动物 API 包含 setDebug(mode, on),支持 albedo、weights、material 和 bare 模式。使用 setCoverings(on) 可切换毛发外壳。

wolf.setDebug('weights', true);
wolf.setCoverings(false);

计算朝向目标点的方向

由于朝向角为零时面向正 Z 轴,计算指向目标的方向时请使用 Math.atan2(dx, dz):

const dx = target.x - wolf.position.x;
const dz = target.z - wolf.position.z;
const heading = Math.atan2(dx, dz);

wolf.move({
  speed: wolf.gears.walk,
  heading
});

清理资源

当动物被永久移除时调用 dispose()。这会释放其 GPU 资源,并将其从场景中移除。

wolf.dispose();

实用导出项和文档

主包还导出了 listSpecies、loadSpecies、setWorkerFactory、clearBuildCache 和 QUALITY:

import {
  createAnimal,
  listSpecies,
  loadSpecies,
  setWorkerFactory,
  clearBuildCache,
  QUALITY
} from 'procedural-animals';

有关每个选项、事件负载、状态字段和配方,请参阅仓库中的完整参考和 TypeScript 声明。

结语

three.js Procedural Animals 提供了一种代码优先的方式,可将多样化、带骨骼绑定且支持动画的野生动物添加到 three.js 应用中。从 createAnimal() 开始,将返回的对象添加到场景,通过动物 API 控制它,并在每帧更新一次。工作线程、烘焙、基于种子的缓存以及多个质量级别,让同一套工作流既能支持精细的近景展示,也能支持大规模群体。