ہر گیم ڈویلپمنٹ ٹیٹوریل ایک ہی طرح سے شروع ہوتا ہے: ایک سرمئی کینوس پر رنگین مستطیلوں (rectangles) کا پھسلنا۔ یہ سنٹیکس (syntax) سیکھنے کے لیے تو ٹھیک ہے، لیکن یہ آپ کو یہ نہیں سکھاتا کہ ایک گیم انجن اصل میں کیسے کام کرتا ہے۔ میں ان مستطیلوں کے چکر سے باہر نکلنا چاہتا تھا۔ میں کچھ ایسا بنانا چاہتا تھا جو ایک حقیقی گیم جیسا محسوس ہو—حرکت، لڑائی، HUD، اور آواز کے ساتھ ایک ٹاپ ڈاؤن ڈنجن کرالر (top-down dungeon crawler)۔

میں نے Phaser v4 کا انتخاب کیا اور اپنے لیے ایک سخت اصول مقرر کیا۔ کوئی بیرونی اثاثے (external assets) نہیں۔ نہ کوئی امیج فائل، نہ کوئی آڈیو کلپ، اور نہ ہی Webpack یا Vite جیسے بلڈ ٹولز۔ پورا پروجیکٹ سادہ JavaScript کے ساتھ لکھی گئی ایک ہی HTML فائل کے اندر ہونا چاہیے تھا۔ یہ پابندی محض کم سے کم از کم (minimalism) کے لیے نہیں تھی۔ اس کا مقصد ہر بہانے اور ہر 'بلیک باکس' کو ختم کرنا تھا۔ جب آپ اپنے علم کی کمی کو چھپانے کے لیے کوئی اسپرائٹ پیک (sprite pack) ڈاؤن لوڈ نہیں کر سکتے، تو آپ مجبور ہو جاتے ہیں کہ انجن کے اندرونی طور پر ٹیکسچرز (textures)، اینیمیشنز، آڈیو، اور اسٹیٹ (state) کو کیسے سنبھالا جاتا ہے، یہ سیکھیں۔

کوڈ کے ذریعے دنیا کی تصویر کشی

ایک عام Phaser پروجیکٹ میں، آپ this.load.image() کو preload فنکشن کے اندر کال کرتے ہیں اور انجن کو ایک PNG فائل کی طرف اشارہ کرتے ہیں۔ اس آپشن کے بغیر، آپ Graphics آبجیکٹ کا رخ کرتے ہیں۔ آپ اسے انسٹینشی ایٹ (instantiate) کرتے ہیں، بنیادی اشکال (primitive shapes) بناتے ہیں—فرش کے ٹائلز کے لیے مستطیل، دیواروں کے لیے موٹی لکیریں، شاید کھلاڑی کے نشان کے لیے ایک دائرہ—اور پھر generateTexture کو کال کرتے ہیں۔ یہ میتھڈ گرافکس بفر کو کیپچر کرتا ہے اور اسے آپ کے منتخب کردہ کی (key) کے تحت Phaser کے ٹیکسچر مینیجر کے ساتھ رجسٹر کر دیتا ہے۔

اس لمحے سے، انجن اس تیار شدہ بپ میپ (bitmap) کے ساتھ بالکل ایک لوڈ شدہ امیج فائل کی طرح پیش آتا ہے۔ آپ اسے tilemaps پر تفویض کر سکتے ہیں، اسپرائٹس میں تقسیم کر سکتے ہیں، یا اس کا رنگ بدل سکتے ہیں۔ ڈنجن کرالر کے لیے، اس کا مطلب یہ تھا کہ میں اپنے کوڈ ایڈیٹر کو چھوڑے بغیر ایک فرش کا گرڈ (floor grid) تیار کر سکتا تھا، دیواروں کے حصے لگا سکتا تھا، اور کلر پیلیٹ (color palette) میں تبدیلی کر سکتا تھا۔ عملی سبق یہ ہے کہ ایک ٹیکسچر محض میموری میں موجود بپ میپ ڈیٹا کا ایک ٹکڑا ہے۔ Phaser کو اس سے کوئی فرق نہیں پڑتا کہ وہ HTTP ریکویسٹ کے ذریعے آیا ہے یا ہاتھ سے کوڈ کیے گئے Graphics کال کے ذریعے۔

یہ طریقہ کار آپ کو ڈرا آرڈر (draw order) اور بیچنگ (batching) کے بارے میں سوچنے پر بھی مجبور کرتا ہے۔ جب ہر دیوار اور فرش کا ٹائل تیار شدہ ٹیکسچرز کے ایک ہی خاندان سے آتا ہے، تو آپ اس بات پر توجہ دینا شروع کر دیتے ہیں کہ Phaser رینڈر کالز (render calls) کو کیسے گروپ کرتا ہے۔ آپ ایک اسٹیٹک ٹائل میپ لیئر اور انفرادی اشیاء کے اسپرائٹ میپ (spritemap) کے درمیان فرق محسوس کرتے ہیں کیونکہ آپ خود فیصلہ کر رہے ہوتے ہیں کہ کس چیز کو ٹیکسچر انسٹنس کے طور پر موجود ہونا چاہیے۔

اسپرائٹ شیٹس کے بغیر اینیمیشن

ساکن مربع (static squares) جلد ہی بورنگ ہو جاتے ہیں، لیکن Phaser کا اینیمیشن سسٹم ایک اسپرائٹ شیٹ (spritesheet) کی توقع کرتا ہے—جو عام طور پر ایک گرڈ میں ترتیب دیے گئے فریمز کے ساتھ ایک واحد PNG ہوتی ہے۔ میں نے ایک آف اسکرین HTML canvas ایلیمنٹ کا استعمال کرتے ہوئے اس اسٹرپ کی نقل کی۔ اینیمیشن کے ہر فریم کے لیے، میں نے کینوس کو صاف کیا اور ایک نیا پوز (pose) بنایا: تلوار کا ایک سادہ وار، دو قدموں کا واک سائیکل، یا دشمن کا ساکن ہلنا جلنا۔ جب اسٹرپ مکمل ہو گئی، تو میں نے اسے Phaser کے ساتھ ایک اسپرائٹ شیٹ کے طور پر رجسٹر کیا، اور فریم کی چوڑائی اور اونچائی متعین کی تاکہ انجن کو معلوم ہو کہ ایک پوز کہاں ختم ہوتا ہے اور اگلا کہاں سے شروع ہوتا ہے۔

اسے دستی طور پر کرنے سے یہ واضح ہو جاتا ہے کہ سطح کے نیچے اینیمیشن اصل میں کیا ہے: ایک مشترکہ ٹیکسچر پر فریم کی حدود کا ایک مجموعہ۔ Phaser کا اینیمیشن کمپوننٹ ایک اسٹارٹ فریم، اینڈ فریم، اور فریم ریٹ مانگتا ہے۔ پھر یہ گیم کلاک کے ہر ٹک پر ان مستطیل حصوں کے ذریعے ایک پوائنٹر کو آگے بڑھاتا ہے۔ آپ اسپرائٹ شیٹس کو آرٹسٹوں کے تیار کردہ جادوئی اثاثوں کے طور پر دیکھنا چھوڑ دیتے ہیں اور انہیں کوآرڈینیٹ میتھ (coordinate math) کے طور پر دیکھنا شروع کر دیتے ہیں۔ یہ نظریہ بعد میں بہت قیمتی ثابت ہوتا ہے جب آپ حقیقی آرٹ کو کاٹ رہے ہوں یا اس بات کی ڈی بگنگ (debugging) کر رہے ہوں کہ اینیمیشن فریمز کو غلط ترتیب میں کیوں چلا رہی ہے۔

کچھ نہیں سے آواز

آڈیو فائلوں پر پابندی تھی، اس لیے میں نے براہ راست Web Audio API کا استعمال کیا۔ JavaScript کی چند لائنیں ایک آسیلیٹر نوڈ (oscillator node) بنا سکتی ہیں، اسے اسکوائر یا سائن ویو پر سیٹ کر سکتی ہیں، اسے گین نوڈ (gain node) کے ذریعے گزار سکتی ہیں، اور آواز کا ایک مختصر وقفہ شیڈول کر سکتی ہیں۔ میں نے عام واقعات کے لیے چھوٹے ہیلپرز لکھے: قدموں کے لیے ایک کم فریکوئنسی والی بیپ، لوٹ (loot) اٹھانے کے لیے ایک بڑھتی ہوئی چڑپ، اور نقصان پہنچنے پر ایک سخت اسکوائر ویو ٹون۔

یہ سنتھیسائزڈ (synthesized) آوازیں ڈیزائن کے لحاظ سے صرف پلیس ہولڈرز ہیں، لیکن یہ گیم کو فوری طور پر مکینیکل فیڈ بیک دیتی ہیں۔ آپ اصل آڈیو ریکارڈ کرنے یا حاصل کرنے سے پہلے یہ محسوس کر سکتے ہیں کہ ٹائمنگ درست ہے یا نہیں۔ اصل فائدہ آرکیٹیکچر میں ملتا ہے۔ چونکہ آپ نے ساؤنڈ ٹرگر کو ایک سادہ فنکشن میں لپیٹا ہوا ہے، اس لیے بعد میں سنتھیسائزڈ بیپ کو ایک لوڈ شدہ ساؤنڈ بفر سے بدلنے کے لیے صرف ایک لائن کی تبدیلی درکار ہوتی ہے۔ گیم کا باقی حصہ—کولیشن ایونٹ، UI فلیش، اسکور میں اضافہ—غیر تبدیل شدہ رہتا ہے۔ آپ پہلے احساس کا پروٹو ٹائپ بناتے ہیں، پھر معیار (fidelity) کو بہتر بناتے ہیں۔

متعدد دنیاؤں کا انتظام

ایک حقیقی گیم کے لیے ایک سے زیادہ اسکرینز کی ضرورت ہوتی ہے، اس لیے میں نے پروجیکٹ کو الگ الگ Phaser scenes میں تقسیم کر دیا: Menu، Game، UI، اور Pause۔ UI scene، Game scene کے ساتھ متوازی (parallel) چلتی ہے، جسے بیک وقت شروع کیا جاتا ہے تاکہ ہیلتھ بار اور اسکور کاؤنٹر اپنے الگ سانڈ باکس (sandbox) میں رہیں جبکہ نیچے ڈنجن کرال (dungeon crawls) جاری رہے۔ وہ سختی سے ایونٹس (events) کے ذریعے ایک دوسرے سے رابطہ کرتے ہیں۔ جب کھلاڑی کو نقصان پہنچتا ہے، تو Game scene ایک تبدیلی کا ایونٹ (emit) کرتی ہے۔ UI scene اسے سنتی ہے اور اپنے ٹیکسٹ آبجیکٹس کو اپ ڈیٹ کرتی ہے۔ Game scene، UI کو امپورٹ نہیں کرتی، اس کے میتھڈز کو کال نہیں کرتی، اور نہ ہی یہ چیک کرتی ہے کہ وہ موجود ہے یا نہیں۔ یہ محض ڈیٹا کو خلا (void) میں بھیج دیتی ہے۔ اس ڈی کپلنگ (decoupling) کا مطلب یہ ہے کہ آپ کور گیم لوپ (core game loop) کو چھیڑے بغیر ٹیسٹنگ کے لیے HUD کو نکال سکتے ہیں، یا اسے مکمل طور پر تبدیل کر سکتے ہیں۔

پاز (Pause) کرنے کے لیے، میں نے ایک اسٹیکڈ Pause scene کا استعمال کیا جو Game scene کے اوپر رہتی ہے۔ اہم بات یہ ہے کہ Game scene پر scene.pause() کال کرنے سے درحقیقت فزکس ورلڈ (physics world) منجمد ہو جاتا ہے اور ٹائمر رک جاتے ہیں۔ Game scene اپ ڈیٹ ہونا بند کر دیتی ہے، لیکن Pause scene مینو دکھانے اور ان پاز (unpause) سگنل کا انتظار کرنے کے لیے بیدار رہتی ہے۔ اگر آپ نے اب تک صرف ایک بڑے اپ ڈیٹ لوپ کے اندر بولین فلیگ (boolean flag) کے ذریعے پاز اسٹیٹس کو مینیج کیا ہے، تو یہ ایک لائٹ سوئچ دریافت کرنے جیسا محسوس ہوگا۔ انجن آپ کو if (isPaused) return جیسے گارڈز (guards) کے ساتھ اپنے کوڈ کو بھرنے پر مجبور کرنے کے بجائے ایک حقیقی پاز لائف سائیکل (pause lifecycle) فراہم کرتا ہے۔

حقیقی بگز (Bugs) سے حاصل ہونے والے کڑے سبق

اس طرح قریب سے کام کرنے سے مجھے دو ایسی عادات کا پتہ چلا جنہیں تبدیل کرنے کی ضرورت تھی۔

پہلی بات یہ کہ میں نے Phaser v4 میں ایک ایسے میتھڈ کا استعمال کرنے کی کوشش کی جو پبلک (public) لگتا تھا لیکن وہ دستاویزی API کا حصہ نہیں تھا۔ یہ ورژن کے درمیان تبدیل ہو گیا اور میرے بلڈ (build) کو خراب کر دیا۔ میں نے getChildren() استعمال کرنے کے لیے کوڈ کو ری فیکٹر (refactor) کیا، جو کہ ایک مستحکم اور دستاویزی پبلک میتھڈ ہے، اور عدم استحکام ختم ہو گیا۔ سبق بالکل واضح ہے: اگر کوئی میتھڈ آفیشل ڈاکومنٹیشن میں نہیں ہے، تو اس پر اپنا گیم نہ بنائیں۔ انٹرنل APIs کسی وجہ سے ہی انٹرنل ہوتی ہیں۔ پبلک سطح (public surface area) تک محدود رہیں اور آپ کا پروجیکٹ انجن اپ ڈیٹس میں محفوظ رہے گا۔

دوسری بات یہ کہ میں نے سیکھا کہ ہر چیز کے لیے ایونٹس (events) پر کبھی بھروسہ نہ کریں۔ ایونٹ کال بیکس (Event callbacks) کم اہمیت کے تعاملات جیسے کہ سکہ اٹھانے یا صندوق کھولنے کے لیے بالکل ٹھیک کام کرتے ہیں۔ لیکن اہم اسٹیٹ ٹرانزیشنز (state transitions)—خاص طور پر Game Over—کے لیے، میں نے مین اپ ڈیٹ لوپ کے اندر ایک اضافی چیک (redundant check) شامل کیا۔ اگر کوئی لسنر (listener) ہٹا دیا جائے، کوئی سین ایک نامناسب مائیکرو سیکنڈ پر پاز ہو جائے، یا ایونٹ کے بھیجنے اور اسے ہینڈل کرنے کے درمیان کوئی ریس کنڈیشن (race condition) پیدا ہو جائے، تو ایونٹس غلط بھی ہو سکتے ہیں۔ اپ ڈیٹ لوپ میں براہ راست کھلاڑی کی صحت کو چیک کر کے اور اگر وہ صفر ہو جائے تو گیم اوور اسٹیٹ کو نافذ کر کے، میں نے اس بات کو یقینی بنایا کہ اگر کوئی ایونٹ فائر ہونے میں ناکام رہے تو گیم کبھی بھی کسی غیر یقینی حالت (limbo state) میں نہ پھنس جائے۔ ایونٹس اب بھی ثانوی اثرات—اسکرین شیک، ساؤنڈ کیوز، اسکور سبمیشن— کو سنبھالتے ہیں، لیکن مستند منطق (authoritative logic) وہیں رہتی ہے جہاں گیم کلاک (game clock) موجود ہوتی ہے۔

آپ کو یہ کیوں آزمانا چاہیے

اگر آپ گیم ڈویلپمنٹ سیکھ رہے ہیں، تو اپنے اگلے پروجیکٹ پر بالکل یہی پابندی لگائیں: کوئی بیرونی اثاثے (external assets) نہیں، صرف ایک HTML فائل۔ یہ سننے میں محدود لگتا ہے، لیکن یہ ہر عذر کو ختم کر دیتا ہے۔ آپ کو بنڈلر (bundler) کنفیگر کرنے، لوکل آڈیو فائلوں پر CORS ایررز سے لڑنے، یا ایک دوپہر مفت اثاثوں کے پیک (asset packs) ترتیب دینے میں گزارنے کی ضرورت نہیں ہے۔ آپ کوڈ لکھتے ہیں، براؤزر کو ریفریش کرتے ہیں، اور نتائج دیکھتے ہیں۔

اس سے بھی اہم بات یہ ہے کہ آپ سمجھ جائیں گے کہ انجن اسی طرح کیوں کام کرتا ہے۔ آپ کو معلوم ہوگا کہ ٹیکسچر (texture) GPU میں کیسے داخل ہوتا ہے کیونکہ آپ نے generateTexture کال کیا تھا۔ آپ کو معلوم ہوگا کہ اینیمیشن فریمز (animation frames) کو کیسے انڈیکس کیا جاتا ہے کیونکہ آپ نے خود ان کی حدود رجسٹر کی تھیں۔ آپ کو معلوم ہوگا کہ آڈیو اسپیکرز تک کیسے پہنچتی ہے کیونکہ آپ نے آسلیٹر (oscillator) کو وائر کیا تھا۔ یہ علم براہ راست ان بڑے پروجیکٹس میں منتقل ہوتا ہے جو بیرونی اثاثوں کا استعمال کرتے ہیں، کیونکہ بنیادی میکانکس کبھی نہیں بدلتے—انجن