Chuyển đến nội dung chính
Hướng dẫn AI

Xây dựng động vật hoạt ảnh theo quy trình trong Three.js

Tìm hiểu cách cài đặt Procedural Animals, tạo động vật theo seed, điều khiển chuyển động và hành động, xử lý sự kiện, cấu hình worker, đóng gói asset để tải nhanh và chọn thiết lập chất lượng cho mọi nhu cầu, từ cận cảnh điện ảnh đến đám đông lớn.

Xây dựng động vật hoạt ảnh theo quy trình trong Three.js

three.js Procedural Animals là gì?

three.js Procedural Animals là thư viện chứa 24 loài động vật được tạo trong thời gian chạy cho three.js. Thay vì tải xuống mô hình, texture, rig hoặc đoạn hoạt ảnh, bạn gọi createAnimal() và thư viện sẽ dựng một động vật hoàn chỉnh có hoạt ảnh bằng mã.

Mỗi loài động vật được tạo hình từ các trường khoảng cách có dấu, chuyển thành lưới, dựng rig, gắn skin, phủ lông, lông vũ hoặc vảy, rồi được tạo hoạt ảnh bằng dáng đi và hành động dựa trên động học ngược. Các kiểu cơ thể hiện có gồm động vật chân có đệm thịt, động vật móng guốc, chim, động vật bơi, rắn, nhện và động vật bật nhảy.

Bạn có thể khám phá mọi loài, biến thể, hành động và chế độ gỡ lỗi trong bản trình diễn trực tiếp, hoặc xem mã nguồn trong kho GitHub.

Tính năng chính

  • 24 loài: Các lựa chọn trải dài từ sói, mèo, ngựa và thỏ đến chim, cá, rắn và nhện.
  • Cá thể theo seed: Một seed tạo ra kích thước, tỷ lệ cơ thể, bộ lông, giới tính, độ tuổi và lựa chọn biến thể có thể lặp lại, trừ khi bạn ghi đè chúng.
  • Hoạt ảnh trong thời gian chạy: Động vật tự động chọn dáng đi phù hợp và cung cấp các hành động như ngồi, ngủ, ăn, tấn công và nhảy.
  • Năm mức chất lượng: Chọn từ hero, high, medium, low và crowd.
  • Dựng bằng Worker: Theo mặc định, quá trình dựng chạy trong Web Worker dạng module, với cơ chế dự phòng sang luồng gọi.
  • Đóng gói ngoại tuyến: Dựng sẵn một cá thể thành tệp .animal để tải trong khoảng 20 mili giây.
  • Điều khiển hướng đến game: Điều khiển động vật, di chuyển đến đích, bám theo vật thể vật lý, theo dõi mục tiêu, phát hành động và nhận sự kiện gameplay.
  • Hỗ trợ TypeScript: Gói sử dụng ES module và bao gồm các khai báo TypeScript.

Cài đặt

Thư viện yêu cầu three.js r160 trở lên làm dependency ngang hàng duy nhất. Cài đặt gói và three.js bằng npm:

npm install procedural-animals three

Nếu gói chưa có trên npm, hãy cài đặt trực tiếp từ GitHub:

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

Tạo động vật đầu tiên

Bắt đầu bằng cách import three.js và createAnimal. Hàm này là bất đồng bộ vì việc dựng lưới, rig, lớp phủ và dữ liệu liên quan cần thời gian.

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

Ví dụ này có một số quy tắc quan trọng:

  1. Tạo động vật bên trong một hàm async và await kết quả.
  2. Thêm animal.object vào scene, nhưng giữ transform của object đó ở trạng thái identity. Động vật tự quản lý việc di chuyển.
  3. Sử dụng các phương thức như move(), moveTo() hoặc follow() để điều khiển hướng di chuyển.
  4. Gọi update(dt) chính xác một lần mỗi frame, với dt được đo bằng giây.
  5. Sử dụng tốc độ từ animal.gears vì tên gear khả dụng có thể khác nhau tùy loài.

Tránh dùng await ở cấp cao nhất trong module entry. Theo tài liệu dự án, điều này có thể khiến bản build production của Vite bị deadlock.

Di chuyển và điều hướng

Điều khiển trực tiếp

Phương thức move() nhận tốc độ tính bằng mét trên giây và hướng tính bằng radian. Hướng 0 quay về trục Z dương.

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

Gọi stop() khi muốn động vật dừng di chuyển:

wolf.stop();

Di chuyển đến đích

Sử dụng moveTo() để đưa động vật đến một điểm trong không gian thế giới. Phương thức này phát sự kiện arrive sau khi đến đích.

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

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

Bám theo vật thể vật lý

Nếu một hệ thống khác điều khiển một vật thể, hãy truyền vị trí và vận tốc hiện tại của vật thể đó vào follow() mỗi frame:

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

Theo dõi mục tiêu

Yêu cầu động vật quay đầu về phía mục tiêu bằng lookAt(). Truyền null để dừng theo dõi.

wolf.lookAt(player.position);

// Later:
wolf.lookAt(null);

Các hành động chơi

Mọi loài đều hỗ trợ các hành động chung jump, sit, lie, sleep, eat, drink, attack, hit, death và stand. Các loài cũng có thể cung cấp những hành động chuyên biệt. Ví dụ, rắn có thể cuộn mình và tấn công, chim có thể cất cánh và hạ cánh, còn thỏ có thể nhảy và dậm chân.

Phương thức play() trả về một Promise với kết quả có thể là done, interrupted, stopped hoặc refused.

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

Hãy kiểm tra animal.actions thay vì giả định rằng một loài hỗ trợ hành động chuyên biệt nào đó. Bạn có thể dừng một hành động được chỉ định tên bằng stopAction():

wolf.stopAction('attack');

Phản hồi các sự kiện của động vật

API sự kiện cho phép các hệ thống trò chơi phản hồi animation và chuyển động. Các sự kiện khả dụng gồm footstep, attackHit, actionStart, actionEnd, arrive, takeoff, land, death, vocalize và 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);

Cấu hình địa hình và nước

Tùy chọn ground là một hàm trả về độ cao địa hình tại tọa độ X và Z. Tọa độ của hàm phải nằm trong không gian của animal.object.parent, và độ cao trả về phải khớp với địa hình được hiển thị trong cảnh.

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

Động vật bơi cần một hàm water và vị trí khởi đầu bên dưới mặt nước. Hàm này trả về độ cao mặt nước hoặc null tại nơi không có nước.

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

scene.add(fish.object);

Động vật trên cạn cũng có thể sử dụng định nghĩa nước để uống và lội nước. Chim có thể nhận một mảng perches chứa các điểm THREE.Vector3 mà chúng có thể bay tới.

Chọn loài, biến thể và cá thể

Thư viện bao gồm chó sói, chó, cáo, mèo, sư tử, báo săn, gấu, chuột, thỏ, ngựa, hươu, bò, lợn, cừu, dê, lợn rừng, ếch, gà, quạ, đại bàng, cá, cá mập, rắn và nhện.

Dùng seed để tạo một cá thể có thể tái lập. Bạn cũng có thể chọn rõ biến thể, giới tính hoặc độ tuổi:

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

Các mã định danh loài dạng chuỗi được tải theo yêu cầu. Nếu ứng dụng chỉ cần một loài, hãy import trực tiếp loài đó để giữ bundle gọn hơn:

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

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

Chọn cấp chất lượng phù hợp

  • hero: Khoảng 110.000 đến 130.000 đỉnh cho các cảnh cận điện ảnh.
  • high: Khoảng 65.000 đến 80.000 đỉnh cho động vật của người chơi. Đây là mặc định.
  • medium: Khoảng 25.000 đến 32.000 đỉnh cho góc nhìn trung và điện thoại.
  • low: Khoảng 11.000 đến 15.000 đỉnh cho đàn khoảng 10 đến 20 con.
  • crowd: Khoảng 5.000 đến 8.000 đỉnh cho đám đông khoảng 100 con, với một lệnh vẽ.

Bạn có thể thay đổi độ chi tiết bộ lông trên mesh hiện có bằng setQuality(). Với các đám đông lớn, hãy kết hợp cấp crowd với setLod(2) để giảm độ chi tiết chuyển động:

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

sheep.setLod(2);

Kết quả build được lưu vào bộ nhớ đệm theo loài, seed, chất lượng và biến thể. Vì vậy, một đàn 100 con sử dụng sáu seed chỉ cần sáu bản build riêng biệt. Chi phí CPU được ghi nhận là khoảng 0,1 mili giây cho mỗi con mỗi khung hình.

Cấu hình Web Worker

Quá trình build động vật mất khoảng 0,4 đến 6 giây. Theo mặc định, việc khởi tạo chạy trong các Web Worker dạng module. Nếu Worker không khả dụng, thư viện sẽ chuyển sang luồng gọi.

Cấu hình Vite

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

webpack 5 và Parcel 2 không yêu cầu cấu hình worker bổ sung.

Các bundler khác

Với bundler khác, hãy tự bundle worker:

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

Sau đó đăng ký một factory tạo worker:

import { setWorkerFactory } from 'procedural-animals';

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

Bạn có thể tắt việc build bằng worker cho một động vật cụ thể, nhưng thao tác này sẽ chặn luồng gọi trong suốt thời gian build:

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

Đóng gói sẵn động vật để tải nhanh

Đối với một cá thể đã biết, hãy dựng sẵn nó thành tệp .animal. Việc này thay thế độ trễ dựng lúc chạy bằng thao tác tải tệp, được README ước tính mất khoảng 20 mili giây.

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

Tải tệp đã tạo dưới dạng ArrayBuffer và truyền tệp đó qua tùy chọn baked:

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

Cá thể đã dựng sẵn phải tương ứng với loài và cấu hình được chuẩn bị bởi lệnh.

Mẹo nâng cao

Sử dụng trạng thái chuyển động chỉ đọc

Đọc position, heading, speed, velocity và state để điều phối camera, gameplay hoặc giao diện. Không thao tác với đối tượng gốc của động vật để tạo chuyển động.

Gắn đối tượng phụ vào các điểm được đặt tên

Tập hợp attachments cung cấp các điểm như mouth, head và back. Chim cũng cung cấp các điểm gắn ở chân, còn ngựa có các điểm liên quan đến cưỡi, bao gồm seat, bàn đạp và hàm thiếc. Hãy gắn các đối tượng phụ vào những đối tượng điểm gắn này thay vì tự ước tính vị trí đang chuyển động của chúng.

Sử dụng các tùy chọn gỡ lỗi và lớp phủ

API động vật bao gồm setDebug(mode, on) với các chế độ albedo, weights, material và bare. Sử dụng setCoverings(on) để bật hoặc tắt các lớp lông.

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

Tính hướng về một điểm

Vì hướng bằng 0 quay về Z dương, hãy sử dụng Math.atan2(dx, dz) khi tính hướng tới mục tiêu:

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

Dọn dẹp tài nguyên

Gọi dispose() khi một động vật bị xóa vĩnh viễn. Thao tác này giải phóng tài nguyên GPU của nó và xóa nó khỏi cảnh.

wolf.dispose();

Các export và tài liệu hữu ích

Gói chính cũng export listSpecies, loadSpecies, setWorkerFactory, clearBuildCache và QUALITY:

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

Để biết mọi tùy chọn, payload sự kiện, trường trạng thái và công thức, hãy tham khảo tài liệu tham khảo đầy đủ và khai báo TypeScript của kho lưu trữ.

Kết luận

three.js Procedural Animals cung cấp cách tiếp cận ưu tiên mã nguồn để thêm các loài động vật hoang dã đa dạng, có khung xương và hoạt ảnh vào ứng dụng three.js. Hãy bắt đầu với createAnimal(), thêm đối tượng được trả về vào cảnh, điều khiển nó thông qua API động vật và cập nhật một lần mỗi khung hình. Worker, tính năng dựng sẵn, bộ nhớ đệm theo seed và nhiều cấp chất lượng cho phép cùng một quy trình hỗ trợ cả cận cảnh chi tiết lẫn đám đông quy mô lớn.