Player آرکیٹیکچر
Romergo Player عام DOM پر کیوں چلتا ہے
عام طور پر ویژول ناول کا انجن 3D شوٹر یا ریئل ٹائم اسٹریٹجی گیم کے انجن جتنا پیچیدہ نہیں ہوتا۔ اسے ہر سیکنڈ ایک بڑی دنیا کی فزکس، سیکڑوں آبجیکٹس کے رویے اور پیچیدہ روشنی کا حساب نہیں کرنا پڑتا۔ لیکن اس کا مطلب یہ نہیں کہ کلک پر صرف تصاویر بدل دینا کافی ہے۔
ویژول ناول کا عام منظر کچھ واضح عناصر پر مشتمل ہوتا ہے:
- ایک یا زیادہ پس منظر؛
- کردار اور ان کی جگہیں؛
- مکالمہ، بولنے والے کا نام اور تصویر؛
- بصری اثرات اور transitions؛
- موسیقی اور آوازیں؛
- کوئی انتخاب یا کھلاڑی کی دوسری کارروائی۔
سارا جادو وقت کے ساتھ ان parameters کو بدلنے سے پیدا ہوتا ہے۔ صرف یہ اہم نہیں کہ کیا دکھانا ہے، بلکہ یہ بھی کہ کب دکھانا ہے، کیا بدلے بغیر رکھنا ہے، مناظر کے درمیان موسیقی کیسے جاری رکھنی ہے، کون سی زبان منتخب کرنی ہے اور کھلاڑی کی کارروائی کے بعد کہاں جانا ہے۔
میں نے کافی عرصہ انجن تلاش کیا اور براؤزر منتخب کیا
میں نے Godot، PixiJS اور Three.js پر منظر بنانے کی کوشش کی۔ مختلف درجے کی کامیابی کے ساتھ سب کچھ چلتا تھا، اور خود انجنوں کی کوئی غلطی نہیں تھی۔ لیکن quests بنانے کے میرے طریقے میں وہ ایک اضافی layer شامل کرتے تھے جس کے ساتھ الگ سے ہم آہنگی بنانا پڑتی تھی۔
مجھے صرف تیار گیم اسکرین نہیں چاہیے تھی۔ میرے لیے Builder میں ہر تبدیلی فوراً دیکھنا، پورا گیم build کیے بغیر ایک منظر چلانا، preview میں عناصر کو براہ راست منتخب اور منتقل کرنا، اور پھر شائع شدہ Player میں وہی رویہ حاصل کرنا اہم تھا۔
آخرکار سب سے مؤثر حل سب سے آسان نکلا: براؤزر کا عام DOM۔
پس منظر، کردار اور مکالمہ براؤزر کی مانوس layers ہی رہتے ہیں۔ زیادہ تر composition اور effects CSS سے بنائے جاتے ہیں۔ Canvas 2D الگ بصری عناصر کے لیے استعمال ہوتا ہے، جبکہ WebGL canvas صرف وہاں شامل کیا جاتا ہے جہاں تصاویر کے درمیان shader transitions واقعی ضروری ہوں۔
آواز براؤزر کے HTMLAudioElement کے ذریعے کام کرتی ہے۔ Runtime فعال clips کے لیے audio بناتا ہے، volume اور looping کو کنٹرول کرتا ہے اور اگر asset نہیں بدلا تو transition کے دوران وہی موسیقی جاری رکھتا ہے۔
یہ ہر گیم کے لیے عالمگیر نسخہ نہیں ہے۔ لیکن ویژول ناول کے لیے DOM کوئی سمجھوتا نہیں، بلکہ ایک نہایت درست آلہ ثابت ہوا۔
سادہ منظر اور ذہین runtime
میں ذہنی طور پر Player کو دو حصوں میں تقسیم کرتا ہوں:
- “سادہ” منظر اسے دی گئی چیز دکھاتا اور چلاتا ہے؛
- “ذہین” runtime کہانی کی حالت محفوظ رکھتا اور طے کرتا ہے کہ آگے کیا ہونا چاہیے۔
منظر کو یہ جاننے کی ضرورت نہیں کہ کوئی خاص کردار کیوں آیا، کس شرط نے جواب کھولا یا اگلا chapter کون سا ہے۔ اسے clips، موجودہ وقت، زبان، screen mode اور media references ملتے ہیں۔ پھر وہ نتیجہ دکھاتا ہے اور کھلاڑی کی کارروائیاں واپس بھیجتا ہے۔
Runtime کو quest کی تفصیل اور playthrough state ملتی ہے۔ اسے موجودہ chapter، node اور scene، variables کی قدریں، کیے گئے انتخاب، checkpoints اور مکمل chapters معلوم ہوتے ہیں۔ جب کھلاڑی منظر پر کلک کرتا یا جواب منتخب کرتا ہے تو runtime effects لاگو کرتا، اگلا مرحلہ تلاش کرتا اور منظر کو دوبارہ تیار state دیتا ہے۔
بہت سادہ صورت میں یہ cycle یوں نظر آتا ہے:
Quest JSON + محفوظ پیش رفت
↓
runtime موجودہ مرحلہ طے کرتا ہے
↓
منظر clips اور media دکھاتا ہے
↓
کھلاڑی کی کارروائی runtime میں واپس آتی ہے
↓
اگلا مرحلہ — اور cycle اختتام تک دہرایا جاتا ہے
شائع شدہ گیم میں Player پوری quest کا مقرر runtime snapshot لوڈ کرتا ہے۔ Builder میں preview موجودہ draft سے compatible runtime بناتا ہے۔ اس لیے یہ دو ملتے جلتے players نہیں جو وقت کے ساتھ الگ رویہ اختیار کریں، بلکہ مختلف shells میں ایک مشترک RuntimePlayer، SceneStage اور viewport ہیں۔
مناظر کے درمیان تسلسل کیسے برقرار رہتا ہے
Transition کے دوران runtime نئے منظر کی state دوبارہ حساب کرتا ہے۔ دہرائی جانے والی موسیقی asset ID سے پہچانی جاتی ہے اور نئے آغاز یا دوبارہ fade-in کے بغیر چلتی رہتی ہے۔ تصاویر براؤزر load اور cache کرتا ہے، اس لیے ایک ہی پس منظر کے دوبارہ استعمال کا مطلب network سے file دوبارہ download کرنا نہیں ہوتا۔
یعنی optimization کسی ایک بڑے “کچھ بھی نہ بھیجو” condition میں نہیں، بلکہ درست سطحوں پر ہوتی ہے: runtime playback کا تسلسل برقرار رکھتا ہے، resolver مستحکم resources واپس کرتا ہے اور browser cache پہلے سے معلوم media دوبارہ download نہیں کرتا۔
ایک منظر کے لیے دو اسکرینیں
میرے لیے سب سے مشکل کام rendering نہیں، بلکہ فون کے لیے adaptation تھا۔ Horizontal منظر کو صرف چھوٹا کرنے سے کردار بہت چھوٹے، مکالمہ تنگ اور پس منظر کی اہم تفصیلات آسانی سے کنارے سے باہر ہو جاتی ہیں۔
Romergo میں ایک منظر کے دو مقرر virtual viewport ہیں: horizontal 1280 × 720 اور vertical 390 × 693۔ Player device اور orientation کے مطابق مناسب mode منتخب کرتا ہے، پھر تیار منظر کو مکمل طور پر scale کرتا ہے۔
Desktop layout بنیادی رہتا ہے۔ فون کے لیے کرداروں کی الگ positions اور scale مقرر کی جا سکتی ہے؛ override نہ ہو تو desktop version کو مزید چھوٹا کر کے استعمال کیا جاتا ہے۔ اس طرح مصنف دو آزاد مناظر نہیں، بلکہ مخصوص mobile adjustments کے ساتھ ایک منظر edit کرتا ہے۔
پس منظر کے لیے bgX، bgY اور scale دونوں modes میں مشترک ہیں، جبکہ vertical viewport اسی تصویر کو اپنے طریقے سے crop کرتا ہے۔ اس لیے release سے پہلے دونوں formats میں framing چیک کرنا اور مشترک focus منتخب کرنا ضروری ہے۔ الگ mobile background settings runtime contract کا حصہ نہیں ہیں۔
کرداروں کی ایک جیسی adjustments MCP کے ذریعے AI agent کو دی جا سکتی ہیں اور پھر دونوں viewport کے حقیقی preview میں چیک کی جا سکتی ہیں۔ اسی لیے میں اس adaptation کو مکمل automatic نہیں بلکہ semi-automatic کہتا ہوں۔
کھلاڑی کو سیاہ اسکرین دکھانے سے کیسے بچیں
اگر ضروری تصویر ابھی network سے نہیں پہنچی تو سادہ منظر بھی مدد نہیں کرتا۔ اسی لیے loading بھی runtime contract کا حصہ بن گئی۔
پہلے render سے قبل Player ابتدائی منظر کا active media جمع کرتا ہے اور اس کے load ہونے کا انتظار کرتا ہے۔ اس دوران کھلاڑی خالی پس منظر کے بجائے مناسب progress indicator دیکھتا ہے۔ آغاز کے بعد runtime تھوڑا انتظار کر کے موجودہ منظر اور graph کے اگلے دو مراحل میں قابل رسائی مناظر کے resources پہلے سے تیار کرتا ہے۔ Default depth دو transitions آگے ہے، جس میں دونوں مراحل کی تمام ممکن branches شامل ہیں۔
Preloading transition graph سے منسلک ہے اور اس کی depth الگ سے بدلی جا سکتی ہے۔ یہاں مناظر کی مقرر تعداد استعمال نہیں ہوتی: linear sequence اور branch مختلف load پیدا کرتے ہیں، چاہے رسمی طور پر آگے مراحل کی تعداد برابر ہو۔
Offline کھیلنے کے لیے دوسرا mode ہے: صارف پوری quest پہلے سے download کر سکتا ہے۔ تب runtime snapshot، media اور PWA shell browser cache میں رہتے ہیں اور network کے بغیر کھلتے ہیں۔ اگلے منظر کی preloading ہموار online play دیتی ہے، جبکہ مکمل download حقیقی offline play مہیا کرتا ہے۔
Telegram ایک shell، Discord ایک الگ system
Player کو Telegram اور Discord میں embed کرتے وقت براؤزرز سے میری محبت خاص طور پر کام آئی۔ دونوں صورتوں میں platform کے اندر وہی web runtime کھلتا ہے، اس لیے منظر اور playthrough rules کو نئے game engine کے لیے دوبارہ لکھنے کی ضرورت نہیں پڑی۔
Telegram کے لیے بنیادی طور پر platform session، quest launch، local progress اور Mini App اور عام Player کے درمیان navigation درکار تھے۔ گیم خود وہی رہا۔
Discord زیادہ پیچیدہ ہے کیونکہ کئی لوگوں کو کہانی کا ایک ہی مقام دیکھنا ہوتا ہے۔ ہر participant اپنا local runtime چلاتا ہے، لیکن host حقیقت کا ذریعہ ہوتا ہے۔ Host کا Player تبدیلیوں پر اور تقریباً ہر 750 milliseconds میں state بھیجتا ہے۔ Realtime room WebSocket کے ذریعے command لیتا، snapshot محفوظ کرتا اور participants کو نشر کرتا ہے۔ ناظرین کے runtimes remote state لاگو کرتے، منظر بحال کرتے اور synchronization کے درمیان local وقت جاری رکھتے ہیں۔
اس تقسیم سے مفید نتیجہ ملا: کہانی کی state مشترک ہے، لیکن زبان اور screen size local رہتے ہیں۔ ایک participant فون پر اردو میں منظر دیکھ سکتا ہے، دوسرا بڑی screen پر انگریزی میں، اور دونوں host کے ساتھ sync میں رہتے ہیں۔ ناظرین دوسرے host بنے بغیر جواب کے options پر vote بھی کر سکتے ہیں۔
تمام modes کے لیے ایک بنیاد
Player بہت پہلے “پس منظر، کردار اور مکالمہ” کے بنیادی مجموعے سے آگے بڑھ چکا ہے۔ اب اس میں branches، variables، checkpoints، rewind، interactive interface scenes، translations، offline اور synchronized playthrough شامل ہیں۔
لیکن بنیادی فیصلہ اس ترقی کے ساتھ قائم رہا۔ منظر اب بھی تصویر اور آواز سنبھالتا ہے۔ Runtime اب بھی state اور transitions سنبھالتا ہے۔ Browser API rendering، media، cache اور embedding کو پورا کرتی ہیں، جبکہ خاص layers صرف وہاں شامل ہوتی ہیں جہاں واقعی ضروری ہوں۔
اسی وجہ سے ایک ہی Player کو Builder preview، tests، عام browser play، Telegram اور Discord میں برقرار رکھا جا سکتا ہے۔ میرے لیے یہ ایک اچھی مثال ہے کہ اگر ذمہ داری کی حد درست بنائی جائے تو سب سے آسان اور قدرے کھردرا حل سب سے زیادہ لچکدار ثابت ہو سکتا ہے۔