सर्वोत्तम रोगलाईक्स (roguelikes) तुम्हाला फक्त मारत नाहीत. ते तुम्हाला मरण्याची इच्छा निर्माण करतात.

हे ऐकायला विचित्र वाटू शकते, पण ज्याने मध्यरात्री आपला गेमचा 'रन' (run) गमावला आहे आणि १२:०३ ला दुसरा सुरू केला आहे, त्याला ही भावना समजते. मृत्यू वेदनादायक असतो. तुमचे आरोग्य शून्य होते. स्क्रीन अपयशाने भरून जाते. तरीही तुमचे बोट आधीच 'प्ले' बटणावर असते. गेल्या वीस मिनिटांत काहीतरी इतके महत्त्वाचे घडले असते की तुम्ही गेम जिथे संपला तिथे तो सोडून जाऊ शकत नाही.

ही "वन मोर रन" (one more run) लूप आहे, आणि ती योगायोग नाही. ती मुद्दाम डिझाइन केलेली असते. जर तुम्ही एखादा रोगलाईक किंवा 'परमडेथ' (permadeath) असलेला कोणताही गेम बनवत असाल, तर तुमचे संपूर्ण काम दोन विरोधी शक्तींमध्ये संतुलन राखणे हे आहे. खेळाडूला तणाव जाणवेल इतका तो हरला पाहिजे. आणि आशा टिकून राहील इतके त्याच्याकडे शिल्लक राहिले पाहिजे.

अपयशाचे चलन

माझ्या अलीकडील प्रकल्पात, Neon Survivor, मला नेमका तोच तणाव हवा होता. जेव्हा खेळाडू मरतो, तेव्हा रन दरम्यान जमा केलेले सर्व काही गायब होते, फक्त एक गोष्ट वगळता: सोने (gold). ते सोने आपोआप जमा (banked) होते. मेनूवर परतल्यावर, ते कायमस्वरूपी अपग्रेड्ससाठी (permanent upgrades) वापरले जाते. त्यानंतर ते पुन्हा खेळायला सुरुवात करतात, आधीपेक्षा थोडे अधिक शक्तिशाली होऊन.

ही साधी लूप संपूर्ण गेमला पुढे नेते. याशिवाय, मृत्यू म्हणजे पूर्णविराम आहे. खेळाडू गेम सोडून निघून जातो कारण शेवटच्या रनमधून त्याला काहीच मिळाले नाही. या लूपमुळे, मृत्यू म्हणजे एक स्वल्पविराम (comma) ठरतो. तो रन आता एक 'फार्मिंग ट्रिप' (farming trip) बनतो. तो पराभव वेदनादायी असतो, पण तो उद्यासाठी तयारीही करतो.

ही युक्ती साधी आहे. तुम्ही सर्व काही गमावले तरीही काहीतरी तुमच्याकडे शिल्लक राहते. कठीण भाग म्हणजे, तुम्ही जे शिल्लक ठेवता ते आव्हानावर परिणाम न करता महत्त्वाचे ठरेल याची खात्री करणे. जर अपग्रेड्स खूप कमकुवत असतील, तर खेळाडूला त्याची पर्वा उरत नाही. जर ते खूप शक्तिशाली असतील, तर गेम आपोआपच खेळला जातो. दोन्ही परिस्थितीत ही लूप कोलमडते.

दोन घड्याळे, शून्य गोंधळ

हे योग्यरित्या तयार करण्यासाठी, मला दोन वेगळ्या कालरेषा (timelines) व्यवस्थापित कराव्या लागल्या.

'रन क्लॉक' (Run Clock) तुम्ही प्रत्येक वेळी 'प्ले' केल्यावर रिसेट होतो. तो आरोग्य (health), सध्याचा स्कोअर, शत्रूंच्या लाटांची संख्या (enemy wave count) आणि सत्रादरम्यान घेतलेले कोणतेही तात्पुरते पॉवर-अप्स ट्रॅक करतो. जेव्हा पात्र मरते, तेव्हा हे घड्याळ शून्यवर येते.

'मेटा क्लॉक' (Meta Clock) कधीही रिसेट होत नाही. त्यात प्रत्येक प्रयत्नात कमावलेले एकूण सोने, आतापर्यंत गाठलेली सर्वोच्च लाट आणि खरेदी केलेले प्रत्येक कायमस्वरूपी अपग्रेड साठवले जातात. ब्राउझर कितीही वेळा रिफ्रेश केला तरी हे घड्याळ चालूच राहते.

या दोघांची सरमिसळ केल्यास असे बग्स (bugs) निर्माण होतात जे शोधणे कठीण आणि दुरुस्त करणे त्रासदायक असते. मी डेव्हलपर्सना पाहिल आहे जे नियमित सीन रिसेट दरम्यान चुकून खेळाडूची प्रगती पुसून टाकतात, कारण एखादे क्लीनअप फंक्शन चुकीच्या डेटा स्टोअरला स्पर्श करते. मेटा क्लॉकचा डेटा नाहीसा होतो. खेळाडू शून्य सोने आणि शून्य अपग्रेड्ससह परत येतो. त्या क्षणी, तुमच्या आणि तुमच्या खेळाडूच्यातील नाते तुटते. ते नवीन रन सुरू करत नाहीत; ते नवीन वैमनस्य (grudge) सुरू करत असतात.

घड्याळे वेगळी ठेवणे ही केवळ एक शैली नाही. ती एक जगण्याची रणनीती (survival strategy) आहे.

Phaser v4 या विभाजनाचे व्यवस्थापन कसे करते

मी Neon Survivor Phaser v4 मध्ये बनवले आहे, जे या समस्येसाठी दोन विशिष्ट साधने प्रदान करते.

Registry सध्याच्या सत्रासाठी मेमरीमध्ये लाईव्ह डेटा साठवते. ते वेगवान आहे. ते साधे आहे. पण खेळाडूने पेज रिफ्रेश केल्यावर तेही नाहीसे होते.

LocalStorage डेटा ब्राउझरमध्येच सेव्ह करते. टॅब बंद करणे, ब्राउझर रीस्टार्ट करणे आणि वीज जाणे अशा परिस्थितीतही तो टिकून राहतो. ते थोडे संथ आणि कमी विश्वसनीय देखील आहे. स्टोरेज कोटा भरल्यास ब्राउझर ते ब्लॉक करू शकतात किंवा पुसून टाकू शकतात.

माझी डिझाइन निवड कडक होती. गेमप्ले दरम्यान Registry हाच सत्याचा एकमेव स्रोत (source of truth) आहे. गेम त्यातून वाचतो, त्यात लिहितो आणि त्यावर पूर्ण विश्वास ठेवतो. LocalStorage सह-लेखक (co-author) म्हणून काम करत नाही. ते एका आरशाप्रमाणे (mirror) काम करते.

प्रवाह असा काम करतो: गेम अपग्रेड खरेदीची नोंद Registry मध्ये करतो. एक सिंगल मॅनेजर क्लास Registry वर लक्ष ठेवतो. योग्य वेळी, तो मॅनेजर Registry चा डेटा LocalStorage मध्ये मिरर करतो. जर ब्राउझरने लिहिण्यास (write) नकार दिला, तरी गेममध्ये अडथळा येत नाही. जर स्टोरेज फेल झाले, तरी सध्याचे सत्र उत्तम प्रकारे चालते. खेळाडूने अगदी त्याच सेकंदात टॅब बंद केल्यास त्याची प्रगती जाऊ शकते, परंतु सत्र स्वतः कधीही क्रॅश होत नाही.

ही पद्धत एका सूक्ष्म आपत्तीला रोखते. जर तुम्ही प्रत्येक सिस्टमला थेट LocalStorage मध्ये लिहिण्याची परवानगी दिली, तर तुम्ही एका नाजूक API वर अवलंबून राहता. प्रायव्हसी सेटिंग्ज वाढवलेला किंवा कमी स्टोरेज असलेला खेळाडू गेम खेळताना संथ होऊ शकतो किंवा कॉम्बॅट दरम्यान गेम फ्रीझ होऊ शकतो, कारण एखादे बॅकग्राउंड फंक्शन स्टॅट्स सेव्ह करण्याचा प्रयत्न करत असते. Registry ला एकमेव 'सोर्स ऑफ ट्रुथ' बनवून, तुम्ही कृती वेगवान ठेवता आणि जोखीम मर्यादित ठेवता.

कोडला मोकळीक द्या

मी सिस्टम्सना एकमेकांपासून वेगळे (decouple) करण्यासाठी इव्हेंट्सचा (events) देखील वापर केला. जेव्हा एखादा रन संपतो, तेव्हा GameScene स्वतःचा अंत (funeral) हाताळत नाही. ते कोणतेही सेव्ह फंक्शन कॉल करत नाही. ते कोणतेही स्टोरेज युटिलिटी इम्पोर्ट करत नाही. ते फक्त संबंधित डेटासह "run-ended" इव्हेंट उत्सर्जित (emit) करते.

एक स्वतंत्र लिसनर हिशोबाची जबाबदारी सांभाळतो. तो इव्हेंट स्वीकारतो, Meta Clock अपडेट करतो आणि मॅनेजरला नवीन एकूण आकडे LocalStorage मध्ये सेव्ह करण्यास सांगतो.

या विभाजनाचा फायदा लगेचच मिळतो. मी सेव्ह सिस्टमला स्पर्श न करता संपूर्ण GameScene पुन्हा लिहू शकतो, खेळाडूचे पात्र बदलू शकतो, कॅमेरा अँगल बदलू शकतो किंवा अगदी गेमचा प्रकार 'survival' कडून 'bullet hell' कडे वळवू शकतो. ही प्रणाली एकमेकांपासून स्वतंत्र आहे. ती थेट फंक्शन कॉल्सद्वारे नाही, तर इव्हेंट्सद्वारे संवाद साधते. याचा अर्थ असा की कमी मर्ज कॉन्फ्लिक्ट्स, कमी बग्स आणि सहा महिन्यांनंतरही कोडबेस 'स्पॅगेटी' (अस्ताव्यस्त) होणार नाही.

गेम बदलणारे अपग्रेड्स

जर रिवॉर्ड्स एखाद्या स्प्रेडशीटसारखे वाटत असतील, तर तांत्रिक बॅकबोन असण्याचा काहीही उपयोग नाही. अपग्रेड्स खेळताना प्रत्यक्षात कसे वाटतात, यावर मी बराच वेळ खर्च केला.

काही अपग्रेड्स सुरक्षित असतात. अतिरिक्त हालचालीचा वेग (movement speed), बोनस हेल्थ, किंवा फास्टर रीलोड. हे खेळाडूला चुका सुधारण्यासाठी अधिक संधी देतात. ते खेळताना सुरक्षित वाटते. ते गेमचे नियम न बदलता गेम अधिक सोपा करतात.

इतर अपग्रेड्स पूर्णपणे नियम बदलतात. Neon Survivor मध्ये, मी "Piercing Rounds" जोडले. या अपग्रेडपूर्वी, गोळी पहिल्या शत्रूला लागताच थांबत असे. अपग्रेडनंतर, ती शत्रूंमधून आरपार जाते, ज्यामुळे एकाच शॉटमध्ये संपूर्ण रांगा साफ होऊ शकतात.

हा फरक खूप मोठा आहे. वेग आणि हेल्थ तुम्हाला जास्त वेळ टिकवून ठेवू शकतात, पण Piercing Rounds तुमची पोझिशन कशी असावी हे बदलते. तुम्ही शत्रूंना एका रांगेत आणायला सुरुवात करता. तुम्ही कडांवरून फिरण्याऐवजी मध्यभागातून शत्रूंना छेदत पुढे जाता. गेममधील निर्णय घेण्याची व्याप्ती वाढते.

चांगली प्रगती (progression) केवळ आकडेवारी न वाढवता खेळाडूचे निर्णय बदलणारी असावी. जर प्रत्येक अपग्रेड केवळ टक्केवारीतील वाढ असेल, तर खेळाडू त्याचे वर्णन वाचणे थांबवतात. ते क्लिक करतात, अपग्रेड करतात आणि विसरून जातात. जर एखाद्या अपग्रेडमुळे त्यांना त्यांच्या रणनीतीचा पुन्हा विचार करावा लागला, तर ते ते लक्षात ठेवतात. ते त्याबद्दल चर्चा करतात. गेममध्ये अजून काय बदल घडवून आणता येईल हे पाहण्यासाठी ते पुन्हा परत येतात.

खरा फायदा

"एक अजून राऊंड" (one more run) हा लूप केवळ एक प्रणाली नाही. तो तोटा आणि फायदा यांच्यातील एक संबंध आहे, जो क्लीन आर्किटेक्चर आणि अर्थपूर्ण रिवॉर्ड्सवर आधारित आहे.

दोन वेगळ्या टाइमलाईन्स तयार करा आणि Meta Clock चे रक्षण अशा प्रकारे करा जणू काही त्यात तुमच्या खेळाडूंचा विश्वास दडलेला आहे, कारण तो आहेच. लाईव्ह डेटा वेगवान आणि पर्सिस्टंट डेटा सुरक्षित ठेवण्यासाठी तुमच्या इंजिनच्या टूल्सचा वापर करा. तुमच्या सीन्सना (scenes) स्टोरेजपासून वेगळे (decouple) करा जेणेकरून तुम्ही कोणत्याही भीतीशिवाय त्यात सुधारणा करू शकाल. आणि जेव्हा तुम्ही अपग्रेड्स डिझाइन करता, तेव्हा स्वतःला विचारा की ते खेळाडूला अधिक वेळ देतात की अधिक रंजक पर्याय?

हे योग्यरित्या करा, आणि तुमचे खेळाडू मृत्यूला केवळ सहन करणार नाहीत, तर ते त्यावर अवलंबून राहतील. प्रत्येक राऊंड हा पुढच्या राऊंडसाठी एक पाया बनतो. गेम केवळ वारंवार रीस्टार्ट करण्याची मालिका न राहता, एक अखंड चढाई बनतो.