JavaScript ב-Engine
ה-API של הסקריפטים של Engine: מחזור החיים, מלכודות הקואורדינטות, שלוש הדרכים ליצור אובייקט, ואיך מזיזים אחד בלי ללכת דרך קירות.
בשורה אחת#
כל entity יכול לשאת סקריפט JavaScript. הוא רץ בדפדפן, בלשונית שלכם, מול הסצנה החיה, והוא ה-API הקאנוני לכתיבת סקריפטים: ניב ה-BASIC, ותצוגות Python, C# ו-Rust בעורך, כולם מתקמפלים לאותו משטח.
הרשימה המלאה היא רפרנס ה-API: 538 חברים ב-50 namespaces, עמוד אחד לכל אחד, עם חיפוש. העמוד הזה הוא מה שצריך לדעת לפני שקוראים אותו.
מחזור החיים#
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 הם גלובליים; getComponent(type)
קורא רכיב מה-entity שמחזיק את הסקריפט.
התנגשויות וטריגרים מופעלים גם ב-2D וגם ב-3D, וכל צד מקבל את נורמל
המגע שלו: other.contact.normal מצביע מה-entity האחר כלפי זה. זה מה
שמאפשר לסקריפט להבדיל בין "נחתתי עליו" ל"הוא נחת עלי", דריכת פלטפורמר
קוראת other.contact.normal.y > 0.5.
שתי מלכודות הקואורדינטות#
שתיהן עולות זמן דיבוג אמיתי, ואף אחת לא נכשלת בקול רם.
עכבר: page מול canvas#
input.mousePosition הוא בקואורדינטות page. scene.pick() רוצה
קואורדינטות canvas. העברת אחת במקום השנייה לא מפספסת, היא פוגעת
בנקודה שהוסטה לפי מיקום ה-viewport בחלון, כך שגיבור שנע בקליק הולך למקום
סביר אך שגוי.
קראו ל-scene.pick() בלי ארגומנט (הוא מכוון לסמן), או השתמשו ב-
input.mouseViewport כשאתם צריכים את המספרים.
בקרים וירטואליים הם בפיקסלי canvas#
input.addVirtualJoystick(id, x, y, size) ו-input.addVirtualButton(...)
ממקמים את המרכזים שלהם באותו מרחב כמו mouseViewport. ג'ויסטיק מחזיר
X/Y מנורמל אחרי אזור המת הרדיאלי שלו; כפתור חושף held,
pressed-this-frame ו-released-this-frame בנפרד. המצביע שלהם נלכד
ונצרך, כך שמגע על הבקר לא מדווח גם כפעולת canvas גולמית.
הג'ויסטיק ברירת המחדל מאוחד.
input.getJoystickX/Y()בוחר וקטור שלם אחד מהמקל שעל המסך, gamepad פיזי, או המקלדת (WASD/חצים), לפי מי חזק יותר. pad מחובר במצב מנוחה לכן לא מבטל תנועת מקלדת. הציר Y שלו עוקב אחרי מרחב מסך 2D: למעלה זה-1.
שלוש דרכים ליצור אובייקט, והן לא ניתנות להחלפה#
| מה זה יוצר | חי כמה זמן | |
|---|---|---|
scene.createEntity(def) | entity מחובר: הוא מופיע ב-Scene Graph וב-Inspector | הפרויקט, הוא נשמר |
game.spawn(type, pos) | פרימיטיב חשוף: בלי בריאות, בלי AI, בלי התנהגות | סשן המשחק |
game.spawnFrom(template, pos, opts) | עותק של entity שלם: components, גוף פיזיקה, tags, layer, קנה מידה, וערימת ההתנהגות המקומפלת | סשן המשחק |
game.spawn נכון לגרוטאות ולפרוטוטייפים וחסר תועלת לאויבים: spawner
שמוזן ממנו מייצר כדורים דוממים. התבנית ש-spawnFrom מעתיק היא entity
רגיל שאתם מרכיבים בעורך ומכבים עם העין בהיררכיה: אין פורמט נפרד, אין
עורך שני, מה שרואים זה מה שמופיע.
// 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, שם או tag. עותקים שנוצרים בזמן משחק מוגבלים למשחק,
כך ש-stop() מסיר אותם והסצנה בעריכה לעולם לא מזוהמת.
כיבוי entity אומר מוסתר ושקט כאחד: הצומת מנוטרל, הסקריפט נעצר, גוף הפיזיקה לא מגיב יותר. הסתרה בלבד הייתה משאירה אויב בלתי-נראה שעדיין פוגע בכם.
game.setEnabled(id, on)מפעיל את זה מסקריפט, אייקון העין מפעיל את זה מההיררכיה.
הזזת משהו: דחפו גוף, אל תטלפרו#
כתיבת transform.position כל פריים הולכת דרך קירות, דרך מפלצות אחרות
ודרך הרצפה. הפעילו את המהירות במקום, אבל רק כשיש מה להפעיל:
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 בלי גוף ממלא שדה שאף אחד לא משלב, וה-entity נעצר
לגמרי. זו הסיבה ש-physics.hasBody קיים, וזו הסיבה שכל התנהגות תנועה
בקטלוג שואלת את השאלה במקום להניח תשובה. עצירה אומרת ביטול המהירות:
לא לעשות כלום משאיר את הגוף גולש.
כתיבת מיקום כן מזיזה גוף פיזיקה, עכשיו. Havok מנהל את ה-transform, אף פעם לא ההפך, כך שהשמה ל-
transform.positionעל גוף דינמי נהגה להיכתב מחדש בצעד הבא: כל טלפורט נכשל בשקט על entity פיזי. ה-proxy של המיקום מסנכרן מחדש את הגוף, כפי שה-proxy של הסיבוב תמיד עשה.
Pathfinding הוא תמונת מצב#
pathfinding.createGrid() שולח קרניים על רשת פעם אחת, ממורכזת במקום
שבו נקראה. יציאה מהתיבה ההיא מחזירה null, ורוב הקוראים אז נופלים
לקו ישר, דרך קירות, בלי מילה.
הרשת ממורכזת מחדש בברירת המחדל בכל נסיעה מבוקשת. setAutoRecenter(false)
מוותר על זה בעולם קבוע, שם בנייה מחדש עולה מעבר קרניים לחינם.
console.log רושם. Print מצייר.#
הקונסולה היא console.log / warn / error, כמו בכל מקום אחר. ה-Print
של BASIC מצייר על המסך ונקרא כל פריים, השניים
מכוונים להיות פקודות נפרדות, ובלבול ביניהם הוא
מה שממלא קונסולה בלולאת פריים.
איפה סקריפט רץ#
בעורך ובבנייה web, סקריפטים רצים בדפדפן. בבנייה desktop או מובייל native, הם רצים ללא שינוי על runtime מובנה של QuickJS, אותו מקור, לא port.
סקריפטים נשמרים במסד הנתונים של הדפדפן עצמו תוך כדי עריכה, כך שבאפר שלא נשמר שורד רענון; הם נשמרים לתוך הפרויקט עם כל השאר.
מגבלות ותקלות נפוצות#
scene.pick()עם ארגומנט רוצה קואורדינטות canvas. ראו למעלה; זו הבאג הכי נפוץ במקום הלא נכון.game.spawnמייצר פרימיטיב, לא דמות. השתמשו ב-spawnFrom.- גוף נוצר עבור entities שנולדו באמצע המשחק. הפיזיקה נהגתה להיבנות רק ב-Play, כך שכל דבר ש-spawn אחר כך נפל דרך העולם בשקט בעוד שכניו שהוצבו ביד התנהגו כרגיל.
- שמונה שלבים הוא התקרה של חבר הצוות ה-AI, לא שלכם. ללולאת סקריפט
אין מגבלה כזו;
whileבלתי נשלט יתקע את הפריים כמו בכל מקום אחר. - ארבעת תצוגות השפה האחרות מתומללות מזו. עריכת תצוגת Python או Rust וחזרה עוברת דרך ה-JavaScript השמור, ראו עמוד ה-BASIC להסבר למה.