إذا كنت تعمل مع Three.js فأنت على الأرجح تستمتع بالتحكم الكامل في المشهد وحلقة العرض والخامات والتفاعلات. أعمل بعمق مع Three.js منذ فترة طويلة، خاصة في المُهيّئات ثلاثية الأبعاد والأدوات التفاعلية، وبصراحة لم أشعر يومًا أنني أحتاج إلى محرك آخر — حتى بدأت استكشاف Needle Engine.
هذا المقال لا يتحدث عن استبدال Three.js، بل عن فهم المشكلة التي يحلها Needle Engine فعلًا، ولماذا يسيء كثير من مطوري Three.js فهمه.
❌ مفهوم خاطئ شائع
يظن معظم مطوري Three.js: «Needle Engine مجرد طبقة تجريد أخرى تخفي Three.js وتحد من التحكم.» هذا صحيح جزئيًا، لكنه يغفل الفكرة الأساسية. Needle Engine لا يحاول استبدال Three.js؛ بل يحاول حل مشكلات المعمارية وسير العمل، خاصة في المشاريع الكبيرة أو الغنية بالمحتوى.
🧩 ما هو Needle Engine حقًا
- Three.js ← العرض والتحكم منخفض المستوى
- Needle ← البنية ودورة الحياة وسير العمل
ما زال يستخدم Three.js تحت الغطاء: الشبكات ما زالت THREE.Mesh والخامات ما زالت THREE.Material. الذي يتغير هو طريقة تنظيم المنطق.
🧠 الفكرة الأساسية: معمارية قائمة على السلوكيات
في Three.js الخالص ينتهي المنطق غالبًا هنا:
function animate() {
rotateMesh();
handleSelection();
updateMaterials();
}ومع نمو المشروع يصبح هذا صعب الصيانة والتوسع والفهم. يقدّم Needle Engine السلوكيات (Behaviours): وحدات منطق قابلة لإعادة الاستخدام والإرفاق تنتمي إلى كائن. بدل «استدعِ دالة لتغيير الخامة»، تفكر «هذا الكائن لديه القدرة على تغيير خامته».
🎯 مثال حقيقي: مُهيّئ خزانة ثلاثي الأبعاد
تخيّل خزانة لها هيكل وباب وسطح ومقبض. في تنفيذ Three.js المعتاد تقوم بـ Raycast، وتتتبع الشبكة المحددة عالميًا، وتطبّق الخامات بالشروط وswitch. أما بعقلية Needle:
Cabinet Part (GameObject)
├─ Selectable
├─ SelectionVisual
└─ MaterialSlotمنطق التحديد يعيش على الكائن، ومنطق الخامة يعيش على الكائن، والواجهة لا تعرف شيئًا عن الشبكات أو الخامات. كل ما تفعله الواجهة:
selected.getComponent(MaterialSlot).setTexture(texture);لا if، ولا switch، ولا منطق عالمي متشابك.
⚖️ الدالة مقابل السلوك
الدالة فعل لمرة واحدة، بلا دورة حياة ولا ملكية ولا حالة. أما السلوك فله هوية ودورة حياة (start وupdate وonDestroy)، وينتمي إلى كائن محدد، ويمكن تفعيله أو تعطيله، ويتوسع مع تعقيد المشروع. ويصبح هذا الفرق ضخمًا في المُهيّئات وتجارب AR/WebXR والمنتجات ثلاثية الأبعاد المعيارية.
🧩 لماذا لا «يفهم» كثير من مطوري Three.js مكتبة Needle
لأن Needle Engine يتألق بعد أن يكبر المشروع. لعرض تجريبي بمشهد واحد أو كائن تفاعلي صغير، غالبًا يكون Three.js الخالص الخيار الأفضل. لكن في التجارب الغنية بالمحتوى، وسير العمل المشترك بين الفنانين والمطورين، والمشاهد التفاعلية الكبيرة، والمنتجات الجاهزة لـ AR، تصبح بنية Needle منطقية جدًا.
💡 أهم فكرة
حتى لو لم تستخدم Needle Engine مباشرة، فإنه يعلّمك طريقة أفضل لهيكلة مشاريع Three.js. يمكنك أخذ التفكير القائم على السلوكيات، والمنطق القائم على الملكية، وفصل دورة الحياة، وتطبيقها في Three.js الخالص. وهذا وحده يستحق التعلم.
الدالة مقابل المكوّن — مثال عملي
في مشاريع Three.js كثيرة يُعالَج التحديد وتغيير الخامة بدوال وحالة عامة. يعمل هذا في العروض الصغيرة لكنه يصبح سريعًا صعب التوسع.
❌ النهج التقليدي القائم على الدوال
let selectedMesh = null;
function select(mesh) {
selectedMesh = mesh;
}
function applyTexture(texture) {
if (!selectedMesh) return;
selectedMesh.material.map = texture;
selectedMesh.material.needsUpdate = true;
}يعتمد هذا النهج على حالة عامة وترابط وثيق ومنطق يعيش خارج الكائن.
✅ النهج القائم على المكونات (Needle Engine)
التحديد مكوّن:
import { Behaviour, PointerEventData } from "@needle-tools/engine";
import { SelectionManager } from "./SelectionManager.js";
export class Selectable extends Behaviour {
onPointerClick(_args: PointerEventData) {
// Update the global selection
SelectionManager.current = this.gameObject;
}
}ومعالجة الخامة مكوّن آخر:
import { Behaviour, Object3D } from "@needle-tools/engine";
import * as THREE from "three";
export class MaterialSlot extends Behaviour {
@Object3D()
mesh!: THREE.Mesh;
setTextureColor(color: number) {
const mat = this.mesh.material as THREE.MeshStandardMaterial;
mat.color.set(color);
mat.needsUpdate = true;
}
}import { Behaviour } from "@needle-tools/engine";
export class SelectionManager extends Behaviour {
static current: any = null;
}ويبقى كود الواجهة عامًا تمامًا:
import { onStart, addComponent } from "@needle-tools/engine";
import * as THREE from "three";
import { Selectable } from "./Selectable.js";
import { MaterialSlot } from "./MaterialSlot.js";
import { SelectionManager } from "./SelectionManager.js";
onStart(context => {
const scene = context.scene;
context.mainCamera.position.set(0, 1, 5);
// Add a SelectionManager to the scene
addComponent(scene, new SelectionManager());
// Create objects
const cube = new THREE.Mesh(
new THREE.BoxGeometry(1, 1, 1),
new THREE.MeshStandardMaterial({ color: 0xaaaaaa })
);
cube.position.set(-1, 0.5, 0);
scene.add(cube);
const sphere = new THREE.Mesh(
new THREE.SphereGeometry(0.5, 32, 32),
new THREE.MeshStandardMaterial({ color: 0xff0000 })
);
sphere.position.set(1, 0.5, 0);
scene.add(sphere);
// Attach components
addComponent(cube, new Selectable());
addComponent(cube, new MaterialSlot());
addComponent(sphere, new Selectable());
addComponent(sphere, new MaterialSlot());
// Create the UI
const menu = document.createElement("div");
document.body.appendChild(menu);
[0xff0000, 0x00ff00, 0x0000ff, 0xffff00].forEach((color, index) => {
const btn = document.createElement("button");
btn.textContent = `Color ${index + 1}`;
btn.style.backgroundColor = `#${color.toString(16)}`;
btn.addEventListener("click", () => {
const selected = SelectionManager.current;
if (!selected) return;
selected.getComponent(MaterialSlot)?.setTextureColor(color);
});
menu.appendChild(btn);
});
});لماذا يهم هذا
- منطق التحديد لا يعرف شيئًا عن الخامات.
- منطق الخامة لا يعرف شيئًا عن الإدخال أو الـ Raycasting.
- الواجهة لا تعرف شيئًا عن الشبكات أو Three.js.
لكل كائن قدراته الخاصة بدل الاعتماد على دوال عامة. هذه هي الفكرة الجوهرية وراء Needle Engine: البنية والملكية، لا العرض فقط.
🧭 الخلاصة
Needle Engine ليس «أفضل من Three.js» ولا «بديلًا عنه». إنه طبقة مختلفة من التفكير — وفهم هذه الطبقة يمكن أن يحسّن كثيرًا طريقة تصميم المطورين المتمرسين لتطبيقات ثلاثية الأبعاد معقدة.



