JavaScript في Engine

واجهة سكريبت Engine: دورة الحياة، وفخاخ الإحداثيات، والطرق الثلاث لإنشاء كائن، وكيفية تحريكه دون المرور عبر الجدران.

باختصار#

يمكن لكل كيان (entity) أن يحمل سكريبت JavaScript. يعمل في المتصفح، في تبويبك، على المشهد الحي، وهو واجهة السكريبت المرجعية (canonical): تُترجَم لهجة BASIC، وعروض Python وC# وRust في المحرر، كلها إلى السطح نفسه.

القائمة الشاملة هي مرجع الواجهة: 538 عضوًا في 50 مساحة اسم (namespace)، صفحة لكل واحد، مع بحث. هذه الصفحة هي ما تحتاج معرفته قبل قراءته.

دورة الحياة#

function start() {}                     // once, when the entity starts
function update(dt) {}                  // every frame, dt in seconds
function fixedUpdate(dt) {}             // fixed timestep, for physics
function onCollisionEnter(other) {}     // first frame of contact
function onCollisionStay(other) {}      // every frame while touching
function onCollisionExit(other) {}      // contact just ended
function onTriggerEnter(other) {}       // entered a trigger volume
function onTriggerExit(other) {}
function onMessage(msg, data, senderId) {}   // entity-to-entity messaging
function onAnimationEvent(name, data) {}
function onDestroy() {}
function main() {}                      // console-game entry, async allowed

deltaTime وtime وentityId وentity متغيرات عامة (globals)؛ وgetComponent(type) يقرأ مكوّنًا من الكيان المالك للسكريبت.

تُطلَق الاصطدامات والمشغِّلات (triggers) في 2D وفي 3D معًا، ويتلقى كل طرف عمود التماس (normal) الخاص به: يشير other.contact.normal من الكيان الآخر نحو هذا الكيان. هذا ما يتيح لسكريبت التمييز بين «هبطتُ عليه» و«هبط عليّ»، فدوس منصة (platformer stomp) يقرأ other.contact.normal.y > 0.5.

فخا الإحداثيات#

كلاهما يكلِّف وقت تصحيح أخطاء حقيقيًّا، ولا يفشل أيٌّ منهما بصوت مرتفع.

الفأرة: page مقابل canvas#

يكون input.mousePosition بإحداثيات page. ويريد scene.pick() إحداثيات canvas. تمرير أحدهما بدلًا من الآخر لا يُخطئ الهدف، بل يصيب نقطة مُزاحة بموضع منفذ العرض في النافذة، فيمشي بطل «انقر للتحرك» إلى مكان معقول وخاطئ.

استدعِ scene.pick() بلا معامل (يستهدف المؤشر)، أو استخدم input.mouseViewport عندما تحتاج الأرقام.

عناصر التحكم الافتراضية بوحدات بكسل canvas#

تضع input.addVirtualJoystick(id, x, y, size) وinput.addVirtualButton(...) مراكزها في الفضاء نفسه لـmouseViewport. يعيد عصا التحكم (joystick) قيمة X/Y مُطبَّعة بعد منطقة موتها الشعاعية (dead zone)؛ ويكشف الزر held و pressed-this-frame وreleased-this-frame كلًّا على حدة. يُلتَقط مؤشرها ويُستهلَك، لذا لا تُبلَّغ لمسة على عنصر التحكم أيضًا كإجراء canvas خام.

عصا التحكم الافتراضية موحَّدة. يختار input.getJoystickX/Y() متجهًا كاملًا واحدًا من العصا على الشاشة، أو لوحة ألعاب فيزيائية، أو لوحة المفاتيح (WASD/الأسهم)، أيًّا كان المصدر الأقوى. لذا لوحة ألعاب متصلة وخاملة لا تُعطِّل الحركة بلوحة المفاتيح. يتبع محورها Y فضاء شاشة 2D: الأعلى هو -1.

ثلاث طرق لإنشاء كائن، وهي غير قابلة للتبادل#

ما الذي ينشئهيعيش من أجل
scene.createEntity(def)كيان مُؤلَّف (authored): يظهر في Scene Graph وفي Inspectorالمشروع، ويُحفَظ
game.spawn(type, pos)بدائي عارٍ: بلا صحة، ولا ذكاء اصطناعي، ولا سلوكجلسة اللعب
game.spawnFrom(template, pos, opts)نسخة من كيان كامل: المكوّنات، وجسم الفيزياء، والوسوم، والطبقة، والمقياس، ومكدس السلوك المُصرَّفجلسة اللعب

game.spawn مناسب للحطام والنماذج الأولية وعديم الفائدة للأعداء: مولِّد (spawner) يُغذَّى به ينتج كرات خاملة. القالب (template) الذي تنسخه spawnFrom كيان عادي تؤلِّفه في المحرر وتُطفِئه بعين التسلسل الهرمي: لا صيغة منفصلة، ولا محرر ثانٍ، فما تراه هو ما يظهر.

// Once, at start: a missing template should say so, not fail every wave silently
if (!game.hasTemplate('Skeleton')) console.warn('no Skeleton template');

const mob = game.spawnFrom('Skeleton', { x, y, z }, { tag: 'Enemy' });

يقبل template معرِّفًا (id)، أو اسمًا، أو وسمًا. النُّسخ المُنشأة أثناء اللعب مقيَّدة بنطاق اللعب، لذا يزيلها stop() ولا يتلوّث مشهد التحرير أبدًا.

إطفاء كيان يعني مخفيًّا وصامتًا معًا: العقدة معطَّلة، والسكريبت متوقف، وجسم الفيزياء لم يعد يستجيب. الإخفاء وحده سيترك عدوًّا غير مرئي لا يزال يضربك. يُشغِّله game.setEnabled(id, on) من سكريبت، وتُشغِّله أيقونة العين من التسلسل الهرمي.

تحريك شيء ما: ادفع جسمًا، لا تنقله آنيًا#

كتابة transform.position كل إطار يمشي عبر الجدران، وعبر الوحوش الأخرى، وعبر الأرضية. وجِّه السرعة (velocity) بدلًا من ذلك، لكن فقط عندما يكون هناك ما توجِّهه:

if (physics.hasBody) {
  const v = physics.getVelocity();
  physics.setVelocity({ x: vx, y: v.y, z: vz });   // pushes a real body
} else {
  transform.position.x += vx * dt;                 // legitimate for a flyer
}

physics.setVelocity بلا جسم يملأ حقلًا لا يدمجه أحد، فيتوقف الكيان تمامًا. لهذا يوجد physics.hasBody، ولهذا يطرح كل سلوك حركة في الكتالوج السؤال بدلًا من افتراض إجابة. التوقف يعني إلغاء السرعة: عدم فعل شيء يترك الجسم منزلقًا.

كتابة موضع تُحرِّك جسم فيزياء الآن، فعلًا. يقود Havok التحويل (transform)، لا العكس أبدًا، لذا كان تعيين transform.position على جسم ديناميكي يُستبدَل في الخطوة التالية: كان كل نقل آني (teleport) يفشل بصمت على كيان فيزيائي. يُعيد وكيل الموضع (position proxy) مزامنة الجسم، بالطريقة نفسها التي كان يفعلها وكيل الدوران (rotation proxy) دائمًا.

إيجاد المسار لقطة (snapshot)#

يُطلق pathfinding.createGrid() أشعة على شبكة مرة واحدة، مُمركَزة حيث استُدعيت. مغادرة ذلك الصندوق تُعيد null، وعندها يعود معظم المستدعين إلى خط مستقيم، عبر الجدران، دون كلمة.

تُعيد الشبكة تمركزها على الرحلة المطلوبة افتراضيًا. setAutoRecenter(false) يُلغي ذلك في عالم ثابت، حيث تكلِّف إعادة البناء مرور أشعة بلا طائل.

console.log يسجِّل. Print يرسم.#

الطرفية (console) هي console.log / warn / error، كما في أي مكان آخر. أما Print في BASIC فهو يرسم على الشاشة ويُستدعى كل إطار، والاثنان أمران منفصلان عن قصد، والخلط بينهما هو ما يملأ الطرفية بحلقة إطارات.

أين يعمل السكريبت#

في المحرر وفي بناء ويب، تعمل السكريبتات في المتصفح. في بناء سطح مكتب أو جوّال أصلي (native)، تعمل دون تغيير على بيئة تشغيل QuickJS مضمَّنة، المصدر نفسه، لا منفذ (port).

تُخزَّن السكريبتات في قاعدة بيانات المتصفح نفسها أثناء تحريرك، لذا ينجو buffer غير محفوظ من إعادة تحميل؛ وتُحفَظ في المشروع مع كل شيء آخر.

القيود والمشاكل الشائعة#

  • scene.pick() بمعامل يريد إحداثيات canvas. انظر أعلاه؛ إنه خطأ الموضع الخاطئ الأكثر شيوعًا على الإطلاق.
  • game.spawn يصنع بدائيًا، لا شخصية. استخدم spawnFrom.
  • يُنشأ جسم للكيانات المولودة في منتصف اللعبة. كانت الفيزياء تُبنى فقط عند Play، لذا كان كل ما يُولَّد بعد ذلك يسقط عبر العالم بصمت بينما يتصرف جيرانه الموضوعون يدويًا بشكل طبيعي.
  • ثماني خطوات هي سقف رفيق الذكاء الاصطناعي، لا سقفك أنت. حلقة سكريبت ليس لها هذا الحد؛ وwhile هارب سيُعلِّق الإطار كما في أي مكان آخر.
  • عروض اللغات الأربعة الأخرى مُترجَمة (transpiled) من هذه. تحرير عرض Python أو Rust والعودة يمر عبر JavaScript المُخزَّن؛ انظر صفحة BASIC لمعرفة السبب.