メインコンテンツへ移動
AIチュートリアル

Three.jsでプロシージャルなアニメーション動物を作成する

Procedural Animalsのインストール方法、シード値を指定した動物の作成、動きとアクションの制御、イベント処理、ワーカー設定、高速読み込みのためのアセットベイク、映画的なクローズアップから大規模な群衆まで用途に応じた品質設定の選択方法を学びます。

Three.jsでプロシージャルなアニメーション動物を作成する

three.js Procedural Animalsとは?

three.js Procedural Animalsは、three.js向けに実行時生成される24種類の動物を収録したライブラリです。モデル、テクスチャ、リグ、アニメーションクリップをダウンロードする代わりに、createAnimal()を呼び出すだけで、ライブラリがコードから完全にアニメーション可能な動物を構築します。

各動物は符号付き距離場から造形され、メッシュ化、リギング、スキニングを施したうえで、毛皮、羽毛、鱗などで覆われます。また、逆運動学による歩容やアクションでアニメーションします。利用できるボディプランには、足で歩く動物、ひづめのある動物、鳥、水中を泳ぐ動物、ヘビ、クモ、跳ねる動物が含まれます。

すべての種、バリエーション、アクション、デバッグビューはライブショーケースで確認できます。ソースはGitHubリポジトリで確認できます。

主な機能

  • 24種:オオカミ、ネコ、ウマ、ウサギから、鳥、魚、ヘビ、クモまで選択できます。
  • シード付き個体:シードによって、サイズ、体型、毛色、性別、年齢、バリエーションの選択を再現できます。明示的に上書きすることも可能です。
  • 実行時アニメーション:動物は適切な歩容を自動選択し、座る、眠る、食べる、攻撃する、跳ぶなどのアクションを利用できます。
  • 5段階の品質:hero、high、medium、low、crowdから選択できます。
  • Workerベースの構築:構築処理はデフォルトでモジュールWeb Worker上で実行され、呼び出し元スレッドへのフォールバックにも対応します。
  • オフラインベイク:個体を.animalファイルとして事前構築し、約20ミリ秒で読み込めます。
  • ゲーム向け制御:動物の操縦、目的地への移動、物理ボディへの追従、ターゲットの追跡、アクションの再生、ゲームプレイイベントの受信に対応します。
  • TypeScript対応:ESモジュールを使用し、TypeScriptの型宣言を含みます。

インストール

このライブラリが必要とするpeer依存関係は、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関数内で作成し、結果をawaitします。
  2. animal.objectをシーンに追加します。ただし、そのオブジェクトのトランスフォームはidentityのままにしてください。動物自身が移動を管理します。
  3. move()、moveTo()、follow()などのメソッドを使って操縦します。
  4. 毎フレーム、秒単位で計測したdtを使って、update(dt)を正確に1回呼び出します。
  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() メソッドは、結果として done、interrupted、stopped、refused のいずれかを返す Promise を返します。

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);

陸上の動物でも、水を飲んだり浅瀬を歩いたりするために水の定義を利用できます。鳥には、飛んで移動できる THREE.Vector3 の地点を含む perches 配列を渡せます。

種、バリエーション、個体を選択する

このライブラリには、オオカミ、イヌ、キツネ、ネコ、ライオン、チーター、クマ、ネズミ、ウサギ、ウマ、シカ、ウシ、ブタ、ヒツジ、ヤギ、イノシシ、カエル、ニワトリ、カラス、ワシ、魚、サメ、ヘビ、クモが含まれています。

seed を使うと、再現可能な個体を作成できます。バリエーション、性別、年齢を明示的に選択することもできます。

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

文字列による種の識別子は、必要になった時点で読み込まれます。アプリケーションで必要な種が 1 つだけなら、その種を直接インポートしてバンドルを軽量化できます。

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 匹の群れ向けで、描画コールは 1 回です。

既存のメッシュの毛並みの詳細度は、setQuality() で変更できます。大規模な群れでは、crowd ティアと setLod(2) を組み合わせて動きの詳細度を下げます。

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

sheep.setLod(2);

ビルド結果は、種、シード、品質、バリエーションごとにキャッシュされます。そのため、6 つのシードを使う 100 匹の群れでも、必要な異なるビルドは 6 つだけです。ドキュメントに記載されている CPU コストは、1 フレームあたり動物 1 匹につき約 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 には、albedo、weights、material、bare の各モードに対応した setDebug(mode, on) が含まれています。setCoverings(on) を使うと、毛皮シェルを切り替えられます。

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

ポイントに向かう方位を計算する

方位 0 は正の 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 で制御し、フレームごとに 1 回更新します。ワーカー、ベイク、シード付きキャッシュ、複数の品質レベルにより、同じワークフローで詳細なクローズアップから大規模な群衆まで対応できます。