JavaScript ใน Engine

API สคริปต์ของ Engine: วงจรชีวิต กับดักเรื่องพิกัด สามวิธีสร้างอ็อบเจ็กต์ และวิธีย้ายอ็อบเจ็กต์โดยไม่เดินทะลุกำแพง

สรุปในประโยคเดียว#

ทุกเอนทิตีสามารถมีสคริปต์ JavaScript ได้ มันทำงานในเบราว์เซอร์ ในแท็บของคุณ กับฉากที่ทำงานจริง และมันคือ API สคริปต์ หลักที่เป็นทางการ: ภาษาถิ่น BASIC และมุมมอง Python, C# และ Rust ในตัวแก้ไข ล้วนคอมไพล์ลงไปสู่พื้นผิวเดียวกันนี้

รายการทั้งหมดอยู่ที่ เอกสารอ้างอิง API: 538 สมาชิกใน 50 เนมสเปซ หนึ่งหน้าต่อหนึ่งอัน พร้อมระบบค้นหา หน้านี้คือสิ่งที่คุณ ต้องรู้ ก่อน ไปอ่านหน้านั้น

วงจรชีวิต#

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) อ่านคอมโพเนนต์จากเอนทิตีที่เป็นเจ้าของสคริปต์

การชนกันและ trigger เกิดขึ้นได้ทั้งใน 2D และ 3D และแต่ละฝ่ายได้รับ contact normal ของตัวเอง: other.contact.normal ชี้จากเอนทิตี ฝั่งตรงข้าม มายังฝั่งนี้ นั่นคือสิ่งที่ทำให้สคริปต์บอกความต่างระหว่าง "ฉันลงจอดบนมัน" กับ "มันลงจอดบนฉัน" ได้ การกระโดดเหยียบแบบ platformer อ่านค่า 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 จอยสติ๊กจะคืนค่า X/Y ที่ทำ normalize แล้วหลังผ่านโซนตายแบบเชิงรัศมี ปุ่มหนึ่งจะเปิดเผยสถานะ held, pressed-this-frame และ released-this-frame แยกกัน ตัวชี้ของมันถูกจับและ บริโภคไป ดังนั้นการแตะที่ตัวควบคุมจะไม่ถูกรายงานเป็นการกระทำแบบ canvas ดิบซ้ำอีก

จอยสติ๊กเริ่มต้นเป็นแบบรวมเข้าด้วยกัน input.getJoystickX/Y() เลือกเวกเตอร์ เดียวที่สมบูรณ์จากสติ๊กบนหน้าจอ เกมแพดจริง หรือ คีย์บอร์ด (WASD/ลูกศร) อันไหนก็ตามที่มีสัญญาณแรงที่สุด แพดที่เชื่อมต่ออยู่แต่ไม่ได้ใช้งานจึงไม่ปิดกั้น การเคลื่อนที่ด้วยคีย์บอร์ด แกน Y ของมันตามพื้นที่หน้าจอ 2D: ขึ้นคือ -1

สามวิธีสร้างอ็อบเจ็กต์ และมันใช้แทนกันไม่ได้#

สร้างอะไรอยู่ได้นานแค่ไหน
scene.createEntity(def)เอนทิตีที่ ถูกสร้างไว้แล้ว: ปรากฏใน Scene Graph และ Inspectorตลอดโปรเจกต์ มันถูกบันทึกไว้
game.spawn(type, pos)รูปทรงพื้นฐานเปล่า: ไม่มีเลือด ไม่มี AI ไม่มีพฤติกรรมตลอดเซสชันการเล่น
game.spawnFrom(template, pos, opts)สำเนาของเอนทิตีทั้งตัว: คอมโพเนนต์ physics body แท็ก เลเยอร์ สเกล และชุดพฤติกรรมที่คอมไพล์แล้วตลอดเซสชันการเล่น

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() จะเอามันออก และฉากสำหรับแก้ไขจะไม่มีวันเปื้อน

การปิดเอนทิตีหมายถึงทั้งซ่อนและเงียบ: โหนดถูกปิด สคริปต์หยุด physics body ไม่ตอบสนองอีกต่อไป การซ่อนอย่างเดียวจะทิ้งศัตรูที่มองไม่เห็นไว้ให้ยังโจมตีคุณได้ game.setEnabled(id, on) ขับเคลื่อนมันจากสคริปต์ ไอคอนรูปตาขับเคลื่อนมันจาก โครงสร้างต้นไม้

การย้ายบางสิ่ง: ผลักตัว body อย่าเทเลพอร์ต#

การเขียน 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 โดยไม่มี body จะเติมฟิลด์ที่ไม่มีอะไรนำไปรวมคำนวณต่อ และ เอนทิตีจะหยุดนิ่งทันที นั่นคือเหตุผลที่ physics.hasBody มีอยู่ และเหตุผลที่ทุก พฤติกรรมการเคลื่อนไหวในแคตตาล็อกถามคำถามนี้แทนที่จะสันนิษฐานคำตอบ การหยุด หมายถึงการยกเลิกความเร็ว: การไม่ทำอะไรเลยจะปล่อยให้ body ไถลต่อไป

การเขียนตำแหน่งจะย้าย physics body จริงในตอนนี้แล้ว Havok ขับเคลื่อน transform ไม่เคยกลับกัน ดังนั้นการกำหนด transform.position บน dynamic body เคยถูกเขียนทับในสเต็ปถัดไป: การเทเลพอร์ตทุกครั้งล้มเหลวอย่างเงียบ ๆ บน เอนทิตีที่มี physics ตัวกลาง (proxy) ของตำแหน่งซิงค์ body กลับ เหมือนที่ตัวกลาง ของการหมุนเคยทำมาตลอด

Pathfinding คือภาพนิ่ง#

pathfinding.createGrid() ยิงรังสีสร้างกริด เพียงครั้งเดียว โดยมีศูนย์กลางอยู่ ที่จุดที่มันถูกเรียก การออกนอกกรอบนั้นจะคืนค่า null และผู้เรียกส่วนใหญ่จะถอยกลับ ไปใช้เส้นตรง ทะลุกำแพง โดยไม่บอกกล่าว

กริดจะจัดกึ่งกลางใหม่ตามการเดินทางที่ร้องขอเป็นค่าเริ่มต้น setAutoRecenter(false) ใช้เพื่อปฏิเสธพฤติกรรมนี้บนโลกที่ตายตัว ซึ่งการสร้างใหม่มีต้นทุนเป็นการยิงรังสีโดย เปล่าประโยชน์

console.log บันทึกล็อก Print วาดภาพ#

คอนโซลคือ console.log / warn / error เหมือนที่อื่นทุกแห่ง Print ของ BASIC วาดลงบนหน้าจอ และถูกเรียกทุกเฟรม ทั้งสองคำสั่งนี้แยกจากกันโดยตั้งใจ และการปนกันคือสิ่งที่ทำให้คอนโซลเต็มไปด้วยลูปเฟรม

จุดที่สคริปต์ทำงาน#

ในตัวแก้ไขและใน web build สคริปต์ทำงานในเบราว์เซอร์ ใน native desktop หรือ mobile build มันทำงานเหมือนเดิมบนรันไทม์ QuickJS ที่ฝังไว้ ใช้ต้นฉบับ เดียวกัน ไม่ใช่การพอร์ต

สคริปต์ถูกเก็บไว้ในฐานข้อมูลของเบราว์เซอร์เองระหว่างที่คุณแก้ไข ดังนั้นบัฟเฟอร์ที่ ยังไม่บันทึกจะรอดจากการโหลดหน้าใหม่ มันถูกบันทึกลงในโปรเจกต์พร้อมกับทุกอย่างอื่น

ข้อจำกัดและปัญหาที่พบบ่อย#

  • scene.pick() ที่มีอาร์กิวเมนต์ต้องการพิกัดแบบ canvas ดูด้านบน นี่คือบั๊กเรื่อง พิกัดผิดที่พบบ่อยที่สุด
  • game.spawn สร้างรูปทรงพื้นฐาน ไม่ใช่ตัวละคร ใช้ spawnFrom แทน
  • body จะถูกสร้างให้กับเอนทิตีที่เกิดขึ้นกลางเกม เดิม physics ถูกสร้างเฉพาะตอน Play เท่านั้น ดังนั้นอะไรก็ตามที่ spawn หลังจากนั้นจะตกทะลุโลกอย่างเงียบ ๆ ในขณะที่ เพื่อนบ้านที่วางด้วยมือทำงานปกติ
  • แปดขั้นตอนคือเพดานของทีมงาน AI ไม่ใช่ของคุณ ลูปในสคริปต์ไม่มีขีดจำกัดแบบนั้น while ที่ควบคุมไม่ได้จะทำให้เฟรมค้างเหมือนที่ใดก็ตาม
  • มุมมองภาษาอีกสี่แบบถูกแปลงมาจากตัวนี้ การแก้ไขมุมมอง Python หรือ Rust แล้วสลับกลับมาจะผ่านทาง JavaScript ที่เก็บไว้ ดูหน้า BASIC สำหรับเหตุผล