دوز مباشرة للمحتوى الرئيسي
دروس فالذكاء الاصطناعي

صايب حيوانات إجرائية ومتحركة بـ Three.js

تعلّم كيفاش تنصّب Procedural Animals، تصايب حيوان ببذرة محددة، تتحكّم فالحركة والأفعال، تتعامل مع الأحداث، تهيّأ العمال، تجهّز الأصول باش يتحمّلو بسرعة، وتختار إعدادات الجودة من اللقطات السينمائية القريبة حتى للأسراب الكبيرة.

صايب حيوانات إجرائية ومتحركة بـ Three.js

شنو هو three.js Procedural Animals؟

three.js Procedural Animals هي مكتبة فيها 24 حيوان كيتصاوبو وقت التشغيل باستعمال three.js. بلا ما تحتاج تهبط الموديلات ولا التكستورات ولا الـ rigs ولا مقاطع الأنيميشن، كتعيّط على createAnimal() والمكتبة ككتبني حيوان متحرك كامل بالكود.

كل حيوان كيتنحت باستعمال signed distance fields، وكيتحوّل لـ mesh، وكيتدار ليه rig و skin، وكيتغطى بالفرو ولا الريش ولا الحراشف، وكيتحرك باستعمال مشيات وأفعال مبنية على inverse kinematics. مخططات الجسم المتوفرة فيها حيوانات عندها مخالب، وحيوانات بحوافر، وطيور، وحيوانات كتسبح، وحناش، وعناكب، وحيوانات كتقفز.

تقدر تستكشف جميع الأنواع والنسخ والأفعال وواجهات التصحيح فـ العرض المباشر، ولا تشوف المصدر فـ مستودع GitHub.

المميزات الرئيسية

  • 24 نوع: الاختيارات كتمتد من الذئاب والقطط والخيول والأرانب حتى الطيور والحوتيات والحناش والعناكب.
  • أفراد مبنيين على seed: نفس الـ seed كيعطي الحجم والنِّسب والكسوة والجنس والعمر والنسخة نفسها، إلا بدّلتيها يدوياً.
  • أنيميشن وقت التشغيل: الحيوانات كتختار أوتوماتيكياً المشيات المناسبة، وكتوفر أفعال بحال الجلوس والنعاس والماكلة والهجوم والقفز.
  • خمسة مستويات للجودة: اختار من hero وhigh وmedium وlow وcrowd.
  • البناء باستعمال Worker: عمليات البناء كتخدم افتراضياً فـ Module Web Workers، ومعها حل بديل كيخدم فـ thread اللي عيّط عليها.
  • البناء المسبق بلا إنترنت: بنِ فرداً مسبقاً فـ ملف .animal باش يتحمّل فحوالي 20 ميلي ثانية.
  • تحكمات موجهة للألعاب: وجّه الحيوانات، حرّكها للوجهات، خلّيها تتبع أجسام الفيزياء، تبع الأهداف، شغّل الأفعال، وتوصل بأحداث اللعب.
  • دعم TypeScript: الحزمة كتستعمل ES modules وكتضمّن تعريفات TypeScript.

التثبيت

المكتبة كتحتاج three.js r160 ولا أحدث باعتبارها الاعتمادية الوحيدة ديال peer. ثبّت الحزمة وthree.js باستعمال npm:

npm install procedural-animals three

إلا ما كانتش الحزمة باقية متوفرة فـ npm، ثبّتها مباشرة من GitHub:

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

صايب أول حيوان ديالك

بدا باستيراد three.js وcreateAnimal. هاد الدالة غير متزامنة حيث بناء الـ mesh والـ rig والكسوة والبيانات المرتابطة كياخذ الوقت.

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 للمشهد، ولكن خلّي transform ديال داك object فـ identity. الحيوان هو اللي كيتكلف بالحركة ديالو.
  3. استعمل دوال بحال move() وmoveTo() ولا follow() باش توجّهو.
  4. عيّط على update(dt) مرة وحدة بالضبط فـ كل frame، وخلي dt مقاس بالثواني.
  5. استعمل السرعات من animal.gears حيث أسماء gear المتوفرة تقدر تختلف حسب النوع.

تجنّب await فـ المستوى الأعلى فـ entry module. حسب توثيق المشروع، يقدر يسبب deadlock فـ build الإنتاج ديال Vite.

الحركة والملاحة

التوجيه المباشر

الدالة move() كتقبل السرعة بالمتر فـ الثانية والاتجاه بالراديان. الاتجاه 0 كيوجّه للمحور Z الموجب.

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

عيّط على stop() ملي بغيتي الحيوان يوقف الحركة:

wolf.stop();

التحرك لوجهة

استعمل moveTo() باش توجّه الحيوان لنقطة فـ إحداثيات العالم. منين كيوصل للوجهة، كيطلق event سميتو arrive.

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

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

تتبّع جسم ديال الفيزياء

إلا كان نظام آخر هو اللي كيتحكم فـ شي جسم، مرّر الموقع والسرعة الحاليين ديالو للدالة follow() فـ كل frame:

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

التفاعل مع أحداث الحيوانات

واجهة الأحداث كاتخلي أنظمة اللعبة تتفاعل مع التحريك والحركة. الأحداث المتاحة كاتشمل 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);

نتائج البناء كاتتخزّن حسب النوع والـseed والجودة والسلالة. لذلك قطيع فيه 100 حيوان وكيستعمل ستة ديال الـseeds كيحتاج غير لستة عمليات بناء مختلفة. وكلفة المعالج الموثقة هي تقريبا 0.1 ميلي ثانية لكل حيوان فكل إطار.

إعداد Web Workers

بناء الحيوانات كياخذ تقريبا من 0.4 حتى 6 ثواني. افتراضيا، البناء كايدوز فـWeb Workers ديال الموديولات. إلا ما كانش Worker متاح، المكتبة كاترجع للخيط اللي عيط عليها.

إعداد Vite

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

webpack 5 وParcel 2 ما كيحتاجوش إعداد إضافي للـworkers.

أدوات البناء الأخرى

إلى كنت كتستعمل أداة بناء أخرى، بنِ الـworker يدويا:

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

ومن بعد سجّل factory اللي كاتنشئ الـ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 والركائب واللجم. ربط العناصر بهاد كائنات الربط بدل ما تقرّب مواقعها المتحركة يدوياً.

استعمل أدوات التصحيح والتغطية

واجهة الحيوان فيها 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()، زِد الكائن اللي كيرجع للمشهد، وجّهو عبر واجهة الحيوان، وحدّثو مرة فكل إطار. الـWorkers والخبز والتخزين المؤقت بالبذور ومستويات الجودة المتعددة كيخلّيو نفس طريقة العمل صالحة للقطات القريبة المفصلة وللحشود الكبيرة.