Engine में JavaScript

Engine की script API: lifecycle, coordinate ट्रैप, किसी ऑब्जेक्ट को बनाने के तीन तरीक़े, और दीवारों के आर-पार चले बिना उसे कैसे हिलाया जाए।

एक लाइन में#

हर entity एक JavaScript स्क्रिप्ट रख सकती है। यह ब्राउज़र में, आपके टैब में, live सीन के ख़िलाफ़ चलती है, और यही canonical scripting API है: BASIC डायलेक्ट, और editor में Python, C# और Rust व्यू, सब यहीं आकर उसी surface पर कंपाइल होते हैं।

पूरी लिस्ट API रेफ़रेंस में है: 50 namespace में 538 members, हर एक का अपना पेज, एक सर्च के साथ। यह पेज वह है जो आपको उसे पढ़ने से पहले जानना चाहिए।

Lifecycle#

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) उस entity से एक component पढ़ता है जिसकी यह स्क्रिप्ट है।

Collision और trigger 2D और 3D दोनों में चलते हैं, और हर तरफ़ को अपना contact normal मिलता है: other.contact.normal दूसरी entity से इस वाली की तरफ़ इशारा करता है। यही किसी स्क्रिप्ट को "मैं इस पर उतरा" और "यह मुझ पर उतरा" में फ़र्क़ करने देता है, कोई platformer stomp other.contact.normal.y > 0.5 पढ़ता है।

दो coordinate ट्रैप#

दोनों असली debugging समय लेते हैं, और कोई भी ज़ोर से फेल नहीं होता

Mouse: page बनाम canvas#

input.mousePosition page coordinates में होता है। scene.pick() को canvas coordinates चाहिए। एक को दूसरे की जगह पास करना चूकता नहीं, यह विंडो में viewport की पोज़िशन जितने offset पर किसी पॉइंट को हिट करता है, इसलिए कोई click-to-move हीरो किसी ऐसी जगह चला जाता है जो लगती तो सही है पर होती ग़लत है।

scene.pick() को बिना argument बुलाएँ (यह cursor पर निशाना लगाता है), या जब आपको नंबर चाहिए तो input.mouseViewport इस्तेमाल करें।

Virtual control canvas pixel में होते हैं#

input.addVirtualJoystick(id, x, y, size) और input.addVirtualButton(...) अपने केंद्र उसी स्पेस में रखते हैं जिसमें mouseViewport है। कोई joystick अपने radial dead zone के बाद एक normalised X/Y लौटाता है; कोई button held, pressed-this-frame और released-this-frame को अलग-अलग एक्सपोज़ करता है। इनका pointer capture और consume हो जाता है, इसलिए control पर किया गया कोई touch एक साथ raw canvas action के रूप में भी रिपोर्ट नहीं होता।

डिफ़ॉल्ट joystick एकीकृत है। input.getJoystickX/Y() on-screen stick, किसी physical gamepad, या keyboard (WASD/arrows) में से एक पूरा vector चुनता है, जो भी source सबसे मज़बूत हो। इसलिए कोई idle पड़ा हुआ connected pad keyboard से चलना बंद नहीं करता। इसका Y axis 2D screen space का अनुसरण करता है: ऊपर -1 है।

किसी ऑब्जेक्ट को बनाने के तीन तरीक़े, और ये एक-दूसरे की जगह इस्तेमाल नहीं हो सकते#

यह क्या बनाता हैकब तक जीवित रहता है
scene.createEntity(def)एक authored entity: यह Scene Graph और Inspector में दिखती हैपूरे प्रोजेक्ट भर, यह सेव होती है
game.spawn(type, pos)एक नंगा primitive: कोई health नहीं, कोई AI नहीं, कोई behaviour नहींplay session भर
game.spawnFrom(template, pos, opts)पूरी एक entity की कॉपी: components, physics body, tags, layer, scale और compiled behaviour stackplay session भर

game.spawn मलबे और prototype के लिए सही है और दुश्मनों के लिए बेकार: इससे भरा हुआ कोई spawner निष्क्रिय गेंदें बनाता है। जिस template को spawnFrom कॉपी करता है वह एक सामान्य entity है जिसे आप editor में बनाते हैं और hierarchy की आँख से बंद करते हैं: कोई अलग फ़ॉर्मैट नहीं, कोई दूसरा editor नहीं, इसलिए जो आप देखते हैं वही दिखता भी है।

// 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 स्वीकार करता है। play के दौरान बनाई गई कॉपी play-scoped होती हैं, इसलिए stop() उन्हें हटा देता है और edit सीन कभी गंदा नहीं होता।

किसी entity को बंद करने का मतलब है छुपी हुई AND ख़ामोश दोनों: node disabled, script stopped, physics body अब जवाब नहीं देता। सिर्फ़ छुपाने से एक अदृश्य दुश्मन बच जाता जो फिर भी आपको मारता रहता। game.setEnabled(id, on) इसे किसी स्क्रिप्ट से चलाता है, eye आइकन इसे hierarchy से चलाता है।

किसी चीज़ को हिलाना: body को push करें, टेलीपोर्ट न करें#

हर फ़्रेम 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
}

बिना body के physics.setVelocity एक ऐसी फ़ील्ड भरता है जिसे कोई integrate नहीं करता, और entity बिल्कुल रुक जाती है। यही वजह है कि physics.hasBody मौजूद है, और यही वजह है कि कैटलॉग में हर moving behaviour जवाब मान लेने के बजाय यह सवाल पूछता है। रुकने का मतलब है velocity को cancel करना: कुछ न करने से body coast करती रहती है।

कोई position लिखना अब सचमुच किसी physics body को हिला भी देता है। Havok transform को चलाता है, कभी उल्टा नहीं, इसलिए किसी dynamic body पर transform.position को assign करना पहले अगले step पर overwrite हो जाता था: हर teleport किसी physical entity पर चुपचाप फेल हो जाता था। Position proxy अब body को दोबारा sync करता है, ठीक वैसे ही जैसे rotation proxy हमेशा से करता आया है।

Pathfinding एक snapshot है#

pathfinding.createGrid() किसी grid को एक बार raycast करता है, वहीं केंद्रित जहाँ इसे बुलाया गया था। उस box को छोड़ते ही null मिलता है, और ज़्यादातर callers फिर बिना कुछ बोले दीवारों के आर-पार एक सीधी लाइन पर वापस चले जाते हैं।

डिफ़ॉल्ट रूप से grid हर माँगी गई ट्रिप पर फिर से केंद्रित होता है। setAutoRecenter(false) किसी fixed दुनिया पर इससे बाहर निकल जाता है, जहाँ दोबारा बनाना बिना किसी फ़ायदे के एक raycast पास ख़र्च करता है।

console.log लॉग करता है। Print ड्रॉ करता है।#

console वही console.log / warn / error है, जैसे बाक़ी हर जगह। BASIC का Print स्क्रीन पर ड्रॉ करता है और हर फ़्रेम बुलाया जाता है, ये दोनों जान-बूझकर अलग कमांड हैं, और इन्हें गड्डमड्ड करना ही किसी console को frame loop से भर देता है।

कोई स्क्रिप्ट कहाँ चलती है#

editor में और किसी web build में, स्क्रिप्ट ब्राउज़र में चलती हैं। किसी native desktop या mobile build में, ये बिना बदले एक embedded QuickJS runtime पर चलती हैं, वही सोर्स, कोई port नहीं।

एडिट करते समय स्क्रिप्ट ब्राउज़र के अपने डेटाबेस में स्टोर होती हैं, इसलिए कोई unsaved buffer किसी reload के बाद भी बचा रहता है; ये बाक़ी सब कुछ के साथ प्रोजेक्ट में सेव होती हैं।

सीमाएँ और आम दिक़्क़तें#

  • argument वाले scene.pick() को canvas coordinates चाहिए। ऊपर देखें; यह सबसे आम wrong-place bug है।
  • game.spawn एक primitive बनाता है, कोई character नहीं। spawnFrom इस्तेमाल करें।
  • गेम के बीच में पैदा हुई entity के लिए भी एक body बनाई जाती है। Physics पहले सिर्फ़ Play पर ही बनी जाती थी, इसलिए बाद में spawn हुई कोई भी चीज़ चुपचाप दुनिया से नीचे गिर जाती थी, जबकि उसके हाथ से रखे गए पड़ोसी सही व्यवहार करते थे।
  • आठ स्टेप AI टीममेट की सीमा है, आपकी नहीं। किसी स्क्रिप्ट loop की कोई ऐसी सीमा नहीं है; कोई बेक़ाबू while बाक़ी हर जगह की तरह फ़्रेम को अटका देगा।
  • बाक़ी चार भाषा व्यू इसी से transpile होते हैं। किसी Python या Rust व्यू को एडिट करना और वापस स्विच करना, stored JavaScript से होकर गुज़रता है, वजह के लिए BASIC पेज देखें।