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 สำหรับเหตุผล