केस स्टडी - पूरी दुनिया की भूलभुलैया के अंदर

World Wide Maze एक ऐसा गेम है जिसमें अपने स्मार्टफ़ोन का इस्तेमाल करके, वेबसाइटों से बनाई गई 3D भूलभुलैया में किसी रोलिंग बॉल पर नेविगेट किया जा सकता है. ऐसा करके, अपने लक्ष्यों तक पहुंचने की कोशिश की जा सकती है.

वर्ल्ड वाइड मेज़

इस गेम में HTML5 सुविधाओं का भरपूर इस्तेमाल किया जाता है. उदाहरण के लिए, DeviceOrientation इवेंट, स्मार्टफ़ोन का झुका हुआ डेटा हासिल करता है. इसके बाद, इसे WebSocket की मदद से, पीसी पर भेजा जाता है. यहां प्लेयर, WebGL और वेब वर्कर के बनाए गए 3D स्पेस से गुज़रते हैं.

इस लेख में, मैं सटीक रूप से बताऊँगा कि इन सुविधाओं का इस्तेमाल कैसे किया जाता है, इसके इस्तेमाल की पूरी प्रक्रिया क्या है, और ऑप्टिमाइज़ेशन के ज़रूरी बिंदुओं के बारे में बताया जाएगा.

DeviceOrientation

DeviceOrientation इवेंट (उदाहरण) का इस्तेमाल, स्मार्टफ़ोन से झुकाया गया डेटा पाने के लिए किया जाता है. जब DeviceOrientation इवेंट के साथ addEventListener का इस्तेमाल किया जाता है, तो DeviceOrientationEvent ऑब्जेक्ट वाले कॉलबैक को नियमित इंटरवल पर तर्क के तौर पर शुरू किया जाता है. ये इंटरवल, इस्तेमाल किए गए डिवाइस के हिसाब से अलग-अलग होते हैं. उदाहरण के लिए, iOS + Chrome और iOS + Safari में, कॉलबैक को सेकंड के हर 1/20वें हिस्से में शुरू किया जाता है, जबकि Android 4 और Chrome में, इसे सेकंड के हर 1/10वें हिस्से पर शुरू किया जाता है.

window.addEventListener('deviceorientation', function (e) {
  // do something here..
});

DeviceOrientationEvent ऑब्जेक्ट में, X, Y, और Z ऐक्सिस के लिए, डिग्री (रेडियन नहीं) में झुकाव का डेटा है (HTML5Rocks के बारे में ज़्यादा पढ़ें). हालांकि, डिवाइस और ब्राउज़र के कॉम्बिनेशन के हिसाब से भी रिटर्न वैल्यू अलग-अलग होती हैं. असल रिटर्न वैल्यू की सीमाएं नीचे टेबल में दी गई हैं:

डिवाइस की स्क्रीन की दिशा.

सबसे ऊपर नीले रंग से हाइलाइट की गई वैल्यू, वे होती हैं जिन्हें W3C के निर्देशों में बताया गया है. हरे रंग से हाइलाइट की गई वैल्यू, इन स्पेसिफ़िकेशन से मेल खाती हैं, जबकि लाल रंग से हाइलाइट की गई वैल्यू. हैरानी की बात यह है कि सिर्फ़ Android-Firefox कॉम्बिनेशन ही फ़ीचर से मेल खाने वाली वैल्यू दिखाता है. हालांकि, जब बात लागू करने की हो, तो बार-बार होने वाली वैल्यू को शामिल करना ज़्यादा सही होता है. इसलिए, World Wide Maze, iOS के रिटर्न वैल्यू को स्टैंडर्ड के तौर पर इस्तेमाल करता है. साथ ही, इस हिसाब से Android डिवाइसों के हिसाब से उनमें बदलाव करता है.

if android and event.gamma > 180 then event.gamma -= 360

हालांकि, यह अब भी Nexus 10 के साथ काम नहीं करता है. हालांकि, Nexus 10 में वही वैल्यू मिलती है जो अन्य Android डिवाइसों में मिलती है, लेकिन इसमें एक ऐसा गड़बड़ी है जो बीटा और गामा की वैल्यू को रिवर्स कर देती है. इस समस्या को अलग से ठीक किया जा रहा है. (शायद यह डिफ़ॉल्ट ओरिएंटेशन पर सेट है?)

इससे यह भी पता चलता है कि फ़िज़िकल डिवाइसों से जुड़े एपीआई ने भले ही खास जानकारी सेट की हो, लेकिन इस बात की कोई गारंटी नहीं है कि दिखाई जाने वाली वैल्यू उन खास निर्देशों से मेल खाएगी. इसलिए, सभी संभावित डिवाइसों पर इनका टेस्ट करना ज़रूरी है. इसका मतलब यह भी है कि उम्मीद से अलग वैल्यू डाली जा सकती हैं, जिनके लिए समाधान बनाने की ज़रूरत है. World Wide Maze पहली बार खेलने वाले खिलाड़ियों को, अपने ट्यूटोरियल के पहले चरण में अपने डिवाइसों को कैलिब्रेट करने का प्रॉम्प्ट देता है. हालांकि, अगर इसमें गलत झुकाव की वैल्यू मिलती हैं, तो यह ज़ीरो पोज़िशन पर कैलिब्रेट नहीं करेगा. इसलिए, इसकी समयसीमा भी तय की गई है. यह प्लेयर को, कीबोर्ड कंट्रोल पर स्विच करने का प्रॉम्प्ट देती है. ऐसा तब होता है, जब प्लेयर को कीबोर्ड कंट्रोल पर स्विच करने के लिए कहा जाता है.

WebSocket

World Wide Maze में आपके स्मार्टफ़ोन और पीसी को WebSocket के ज़रिए कनेक्ट किया जाता है. ज़्यादा सटीक तरीके से, ये दोनों एक रिले सर्वर के ज़रिए आपस में कनेक्ट होते हैं. जैसे, स्मार्टफ़ोन से सर्वर टू पीसी. ऐसा इसलिए होता है, क्योंकि WebSocket में ब्राउज़र को सीधे एक-दूसरे से कनेक्ट करने की क्षमता नहीं है. (WebRTC डेटा चैनलों का इस्तेमाल करने से पीयर-टू-पीयर कनेक्टिविटी की सुविधा मिलती है और रिले सर्वर की ज़रूरत नहीं पड़ती. हालांकि, लागू करते समय इस तरीके का इस्तेमाल सिर्फ़ Chrome कैनरी और Firefox Nightly के साथ किया जा सकता है).

मैंने Socket.IO (v0.9.11) नाम की लाइब्रेरी का इस्तेमाल करके, प्रोसेस करने का विकल्प चुना. इसमें कनेक्शन टाइम आउट या डिसकनेक्ट होने पर, फिर से कनेक्ट करने की सुविधाएं शामिल हैं. मैंने इसे NodeJS के साथ इस्तेमाल किया, क्योंकि NodeJS और Socket.IO के इस कॉम्बिनेशन ने WebSocket को लागू करने की कई जांचों में, सर्वर साइड की सबसे अच्छी परफ़ॉर्मेंस दिखाई.

संख्याओं के हिसाब से जोड़ा जा रहा है

  1. आपका पीसी सर्वर से कनेक्ट हो जाता है.
  2. सर्वर आपके पीसी के लिए रैंडम तरीके से जनरेट किया गया नंबर देता है. साथ ही, यह नंबर और पीसी के कॉम्बिनेशन को भी याद रखता है.
  3. अपने मोबाइल डिवाइस से, कोई नंबर डालें और सर्वर से कनेक्ट करें.
  4. अगर बताया गया नंबर वही है जो किसी कनेक्ट किए गए पीसी के लिए है, तो आपका मोबाइल डिवाइस उस पीसी से जोड़ दिया जाता है.
  5. अगर कोई पीसी सेट नहीं किया गया है, तो कोई गड़बड़ी होती है.
  6. जब आपके मोबाइल डिवाइस से डेटा आता है, तो वह जिस पीसी से जुड़ा है उस पर भेजा जाता है. इसी तरह, जब मोबाइल डिवाइस से डेटा आता है, तो वह जिस पीसी से जुड़ा है उस पर भेजा जाता है.

इसके बजाय, अपने मोबाइल डिवाइस से भी शुरुआती कनेक्शन बनाया जा सकता है. इस स्थिति में डिवाइस उलट दिए जाते हैं.

टैब सिंक

Chrome के लिए खास तौर पर दी गई टैब सिंक की सुविधा से, दूसरे डिवाइस से जोड़ने की प्रोसेस और भी आसान हो जाती है. इसकी मदद से, पीसी पर खुले हुए पेजों को मोबाइल डिवाइस पर आसानी से खोला जा सकता है. इसी तरह, पीसी पर खुले हुए पेजों को मोबाइल डिवाइस पर आसानी से खोला जा सकता है. कंप्यूटर, सर्वर से जारी किया गया कनेक्शन नंबर लेता है और उसे history.replaceState का इस्तेमाल करके, पेज के यूआरएल में जोड़ देता है.

history.replaceState(null, null, '/maze/' + connectionNumber)

अगर टैब सिंक की सुविधा चालू है, तो यूआरएल कुछ सेकंड के बाद सिंक हो जाता है. इसके बाद, मोबाइल डिवाइस पर उसी पेज को खोला जा सकता है. मोबाइल डिवाइस, खुले हुए पेज के यूआरएल की जांच करता है. अगर कोई नंबर जोड़ा जाता है, तो वह तुरंत कनेक्ट होने लगता है. इससे मैन्युअल तरीके से नंबर डालने या कैमरे से क्यूआर कोड स्कैन करने की ज़रूरत नहीं होगी.

इंतज़ार का समय

रिले सर्वर अमेरिका में है. इसलिए, स्मार्टफ़ोन के टिल्ट डेटा को पीसी तक पहुंचाने से पहले, उसे जापान से ऐक्सेस करने में करीब 200 मि॰से॰ की देरी होगी. जवाब देने में लगने वाला समय, डेवलपमेंट के दौरान इस्तेमाल किए गए स्थानीय वातावरण के मुकाबले कम था. हालांकि, लो-पास फ़िल्टर (मैंने EMA का इस्तेमाल किया) जैसी चीज़ों को शामिल करने से इसमें कोई रुकावट नहीं थी. (व्यावहारिक तौर पर, प्रज़ेंटेशन के लिए लो-पास फ़िल्टर की भी ज़रूरत थी; टिल्ट सेंसर से मिलने वाले रिटर्न वैल्यू में काफ़ी ज़्यादा नॉइज़ शामिल था और उन वैल्यू को स्क्रीन पर लागू किया गया था, जिसकी वजह से बहुत ज़्यादा झटके महसूस हुए थे.) यह जंप के साथ काम नहीं करता था, जो कि साफ़ तौर पर सुस्ती थी, लेकिन इसे हल करने के लिए कुछ नहीं किया जा सका.

मुझे शुरुआत से ही इंतज़ार के समय से जुड़ी समस्याओं की उम्मीद थी. इसलिए, मैंने दुनिया भर में मौजूद रिले सर्वर सेट अप करने के बारे में सोचा, ताकि क्लाइंट सबसे नज़दीकी उपलब्ध से कनेक्ट कर सकें (इस तरह, इंतज़ार का समय कम करके). हालांकि, मैंने Google Compute Engine (जीसीई) का इस्तेमाल किया, जो उस समय सिर्फ़ अमेरिका में हुआ करता था. इसलिए, यह मुमकिन नहीं था.

नागल एल्गोरिदम से जुड़ी समस्या

आम तौर पर, Nagle एल्गोरिदम को ऑपरेटिंग सिस्टम में शामिल किया जाता है, ताकि टीसीपी लेवल पर बफ़रिंग की मदद से बेहतर तरीके से बातचीत की जा सके. हालांकि, इस एल्गोरिदम के चालू रहने के दौरान, मेरे पास रीयल टाइम में डेटा भेजने का विकल्प नहीं था. (खास तौर पर, जब इसे टीसीपी से देरी से स्वीकार करना के साथ जोड़ा जाए. ACK में देरी न होने के बावजूद भी, अगर सर्वर विदेश में होने जैसी वजहों से ACK में कुछ डिग्री की देरी होती है, तो भी वही समस्या आ सकती है.

Android के लिए Chrome में WebSocket के साथ Nagle इंतज़ार के समय की समस्या नहीं हुई, जिसमें Nagle को बंद करने के लिए TCP_NODELAY विकल्प शामिल था. हालांकि, ऐसा iOS के लिए Chrome में इस्तेमाल किए गए WebKit WebSocket में हुआ था, जिसमें यह विकल्प चालू नहीं है. (उसी WebKit का इस्तेमाल करने वाले Safari को भी यह समस्या थी. इस समस्या की शिकायत Google के ज़रिए Apple को की गई थी. साफ़ तौर पर WebKit के डेवलपमेंट वर्शन में इसे ठीक कर दिया गया है.

यह समस्या होने पर, हर 100 मि॰से॰ के बाद भेजे जाने वाले टिल्ट डेटा को अलग-अलग हिस्सों में बांट दिया जाता है. यह डेटा, पीसी पर हर 500 मि॰से॰ में ही भेजा जाता है. गेम इन स्थितियों में काम नहीं कर सकता. इसलिए, यह इंतज़ार के समय से बचाता है. इसके लिए, सर्वर साइड से कम से कम समय के लिए (हर 50 मि.से. या इसके हिसाब से) डेटा भेजता है. मेरा मानना है कि थोड़े-थोड़े अंतराल पर ACK पाने से Nagle एल्गोरिदम को यह भ्रम हो जाता है कि डेटा भेजना सही है.

नागल एल्गोरिदम 1

ऊपर दिए गए ग्राफ़ में, असल में मिले डेटा के इंटरवल दिखाए जाते हैं. यह पैकेट के बीच के समय के अंतराल को दिखाता है. हरा रंग, आउटपुट इंटरवल को दिखाता है और लाल रंग, इनपुट इंटरवल को दिखाता है. कम से कम 54 मि॰से॰, ज़्यादा से ज़्यादा 158 मि॰से॰, और बीच का तापमान 100 मि॰से॰ के करीब होना चाहिए. यहां मैंने एक iPhone का इस्तेमाल किया, जिसमें जापान में मौजूद रिले सर्वर शामिल था. आउटपुट और इनपुट, दोनों की स्पीड करीब 100 मि॰से॰ है और काम करने में कोई रुकावट नहीं है.

नागल एल्गोरिदम 2

इसके उलट, इस ग्राफ़ में अमेरिका में सर्वर का इस्तेमाल करने पर मिले नतीजे दिखाए गए हैं. हरे रंग के आउटपुट इंटरवल 100 मि॰से॰ पर स्थिर रहते हैं. हालांकि, इनपुट इंटरवल में कम से कम 0 मि॰से॰ और सबसे ज़्यादा 500 मि॰से॰ के बीच उतार-चढ़ाव होता है. इससे यह पता चलता है कि पीसी को डेटा कई हिस्सों में मिल रहा है.

ALT_TEXT_HERE

आखिर में, यह ग्राफ़ सर्वर से प्लेसहोल्डर डेटा भेजकर, इंतज़ार के समय से बचने के नतीजे दिखाता है. हालांकि, यह जैपनीज़ सर्वर का इस्तेमाल करने जितना अच्छा परफ़ॉर्म नहीं करता, लेकिन यह साफ़ है कि इनपुट इंटरवल 100 मि॰से॰ के करीब एक ही समय पर स्थिर रहता है.

क्या यह कोई गड़बड़ी है?

Android 4 (ICS) के डिफ़ॉल्ट ब्राउज़र में WebSocket API होने के बावजूद, यह कनेक्ट नहीं हो सकता. इसकी वजह से, Socket.IO Connect_failed इवेंट जनरेट होता है. आंतरिक रूप से इसका टाइम आउट हो जाता है और सर्वर साइड भी कनेक्शन की पुष्टि नहीं कर पाता. (मैंने अकेले WebSocket के साथ इसकी जांच नहीं की है, इसलिए यह Socket.IO की समस्या हो सकती है.)

स्केलिंग रिले सर्वर

रिले सर्वर की भूमिका उतनी जटिल नहीं है. इसलिए, सर्वर को बढ़ाने और उनकी संख्या बढ़ाने का काम तब तक मुश्किल नहीं होना चाहिए, जब तक आप यह पक्का न कर लें कि एक ही पीसी और मोबाइल डिवाइस हमेशा एक ही सर्वर से कनेक्ट हों.

भौतिक विज्ञान

इन-गेम बॉल की मूवमेंट (ढलान पर लुढ़कना, ज़मीन से टकराना, दीवारों से टकराना, सामान इकट्ठा करना वगैरह) पूरी तरह से, 3D फ़िज़िक्स सिम्युलेटर की मदद से की जाती है. मैंने Emscripten की मदद से JavaScript में बड़े पैमाने पर इस्तेमाल किए जाने वाले Bullet फ़िज़िक्स इंजन का पोर्ट, Ammo.js—का इस्तेमाल किया, ताकि इसे "वेब वर्कर" के तौर पर इस्तेमाल करने के लिए Physijs का इस्तेमाल भी किया जा सके.

वेब वर्कर

Web Workers एक एपीआई है, जो JavaScript को अलग-अलग थ्रेड में चलाने के लिए इस्तेमाल किया जाता है. JavaScript को वेब वर्कर के तौर पर लॉन्च किया गया था. यह एक थ्रेड के तौर पर काम करता है और यह उस थ्रेड से अलग होता है जिसने मूल रूप से उसे कॉल किया था. इसलिए, पेज को रिस्पॉन्सिव रखते हुए ज़्यादा काम किए जा सकते हैं. सामान्य तौर पर तेज़ रफ़्तार में इस्तेमाल होने वाले 3D फ़िज़िक्स इंजन को ठीक से चलाने में मदद करने के लिए, Physijs में वेब वर्कर का बेहतर तरीके से इस्तेमाल होता है. World Wide Maze में फ़िज़िक्स इंजन और WebGL इमेज रेंडरिंग को बिलकुल अलग फ़्रेम रेट पर हैंडल किया जाता है. इसलिए, WebGL रेंडरिंग के ज़्यादा लोड होने की वजह से, कम क्षमता वाली मशीन पर फ़्रेम रेट में गिरावट आने के बाद भी, फ़िज़िक्स इंजन अपने-आप ही 60 FPS (फ़्रेम प्रति सेकंड) के लिए कम या ज़्यादा बनाए रखेगा और गेम के कंट्रोल में रुकावट नहीं डालेगा.

FPS (फ़्रेम प्रति सेकंड)

इस इमेज में, Lenovo G570 पर बनने वाले फ़्रेम रेट दिखाए गए हैं. ऊपर वाला बॉक्स, WebGL के लिए फ़्रेम रेट (इमेज रेंडरिंग) दिखाता है और नीचे वाला बॉक्स, फ़िज़िक्स इंजन का फ़्रेम रेट दिखाता है. जीपीयू एक इंटिग्रेटेड Intel HD Graphics 3000 चिप है, इसलिए इमेज रेंडरिंग फ़्रेम रेट, उम्मीद के मुताबिक 60 FPS (फ़्रेम प्रति सेकंड) के हिसाब से नहीं है. हालांकि, फ़िज़िक्स इंजन ने फ़्रेम रेट को सही तरह से हासिल किया था. इसलिए, गेमप्ले का अनुभव हाई-स्पेस वाली मशीन पर किए जाने वाले परफ़ॉर्मेंस से अलग नहीं है.

सक्रिय वेब वर्कर वाले थ्रेड में कंसोल ऑब्जेक्ट नहीं होते हैं, इसलिए डीबगिंग लॉग बनाने के लिए पोस्ट मैसेज के ज़रिए मुख्य थ्रेड पर डेटा भेजा जाना ज़रूरी है. console4Worker का इस्तेमाल करने से, वर्कर में एक कंसोल ऑब्जेक्ट बन जाता है, जिससे डीबग करने की प्रोसेस काफ़ी आसान हो जाती है.

सर्विस वर्कर

Chrome के नए वर्शन से, वेब वर्कर को लॉन्च करते समय ब्रेकपॉइंट सेट किए जा सकते हैं. यह डीबग करने में भी मदद करता है. यह डेवलपर टूल में "कर्मचारी" पैनल में देखा जा सकता है.

परफ़ॉर्मेंस

ज़्यादा पॉलीगॉन वाले स्टेज में कभी-कभी 1,00,000 पॉलीगॉन से ज़्यादा वैल्यू हो जाती हैं. हालांकि, पूरी तरह Physijs.ConcaveMesh (बुलेट में btBvhTriangleMeshShape) के तौर पर जनरेट होने के बाद भी परफ़ॉर्मेंस पर कोई खास असर नहीं पड़ता.

शुरुआत में, टकराने का पता लगाने वाले ऑब्जेक्ट की संख्या बढ़ने की वजह से फ़्रेम रेट में गिरावट आई. हालांकि, Physijs में गैर-ज़रूरी प्रोसेसिंग को खत्म करने से परफ़ॉर्मेंस बेहतर हुई. यह सुधार मूल Physijs के फ़ोर्क पर आधारित है.

भूतों के ऑब्जेक्ट

ऐसे ऑब्जेक्ट जो टकराने का पता लगाते हैं, लेकिन टकराने पर कोई असर नहीं डालते हैं और जिससे अन्य वस्तुओं पर कोई असर नहीं पड़ता है, उन्हें बुलेट में "घोस्ट ऑब्जेक्ट" कहा जाता है. हालांकि, Physijs आधिकारिक तौर पर घोस्ट ऑब्जेक्ट के साथ काम नहीं करता है, लेकिन Physijs.Mesh जनरेट करने के बाद, फ़्लैग के साथ प्रयोग करके उन ऑब्जेक्ट को बनाया जा सकता है. World Wide Maze में मौजूद चीज़ों और गोल पॉइंट का पता लगाने के लिए, घोस्ट ऑब्जेक्ट का इस्तेमाल किया जाता है.

hit = new Physijs.SphereMesh(geometry, material, 0)
hit._physijs.collision_flags = 1 | 4
scene.add(hit)

collision_flags के लिए, 1 CF_STATIC_OBJECT है और 4 CF_NO_CONTACT_RESPONSE है. ज़्यादा जानकारी के लिए, बुलेट फ़ोरम, स्टैक ओवरफ़्लो या बुलेट का दस्तावेज़ खोजें. Physijs, Ammo.js के लिए एक रैपर है और Ammo.js, बुलेट जैसा है. इसलिए, जो चीज़ें बुलेट में की जा सकती हैं वे Physijs में भी कर सकती हैं.

Firefox 18 की समस्या

Firefox के वर्शन 17 से लेकर 18 तक के अपडेट ने वेब वर्कर के डेटा के लेन-देन का तरीका बदल दिया और नतीजे के तौर पर Physijs ने काम करना बंद कर दिया. इस समस्या की शिकायत GitHub पर की गई थी और कुछ दिनों बाद ठीक कर दी गई. हालांकि, इस ओपन सोर्स की क्षमता ने मुझे प्रभावित किया है, लेकिन इस घटना ने मुझे यह भी याद दिला दिया कि कैसे वर्ल्ड वाइड मेज़ में कई अलग-अलग ओपन सोर्स फ़्रेमवर्क शामिल हैं. मैं इस लेख को इस उम्मीद के साथ लिख रहा/रही हूं कि लोगों को कोई सुझाव या राय मिल सके.

asm.js

हालांकि, यह वर्ल्ड वाइड मेज़ पर सीधे तौर पर नहीं है, लेकिन Ammo.js पहले से ही Mozilla के हाल ही में एलान किए गए asm.js के साथ काम करता है (यह कोई आश्चर्य की बात नहीं है कि asm.js को मूल रूप से Emscripten के ज़रिए जनरेट किए गए JavaScript की रफ़्तार बढ़ाने के लिए बनाया गया है और Emscripten का क्रिएटर भी Ammo.js के क्रिएटर ही है). अगर Chrome में भी asm.js की सुविधा काम करती है, तो फ़िज़िक्स के इंजन का कंप्यूटिंग लोड काफ़ी कम हो जाना चाहिए. Firefox Nightly के साथ टेस्ट करने पर, स्पीड काफ़ी तेज़ हो गई थी. ऐसे सेक्शन को लिखना सबसे अच्छा रहेगा जिनके लिए C/C++ में ज़्यादा रफ़्तार की ज़रूरत हो और फिर Emscripten का इस्तेमाल करके उन्हें JavaScript में पोर्ट करना चाहिए?

WebGL

WebGL को लागू करने के लिए, मैंने सबसे ज़्यादा डेवलप की गई लाइब्रेरी, three.js (r53) का इस्तेमाल किया है. हालांकि, डेवलपमेंट के आखिरी चरणों में ही वर्शन 57 रिलीज़ किया जा चुका है, लेकिन एपीआई में बड़े बदलाव किए गए हैं. इसलिए, मुझे रिलीज़ के मूल वर्शन पर काम करना है.

ग्लो इफ़ेक्ट

बॉल के कोर और आइटम में जोड़े गए चमक के इफ़ेक्ट को "कवासे मेथड एमजीएफ़ के आसान वर्शन का इस्तेमाल करके लागू किया जाता है. हालांकि, कावासे तरीके से सभी चमकदार इलाकों में रंग भर जाते हैं, जबकि वर्ल्ड वाइड मेज़ पर रोशनी की ज़रूरत वाले इलाकों के लिए अलग-अलग रेंडर टारगेट बनाए जाते हैं. ऐसा इसलिए है, क्योंकि स्टेज की बनावटों के लिए वेबसाइट का स्क्रीनशॉट इस्तेमाल किया जाना चाहिए. साथ ही, अगर सभी चमकदार हिस्सों को एक्सट्रैक्ट करने से, पूरी वेबसाइट सफ़ेद बैकग्राउंड में चमकने लगती है, तो ऐसा होता है. मैंने हर चीज़ को एचडीआर में प्रोसेस करने के बारे में भी सोचा, लेकिन इस बार मैंने फ़ैसला किया कि उसे लागू करना काफ़ी जटिल होगा.

Glow

सबसे ऊपर बाईं ओर, पहला पास दिखता है. इसमें चमक वाली जगहों को अलग से रेंडर किया जाता है और फिर धुंधला किया जाता है. सबसे नीचे दाईं ओर दूसरा पास दिखता है, जिसमें इमेज का साइज़ 50% कम करने के बाद उसे धुंधला किया गया. सबसे ऊपर दाईं ओर तीसरे पास का डेटा दिखता है, जहां इमेज को फिर से 50% कम किया गया और फिर उसे धुंधला किया गया. इसके बाद, नीचे बाईं ओर दिखाई गई फ़ाइनल कंपोज़िट इमेज को बनाने के लिए इन तीनों को ओवरले किया गया. मैंने VerticalBlurShader और HorizontalBlurShader को धुंधला करने के लिए जो एलिमेंट इस्तेमाल किया है उसके लिए, तीन.js में शामिल है. इसलिए, अभी और ऑप्टिमाइज़ेशन की संभावना है.

रिफ़्लेक्टिव बॉल

बॉल पर परछाई,3.js के सैंपल पर आधारित होती है. सभी निर्देश, बॉल की पोज़िशन से रेंडर किए जाते हैं और पर्यावरण के मैप के तौर पर इस्तेमाल किए जाते हैं. बॉल के हर बार मूव होने पर, एनवायरमेंट मैप को अपडेट करना ज़रूरी होता है. हालांकि, 60 FPS (फ़्रेम प्रति सेकंड) पर अपडेट होने पर, इन मैप को हर तीन फ़्रेम में अपडेट किया जाता है. नतीजा, हर फ़्रेम को अपडेट करने जितना आसान नहीं है, लेकिन यह अंतर असल में तब तक नहीं दिखता है, जब तक इसके बारे में बताया न जाए.

शेडर, शेडर, शेडर...

सभी रेंडरिंग के लिए, WebGL में शेडर (वर्टेक्स शेडर, फ़्रैगमेंट शेडर) की ज़रूरत होती है. हालांकि,3.js में शामिल शेडर पहले से ही कई तरह के इफ़ेक्ट की अनुमति देते हैं. हालांकि, ज़्यादा बारीकी से शेडिंग और ऑप्टिमाइज़ेशन के लिए, खुद का शेड बनाने से बचा नहीं जा सकता. World Wide Maze अपने फ़िज़िक्स इंजन के साथ, सीपीयू को व्यस्त रखता है. इसलिए, मैंने जीपीयू के बजाय, शेडिंग लैंग्वेज (जीएलएसएल) में जितना हो सके उतना लिखने की कोशिश की. भले ही, सीपीयू की प्रोसेसिंग (JavaScript के ज़रिए) करना आसान हो. महासागर की लहरें स्वाभाविक रूप से शेडर पर निर्भर करती हैं, जैसे कि गोल पॉइंट पर होने वाली आतिशबाज़ी और बॉल दिखने पर मेश इफ़ेक्ट का इस्तेमाल किया जाता है.

शेडर बॉल

ये आउटपुट, बॉल दिखने पर इस्तेमाल किए जाने वाले मेश इफ़ेक्ट के टेस्ट से मिले हैं. बाईं ओर वाला लुक, गेम में इस्तेमाल किया जाने वाला है, जो 320 पॉलीगॉन से बना है. बीच वाला एक पॉलीगॉन करीब 5,000 पॉलीगॉन का इस्तेमाल करता है और दाईं ओर वाला पॉलीगॉन करीब 3, 00,000 पॉलीगॉन का इस्तेमाल करता है. कई पॉलीगॉन के साथ भी, शेडर की मदद से प्रोसेस करने पर, 30 FPS (फ़्रेम प्रति सेकंड) की फ़्रेम रेट स्थिर रह सकती है.

शेडर मेश

पूरे स्टेज में बिखरी छोटी-छोटी चीज़ें एक ही मेश में मिल गई हैं और अलग-अलग मूवमेंट के लिए शेड का इस्तेमाल किया जा सकता है, ताकि हर पॉलीगॉन सुझावों को एक जगह से दूसरी जगह ले जाया जा सके. यह इस टेस्ट से पता चलता है कि क्या बड़ी संख्या में ऑब्जेक्ट मौजूद होने से परफ़ॉर्मेंस पर असर पड़ेगा या नहीं. यहां करीब 5,000 ऑब्जेक्ट बनाए हैं, जिनमें करीब 20,000 पॉलीगॉन हैं. परफ़ॉर्मेंस पर कोई असर नहीं पड़ा.

poly2tri

स्टेज बनाने के लिए, सर्वर से मिली आउटलाइन की जानकारी का इस्तेमाल किया जाता है. इसके बाद, इन चरणों को JavaScript से पॉलीगॉन करके बनाया जाता है. इस प्रोसेस का एक अहम हिस्सा, ट्रायएंगलेशन को तीन.js की मदद से ठीक से लागू नहीं किया जाता है और आम तौर पर यह काम नहीं करता है. इसलिए, मैंने खुद ही poly2tri नाम की एक अलग ट्रायएंगल लाइब्रेरी को इंटिग्रेट करने का फ़ैसला किया है. जैसा कि हमें पता चला है कि तीन.js ने पहले भी यही कोशिश की थी, इसलिए मैंने इसका कोई एक हिस्सा इस्तेमाल करके ठीक से काम किया. इससे गड़बड़ियां काफ़ी घट गईं. इससे, गेम खेलने के कई और चरण आ गए. कभी-कभी होने वाली गड़बड़ी बनी रहती है और किसी वजह से poly2tri, अलर्ट जारी करके गड़बड़ियों को ठीक करता है. इसलिए, मैंने इसमें बदलाव किया है, ताकि अपवाद की सूची बनाई जा सके.

poly2tri

ऊपर दिखाया गया है कि नीले रंग की आउटलाइन, त्रिकोणीय और लाल पॉलीगॉन से बनी है.

एनिसोट्रोपिक फ़िल्टरिंग

स्टैंडर्ड आइसोट्रोपिक MIP मैपिंग, हॉरिज़ॉन्टल और वर्टिकल, दोनों ऐक्सिस पर इमेज का साइज़ कम कर देती है. इसलिए, तिरछा कोणों से पॉलीगॉन देखने से, वर्ल्ड वाइड मेज़ के स्टेज के दूर एंड पर बनावट ऐसी दिखती है जो हॉरिज़ॉन्टल तौर पर लंबी, लो रिज़ॉल्यूशन वाली होती है. इस Wikipedia पेज पर सबसे ऊपर दाईं ओर दी गई इमेज, इसका अच्छा उदाहरण है. व्यावहारिक रूप से, ज़्यादा हॉरिज़ॉन्टल रिज़ॉल्यूशन की ज़रूरत होती है, जिसे WebGL (OpenGL) एनिसोट्रोपिक फ़िल्टरिंग नाम की एक विधि का इस्तेमाल करके रिज़ॉल्व करता है. तीन.js में, THREE.Texture.anisotropy के लिए 1 से ज़्यादा मान सेट करने से एनिसोट्रोपिक फ़िल्टरिंग चालू होती है. हालांकि, यह सुविधा एक एक्सटेंशन है और हो सकता है कि यह सभी जीपीयू के साथ काम न करे.

Optimize

जैसा कि WebGL के सबसे सही तरीकों के लेख में भी बताया गया है, WebGL (OpenGL) की परफ़ॉर्मेंस को बेहतर बनाने का सबसे अहम तरीका ड्रॉ कॉल को कम करना है. वर्ल्ड वाइड मेज़ के शुरुआती विकास के दौरान, इन-गेम द्वीप समूह, पुल, और सुरक्षा रेलिंग अलग-अलग चीज़ें थीं. इस वजह से, कभी-कभी 2,000 से ज़्यादा ड्रॉ कॉल भी आते थे, जिससे जटिल स्टेज में परफ़ॉर्मेंस काफ़ी मुश्किल हो जाती थी. हालांकि, एक ही तरह के ऑब्जेक्ट को एक मेश में पैक करने के बाद, ड्रॉ कॉल की संख्या पचास या उसके आस-पास रहने पर, परफ़ॉर्मेंस बेहतर हो जाएगी.

मैंने इसे बेहतर बनाने के लिए, Chrome की ट्रेस करने वाली सुविधा का इस्तेमाल किया है. Chrome के डेवलपर टूल में शामिल प्रोफ़ाइलर, किसी एक तरीके को प्रोसेस करने में लगने वाला कुल समय कुछ हद तक तय कर सकते हैं. हालांकि, ट्रेस करने से, हर हिस्से के प्रोसेस होने में लगने वाला समय, एक सेकंड के 1/1,000वें हिस्से तक सटीक तरीके से पता चल सकता है. ट्रेस करने की सुविधा इस्तेमाल करने का तरीका जानने के लिए, यह लेख पढ़ें.

ऑप्टिमाइज़ेशन

ऊपर दिए गए ट्रेस नतीजे, बॉल के रिफ़्लेक्शन के लिए एनवायरमेंट मैप बनाने से मिले हैं. काम की जगहों पर console.time और console.timeEnd को शामिल करने से, हमें ऐसा दिखने वाला एक ग्राफ़ मिलता है. समय बाईं से दाईं ओर जाता है और हर लेयर, कॉल स्टैक की तरह होती है. console.time में console.time को नेस्ट करने से, ज़्यादा मेज़रमेंट की अनुमति मिलती है. सबसे ऊपर वाला ग्राफ़, ऑप्टिमाइज़ेशन से पहले का है और सबसे नीचे का ग्राफ़, ऑप्टिमाइज़ेशन के बाद है. जैसा कि सबसे ऊपर मौजूद ग्राफ़ में दिखाया गया है, ऑप्टिमाइज़ेशन से पहले हर रेंडर के लिए updateMatrix को 0 से 5 पर कॉल किया गया था. हालांकि, इस शब्द में काट-छांट की गई है. मैंने इसमें बदलाव किया है, ताकि इसे सिर्फ़ एक बार कॉल किया जाए. हालांकि, इस प्रोसेस की ज़रूरत सिर्फ़ तब होती है, जब ऑब्जेक्ट की पोज़िशन या ओरिएंटेशन बदलता है.

ट्रेस करने की प्रोसेस में अपने-आप संसाधनों की ज़रूरत पड़ती है. इसलिए, console.time का बहुत ज़्यादा इस्तेमाल करने से, परफ़ॉर्मेंस पर काफ़ी असर पड़ सकता है. इससे ऑप्टिमाइज़ेशन की ज़रूरत के बारे में पता लगाना मुश्किल हो जाता है.

परफ़ॉर्मेंस अडजस्टर

इंटरनेट की प्रकृति की वजह से, गेम को अलग-अलग चीज़ों वाले सिस्टम पर खेला जा सकता है. फ़रवरी की शुरुआत में रिलीज़ हुई Find Your Way to Oz सीरीज़ में IFLAutomaticPerformanceAdjust क्लास का इस्तेमाल किया गया है. इसकी मदद से, फ़्रेम रेट में उतार-चढ़ाव के हिसाब से इफ़ेक्ट को बेहतर बनाया जा सकता है, ताकि वीडियो बिना किसी रुकावट के चले. World Wide Maze उसी IFLAutomaticPerformanceAdjust क्लास पर बना है और गेमप्ले को आसान बनाने के लिए नीचे दिए गए इफ़ेक्ट को स्केल करता है:

  1. अगर फ़्रेम रेट 45 FPS (फ़्रेम प्रति सेकंड) से कम हो जाता है, तो एनवायरमेंट मैप अपडेट होना बंद हो जाता है.
  2. अगर यह अब भी 40 FPS (फ़्रेम प्रति सेकंड) से कम हो जाता है, तो रेंडरिंग रिज़ॉल्यूशन को 70% (सतह अनुपात का 50%) तक कम कर दिया जाता है.
  3. अगर यह अब भी 40 FPS (फ़्रेम प्रति सेकंड) से कम हो जाता है, तो FXAA (एंटी-एलियासिंग) को हटा दिया जाता है.
  4. अगर यह अब भी 30 FPS (फ़्रेम प्रति सेकंड) से कम हो जाता है, तो चमक के इफ़ेक्ट हट जाते हैं.

मेमोरी लीक

चीज़ों को सही तरीके से हटाना तीन.js के साथ एक परेशानी है. हालांकि, उन्हें अकेला न छोड़ने से साफ़ तौर पर मेमोरी लीक हो सकती है. इसलिए, मैंने इसके लिए नीचे दिया गया तरीका तैयार किया है. @renderer का मतलब THREE.WebGLRenderer से है. (3.js का सबसे नया वर्शन, डीललोकेशन के लिए थोड़े अलग तरीके का इस्तेमाल करता है, इसलिए हो सकता है कि यह इसके साथ काम न करे.)

destructObjects: (object) =>
  switch true
    when object instanceof THREE.Object3D
      @destructObjects(child) for child in object.children
      object.parent?.remove(object)
      object.deallocate()
      object.geometry?.deallocate()
      @renderer.deallocateObject(object)
      object.destruct?(this)

    when object instanceof THREE.Material
      object.deallocate()
      @renderer.deallocateMaterial(object)

    when object instanceof THREE.Texture
      object.deallocate()
      @renderer.deallocateTexture(object)

    when object instanceof THREE.EffectComposer
      @destructObjects(object.copyPass.material)
      object.passes.forEach (pass) =>
        @destructObjects(pass.material) if pass.material
        @renderer.deallocateRenderTarget(pass.renderTarget) if pass.renderTarget
        @renderer.deallocateRenderTarget(pass.renderTarget1) if pass.renderTarget1
        @renderer.deallocateRenderTarget(pass.renderTarget2) if pass.renderTarget2

एचटीएमएल

निजी तौर पर, मेरे हिसाब से WebGL ऐप की सबसे अच्छी बात यह है कि वह एचटीएमएल में पेज लेआउट डिज़ाइन कर सकता है. फ़्लैश या OpenFrameworks (OpenGL) में स्कोर या टेक्स्ट डिस्प्ले जैसे 2D इंटरफ़ेस बनाना काफ़ी मुश्किल काम है. Flash में कम से कम IDE मौजूद है, लेकिन अगर आपको इसकी अच्छी जानकारी नहीं है, तो OpenFrameworks को मुश्किल काम माना जाता है. Cocos2D जैसी चीज़ों का इस्तेमाल करना इसे आसान बना सकता है. दूसरी ओर, एचटीएमएल की मदद से सीएसएस का इस्तेमाल करके, फ़्रंटएंड डिज़ाइन के सभी पहलुओं को ठीक से कंट्रोल किया जा सकता है. यह ठीक वैसा ही है जैसा वेबसाइट बनाते समय किया जाता है. हालांकि, कणों को लोगो में संघनित करने जैसे जटिल इफ़ेक्ट नामुमकिन हैं, लेकिन सीएसएस ट्रांसफ़ॉर्म की क्षमताओं में कुछ 3D इफ़ेक्ट मुमकिन हैं. वर्ल्ड वाइड मेज़ में "लक्ष्य" और "TIME IS UP" वाले टेक्स्ट इफ़ेक्ट को सीएसएस ट्रांज़िशन (Transit के साथ लागू) में स्केल का इस्तेमाल करके ऐनिमेट किया गया है. (हालांकि, बैकग्राउंड में बदलाव करने के लिए, WebGL का इस्तेमाल किया जाता है.)

गेम के हर पेज (टाइटल, RESULT, रैंकिंग वगैरह) की अपनी एचटीएमएल फ़ाइल होती है. टेंप्लेट के तौर पर लोड होने के बाद, $(document.body).append() को सही समय पर सही वैल्यू के साथ कॉल किया जाता है. एक गड़बड़ी यह थी कि जोड़ने से पहले माउस और कीबोर्ड इवेंट सेट नहीं किए जा सके. इसलिए, जोड़ने से पहले el.click (e) -> console.log(e) आज़माने की सुविधा काम नहीं करती.

इंटरनेशनलाइजे़शन (i18n)

अंग्रेज़ी भाषा का वर्शन बनाने के लिए, एचटीएमएल में काम करना भी आसान था. मैंने अपनी अंतरराष्ट्रीय ज़रूरतों के लिए i18next, एक वेब i18n लाइब्रेरी का इस्तेमाल किया. इसे बिना किसी बदलाव के भी इस्तेमाल किया जा सका.

इन-गेम टेक्स्ट में बदलाव करने और उनका अनुवाद करने का काम, Google Docs स्प्रेडशीट में किया गया था. i18next के लिए JSON फ़ाइल की ज़रूरत होती है, इसलिए मैंने स्प्रेडशीट को TSV में एक्सपोर्ट किया और फिर उन्हें कस्टम कन्वर्टर से बदल दिया. मैंने रिलीज़ के ठीक पहले बहुत सारे अपडेट किए हैं, इसलिए Google दस्तावेज़ स्प्रैडशीट से निर्यात प्रक्रिया को स्वचालित करने से चीज़ें बहुत आसान हो जाती हैं.

Chrome की अपने-आप अनुवाद होने की सुविधा भी आम तौर पर काम करती है, क्योंकि पेज एचटीएमएल से बनाए जाते हैं. हालांकि, कभी-कभी यह भाषा को ठीक से नहीं पहचान पाता, बल्कि उसे पूरी तरह से अलग समझ लेता है (जैसे, वियतनामीज़), इसलिए यह सुविधा फ़िलहाल बंद है. (इसे मेटा टैग का इस्तेमाल करके बंद किया जा सकता है.)

RequireJS

मैंने अपने JavaScript मॉड्यूल सिस्टम के तौर पर, RequireJS को चुना है. गेम की 10,000 लाइनों के सोर्स कोड को करीब 60 क्लास (= कॉफ़ी फ़ाइलें) में बांटा जाता है और अलग-अलग js फ़ाइलों में इकट्ठा किया जाता है. REQUIREJS, डिपेंडेंसी के आधार पर इन अलग-अलग फ़ाइलों को सही क्रम में लोड करता है.

define ->
  class Hoge
    hogeMethod: ->

ऊपर बताई गई क्लास (hoge.कॉफ़ी) का इस्तेमाल इस तरह किया जा सकता है:

define ['hoge'], (Hoge) ->
  class Moge
    constructor: ->
      @hoge = new Hoge()
      @hoge.hogeMethod()

काम करने के लिए, hoge.js को moge.js से पहले लोड किया जाना चाहिए. साथ ही, "hoge" को "डेफ़िनिशन" का पहला आर्ग्युमेंट माना जाता है. इसलिए, hoge.js हमेशा पहले लोड होता है (एक बार hoge.js लोड हो जाने पर, इसे वापस कॉल किया जाता है). इस तरीके को AMD कहा जाता है. किसी भी तीसरे पक्ष की लाइब्रेरी का इस्तेमाल एक ही तरह के कॉलबैक के लिए किया जा सकता है, बशर्ते वह AMD के साथ काम करती हो. जो कार्रवाई नहीं करते (उदाहरण के लिए, third.js) तब तक वैसे ही काम करेंगे, जब तक dएंडेंडिस पहले से तय कर दी गई हों.

यह AS3 को इंपोर्ट करने जैसा है, इसलिए यह अजीब नहीं लग सकता. अगर आपके पास ज़्यादा डिपेंडेंट फ़ाइलें हैं, तो यह एक संभावित समाधान है.

r.js

REQUIREDJS में r.js नाम का एक ऑप्टिमाइज़र शामिल होता है. यह सभी निर्भर js फ़ाइलों के साथ मुख्य js को एक में बंडल करता है, फिर UglifyJS (या Close कंपाइलर) का इस्तेमाल करके उसे छोटा करता है. इससे, फ़ाइलों की संख्या और ब्राउज़र को लोड होने वाला कुल डेटा कम हो जाता है. वर्ल्ड वाइड मेज़ के लिए JavaScript फ़ाइल का कुल आकार करीब 2 एमबी है और r.js ऑप्टिमाइज़ेशन के साथ इसे करीब 1 एमबी तक कम किया जा सकता है. अगर gzip की मदद से गेम को डिस्ट्रिब्यूट किया जा सकता था, तो यह साइज़ 250 केबी तक कम हो जाएगा. (GAE में एक समस्या है, जिसकी वजह से एक एमबी या इससे बड़ी gzip फ़ाइलों को ट्रांसमिशन नहीं किया जा सकता. इसलिए, फ़िलहाल गेम को 1 एमबी या इससे ज़्यादा साइज़ के सादे टेक्स्ट में कंप्रेस नहीं किया जा सकता.)

स्टेज बिल्डर

स्टेज डेटा इस तरह से जनरेट किया जाता है और उसे पूरी तरह से अमेरिका में GCE सर्वर पर इस्तेमाल किया जाता है:

  1. स्टेज में बदलने वाली वेबसाइट का यूआरएल, WebSocket के ज़रिए भेजा जाता है.
  2. PhantomJS एक स्क्रीनशॉट लेता है और div और img टैग की स्थितियों को फिर से पा लिया जाता है और JSON फ़ॉर्मैट में आउटपुट मिल जाता है.
  3. दूसरे चरण के स्क्रीनशॉट और एचटीएमएल एलिमेंट की पोज़िशनिंग डेटा के आधार पर, कस्टम C++ (OpenCV, बूस्ट) प्रोग्राम, गै़र-ज़रूरी जगहों को मिटाता है, द्वीपों को जनरेट करता है, टापुओं को ब्रिज से जोड़ता है, गार्ड रेल और आइटम की पोज़िशन को कैलकुलेट करता है, और लक्ष्य पॉइंट को सेट करता है. नतीजे JSON फ़ॉर्मैट में होते हैं और ब्राउज़र पर दिखाए जाते हैं.

PhantomJS

PhantomJS एक ऐसा ब्राउज़र है जिसे स्क्रीन की आवश्यकता नहीं होती. यह मोड, विंडो खोले बिना ही वेब पेज लोड कर सकता है. इसलिए, इसका इस्तेमाल अपने-आप होने वाले टेस्ट में या सर्वर साइड पर स्क्रीनशॉट कैप्चर करने के लिए किया जा सकता है. इसका ब्राउज़र इंजन WebKit है, जो Chrome और Safari में इस्तेमाल होता है. इसलिए, इसका लेआउट और JavaScript चलाने के नतीजे भी, कुछ स्टैंडर्ड ब्राउज़र जितने ही होते हैं वैसे ही होते हैं.

PhantomJS के साथ, JavaScript या CoffeeScript का इस्तेमाल करके उन प्रोसेस को लिखा जाता है जिन्हें आपको एक्ज़ीक्यूट करना है. स्क्रीनशॉट लेना बहुत आसान है, जैसा कि इस सैंपल में दिखाया गया है. मैं Linux सर्वर (CentOS) पर काम कर रहा था. इसलिए, मुझे जैपनीज़ (M+ फ़ॉन्ट) दिखाने के लिए फ़ॉन्ट इंस्टॉल करने होंगे. फिर भी, फ़ॉन्ट रेंडरिंग को Windows या Mac OS की तुलना में अलग तरीके से मैनेज किया जाता है. इसलिए, वही फ़ॉन्ट अन्य मशीनों पर अलग दिख सकता है (हालांकि, अंतर कम होता है).

img और div टैग की जगहें वापस पाने का काम स्टैंडर्ड पेजों की तरह ही किया जाता है. jQuery का इस्तेमाल बिना किसी समस्या के भी किया जा सकता है.

stage_builder

मैंने शुरुआत में स्टेज जनरेट करने के लिए ज़्यादा DOM पर आधारित तरीके का इस्तेमाल करने पर विचार किया (Firefox 3D इंस्पेक्टर की तरह) और PhantomJS में DOM विश्लेषण जैसा कुछ आज़माया. हालांकि, आखिर में मैंने इमेज प्रोसेसिंग के बारे में फ़ैसला लिया. इसके बाद, मैंने एक C++ प्रोग्राम लिखा, जिसमें OpenCV और Boost का इस्तेमाल किया गया है. इस प्रोग्राम का नाम "स्टेज_बिल्डर" है. यह इस तरह से काम करता है:

  1. स्क्रीनशॉट और JSON फ़ाइल(फ़ाइलें) लोड करता है.
  2. इमेज और टेक्स्ट को "आइलैंड" में बदल देता है.
  3. द्वीपों को जोड़ने के लिए पुल बनाता है.
  4. भूलभुलैया बनाने के लिए गैर-ज़रूरी पुलों को हटाता है.
  5. बड़े आइटम सेव करता है.
  6. छोटे आइटम रखता है.
  7. गार्ड रेलिंग लगाएं.
  8. JSON फ़ॉर्मैट में आउटपुट पोज़िशनिंग डेटा.

हर चरण के बारे में नीचे बताया गया है.

स्क्रीनशॉट और JSON फ़ाइल(फ़ाइलें) लोड हो रही है

स्क्रीनशॉट लोड करने के लिए, आम तौर पर इस्तेमाल किए जाने वाले cv::imread का इस्तेमाल किया जाता है. मैंने JSON फ़ाइलों के लिए कई लाइब्रेरी की जांच की, लेकिन ऐसा लगता है कि picojson का इस्तेमाल करना सबसे आसान है.

इमेज और टेक्स्ट को "आइलैंड" में बदलना

स्टेज बिल्ड

ऊपर दिया गया स्क्रीनशॉट aid-dcc.com (असल साइज़ देखने के लिए क्लिक करें) के News सेक्शन का स्क्रीनशॉट है. इमेज और टेक्स्ट एलिमेंट को टापुओं में बदला जाना चाहिए. इन सेक्शन को अलग करने के लिए, हमें व्हाइट बैकग्राउंड के रंग को मिटाना चाहिए. दूसरे शब्दों में कहें, तो स्क्रीनशॉट में सबसे ज़्यादा इस्तेमाल किया जाने वाला रंग बैकग्राउंड है. ऐसा करने के बाद यह कुछ ऐसा दिखता है:

स्टेज बिल्ड

सफ़ेद हिस्से संभावित द्वीपों के बारे में बताते हैं.

टेक्स्ट बहुत पतला और शार्प है. इसलिए, हम cv::dilate, cv::GaussianBlur, और cv::threshold का इस्तेमाल करके इस टेक्स्ट को और गाढ़ा कर देंगे. इमेज का कॉन्टेंट भी मौजूद नहीं है. इसलिए, हम PhantomJS से मिले img टैग डेटा के आउटपुट के आधार पर उन जगहों को सफ़ेद रंग से भर देंगे. नतीजे में मिलने वाली इमेज कुछ इस तरह दिखेगी:

स्टेज बिल्ड

इस टेक्स्ट में अब सही गुच्छे बन गए हैं और हर इमेज एक सही टापू है.

द्वीपों को जोड़ने के लिए पुल बनाना

द्वीपों के तैयार हो जाने के बाद उन्हें पुलों से जोड़ दिया जाता है. हर द्वीप बाएं, दाएं, ऊपर, और नीचे से पास के द्वीपों को खोजता है, फिर एक पुल को सबसे नज़दीकी द्वीप के सबसे करीबी बिंदु से जोड़ता है, जिसके नतीजे कुछ इस तरह हैं:

स्टेज बिल्ड

भूलभुलैया बनाने के लिए गैर-ज़रूरी पुलों को हटाना

सभी पुलों को रखने से स्टेज पर नेविगेट करना बहुत आसान हो जाएगा. इसलिए, भूलभुलैया बनाने के लिए कुछ पुलों को हटाना होगा. शुरुआत की जगह के तौर पर, एक टापू चुना गया है.जैसे, सबसे ऊपर बाईं ओर वाला टापू. इस टापू को जोड़ने वाले सिर्फ़ एक पुल (बिना किसी क्रम के चुने गए) को मिटा दिया जाता है. इसके बाद, बचे हुए पुल से जुड़े अगले द्वीप के लिए भी ऐसा ही किया जाता है. जब रास्ता बंद हो जाता है या वापस उस टापू पर पहुंच जाता है जहां पहले आप जा चुके हैं, तब वह वापस एक ऐसे बिंदु पर पहुंच जाता है जहां से एक नए द्वीप तक पहुंचा जा सकता है. जब सभी टापू इस तरह से प्रोसेस हो जाते हैं, तब भूलभुलैया पूरी हो जाती है.

स्टेज बिल्ड

बड़े आइटम रखने से

डाइमेंशन के हिसाब से हर टापू पर एक या एक से ज़्यादा बड़े आइटम रखे जाते हैं. इनमें टापू के किनारों से दूर की चीज़ों को चुना जाता है. हालांकि यह बहुत साफ़ नहीं है, फिर भी नीचे इन बिंदुओं को लाल रंग में दिखाया गया है:

स्टेज बिल्ड

इन सभी संभावित बिंदुओं में से, सबसे ऊपर बाईं ओर वाले बिंदु को शुरुआती बिंदु (लाल गोला) के रूप में, नीचे दाईं ओर वाले बिंदु को लक्ष्य (हरा गोला) के रूप में सेट किया गया है, और बाकी में से ज़्यादा से ज़्यादा छह को बड़े आइटम प्लेसमेंट (बैंगनी गोला) के लिए चुना गया है.

छोटे आइटम रखना

स्टेज बिल्ड

सही संख्या में छोटे आइटम, टापू के किनारों से तय दूरी पर लाइनों के साथ रखे जाते हैं. ऊपर दी गई इमेज (जो aid-dcc.com से नहीं ली गई है) अनुमानित प्लेसमेंट लाइन को स्लेटी रंग, ऑफ़सेट में और टापू के किनारों से नियमित अंतरालों पर दिखाती है. लाल बिंदु बताते हैं कि छोटे आइटम कहां रखे गए हैं. यह इमेज डेवलपमेंट के बीच के हिस्से की है. इसलिए, आइटम सीधी लाइनों में दिखाए जाते हैं, लेकिन आखिरी वर्शन में आइटम कुछ ज़्यादा अनियमित रूप से स्लेटी लाइनों के दोनों तरफ़ बिखर जाते हैं.

गार्ड रेल लगाना

गार्ड रेलिंग मूल रूप से टापुओं की बाहरी सीमाओं के साथ लगती है, लेकिन इन तक पहुंचने के लिए पुलों पर इन्हें काटना पड़ता है. बूस्ट जियोमेट्री लाइब्रेरी इसके लिए मददगार साबित हुई. इसने ज्यामितीय गणना को आसान बनाया, जैसे कि यह तय करना कि टापू की सीमा का डेटा, पुल के दोनों तरफ़ की रेखाओं को कहां पर काटता है.

स्टेज बिल्ड

द्वीपों को दिखाने वाली हरी रेखाएं, गार्ड रेलिंग हैं. इस चित्र में देखना कठिन हो सकता है, लेकिन पुलों पर कोई हरी रेखाएं नहीं हैं. यह डीबग करने के लिए इस्तेमाल की जाने वाली आखिरी इमेज है. इसमें, वे सभी ऑब्जेक्ट शामिल होते हैं जिनका JSON में आउटपुट होना ज़रूरी है. हल्के नीले बिंदु, छोटे आइटम होते हैं और धूसर बिंदुओं को रीस्टार्ट करने के लिए पॉइंट के तौर पर दिखाया जाता है. जब बॉल समुद्र में गिर जाती है, तो गेम को नज़दीकी रीस्टार्ट पॉइंट से फिर से शुरू कर दिया जाता है. रीस्टार्ट करने की जगहों को छोटे आइटम की तरह ही क्रम से लगाया जाता है. इन्हें टापू के किनारे से एक तय दूरी पर नियमित अंतराल पर व्यवस्थित किया जाता है.

JSON फ़ॉर्मैट में आउटपुट पोज़िशनिंग डेटा

मैंने आउटपुट के लिए भी picojson का इस्तेमाल किया. यह स्टैंडर्ड आउटपुट में डेटा लिखता है, जो इसके बाद कॉलर (Node.js) को मिलता है.

Linux में चलाने के लिए, Mac पर C++ प्रोग्राम बनाना

इस गेम को Mac पर डेवलप किया गया था और इसे Linux में डिप्लॉय किया गया है. हालांकि, दोनों ऑपरेटिंग सिस्टम के लिए OpenCV और Boost मौजूद हैं. इसलिए, कंपाइल एनवायरमेंट शुरू होने के बाद, डेवलपमेंट करना मुश्किल नहीं था. मैंने Mac पर बिल्ड को डीबग करने के लिए, Xcode में Command Line Tools का इस्तेमाल किया. इसके बाद, मैंने Automake/autoconf का इस्तेमाल करके कॉन्फ़िगर करने वाली एक फ़ाइल बनाई, ताकि बिल्ड को Linux में कंपाइल किया जा सके. इसके बाद, मुझे Linux में "कॉन्फ़िगर करें और बनाएं" का इस्तेमाल करके, एक्ज़ीक्यूटेबल फ़ाइल बनाने के लिए कहा गया. कंपाइलर वर्शन में अंतर की वजह से, मुझे Linux के हिसाब से कुछ गड़बड़ियां मिली हैं. हालांकि, मैं जीडीबी का इस्तेमाल करके इन गड़बड़ियों को आसानी से हल कर सका/सकी.

नतीजा

इस तरह का गेम Flash या Unity के साथ बनाया जा सकता है, जिसके कई फ़ायदे होंगे. हालांकि, इस वर्शन को प्लगिन की ज़रूरत नहीं होती और HTML5 + CSS3 की लेआउट सुविधाएं बहुत असरदार साबित होती हैं. हर टास्क के लिए सही टूल का होना बहुत ज़रूरी है. मैं व्यक्तिगत तौर पर हैरान था कि पूरी तरह से HTML5 में बनाए गए गेम के लिए यह बहुत अच्छी तरह काम कर रहा है. हालांकि, कई क्षेत्रों में अभी भी इसकी कमी है, लेकिन मुझे यह देखने का बेसब्री से इंतज़ार है कि आने वाले समय में यह कैसे बेहतर होगा.