paint-brush
फ़्लटर और फ्लेम के साथ एमवीपी गेम डेवलपमेंटद्वारा@leobit
544 रीडिंग
544 रीडिंग

फ़्लटर और फ्लेम के साथ एमवीपी गेम डेवलपमेंट

द्वारा Leobit28m2024/06/06
Read on Terminal Reader

बहुत लंबा; पढ़ने के लिए

गेमिंग उद्योग में MVP (न्यूनतम व्यवहार्य उत्पाद) तेजी से लोकप्रिय हो रहे हैं। MVP के साथ, आप कम समय में और सीमित बजट पर बुनियादी कार्यक्षमता वाला ऐप बना सकते हैं। फ़्लटर के शीर्ष पर निर्मित एक मज़बूत और ओपन-सोर्स गेम इंजन फ़्लेम की मदद से, आप शानदार 2D गेम बना सकते हैं।
featured image - फ़्लटर और फ्लेम के साथ एमवीपी गेम डेवलपमेंट
Leobit HackerNoon profile picture
0-item

हाल ही में हुए एक सर्वेक्षण के अनुसार, 5 में से केवल 2 स्टार्टअप ही लाभदायक हैं। MVP (न्यूनतम व्यवहार्य उत्पाद) स्टार्टअप की लाभप्रदता की संभावनाओं को काफी हद तक बढ़ा देता है क्योंकि यह ऐसे व्यवसायों को पूर्ण कार्यक्षमता वाले ऐप पर पूरा बजट खर्च किए बिना शुरुआती उपयोगकर्ता प्रतिक्रिया एकत्र करने की अनुमति देता है।


एमवीपी के साथ, आप कम समय में और सीमित बजट पर बुनियादी कार्यक्षमता के साथ एक ऐप बना सकते हैं, उपयोगकर्ता प्रतिक्रिया एकत्र कर सकते हैं, और इस प्रतिक्रिया के अनुसार अपनी विकास टीम के साथ समाधान का विस्तार जारी रख सकते हैं।


गेमिंग उद्योग में एमवीपी तेजी से लोकप्रिय हो रहे हैं। आज, हम फ़्लटर और फ़्लेम के साथ तेज़ गेम एमवीपी विकास के बारे में जानेंगे, जो क्रॉस-प्लेटफ़ॉर्म न्यूनतम-व्यवहार्य उत्पादों के निर्माण के लिए एक शानदार संयोजन है।

फ़्लटर और फ़्लेम क्यों चुनें?

फ़्लटर, क्रॉस-प्लेटफ़ॉर्म डेवलपमेंट के लिए एक फ़ीचर-पैक और सुरक्षित प्लेटफ़ॉर्म है , जिसने मोबाइल ऐप की दुनिया में तूफ़ान मचा दिया है, और इसकी पहुँच UI से कहीं आगे तक फैली हुई है। फ़्लटर के शीर्ष पर निर्मित एक मज़बूत और ओपन-सोर्स गेम इंजन फ़्लेम की मदद से, आप शानदार 2D गेम बना सकते हैं जो Android, iOS, वेब और डेस्कटॉप डिवाइस पर आसानी से चलते हैं।


फ़्लटर अपने अभिन्न विशेषताओं के कारण गेम MVP बनाने के लिए भी एक लोकप्रिय समाधान बन गया है जो विभिन्न उपकरणों में बुनियादी कार्यक्षमता प्रस्तुत करने वाले समाधानों के तेज़ विकास की सुविधा प्रदान करता है। विशेष रूप से, फ़्लटर के विभिन्न लाभ और अभिन्न कार्य निम्नलिखित की अनुमति देते हैं:


  • एंड्रॉइड और iOS सहित विभिन्न प्लेटफ़ॉर्म के लिए साझा कोडबेस वाला उत्पाद बनाएं, जो अलग-अलग प्लेटफ़ॉर्म के लिए अलग-अलग नेटिव ऐप बनाने की तुलना में बहुत तेज़ और किफ़ायती है। समान कोडबेस के साथ फ़्लटर वेब ऐप बनाने के लिए भी कुछ अभ्यास हैं।


  • पूर्व-निर्मित विजेट और डिफ़ॉल्ट एनिमेशन के साथ लचीले उपयोगकर्ता इंटरफेस का निर्माण करें, जो विकास की गति को बढ़ाता है, जो MVP विकास के संदर्भ में सबसे महत्वपूर्ण कारकों में से एक है।


  • फ़्लटर हॉट रीलोड कार्यक्षमता प्रदान करता है जो डेवलपर्स को इन-ऐप कोड में किए गए परिवर्तनों को देखने की अनुमति देता है जो स्क्रीन पर एक साथ दिखाई देते हैं, जिससे MVP विकास में अधिक लचीलापन सुनिश्चित होता है। यह सुविधा पुनरावृत्ति और प्रयोग को बहुत सरल बनाती है, जिससे डेवलपर्स को विभिन्न मैकेनिक्स और विज़ुअल्स को जल्दी से आज़माने की अनुमति मिलती है।


  • न्यूनतम व्यवहार्य उत्पाद के विकास में आमतौर पर न्यूनतम संख्या में संसाधनों की आवश्यकता होती है, और फ़्लटर इस आवश्यकता को पूरी तरह से पूरा करता है, क्योंकि फ़्लटर का फायरबेस के साथ डिफ़ॉल्ट एकीकरण सर्वर-साइड प्रोग्रामिंग की जटिलता को काफी कम कर देता है।


फ़्लटर बहुत अधिक कंप्यूटिंग संसाधनों का उपभोग नहीं करता है और क्रॉस-प्लेटफ़ॉर्म अनुप्रयोगों के सरल सेटअप की सुविधा प्रदान करता है।


फ़्लटर और फ़्लेम संयोजन पर आधारित ऐप MVP एक विश्वसनीय लेकिन अपेक्षाकृत सरल समाधान है जिसे विकसित करना आसान है। यह सीधे मूल कोड में संकलित होता है, जिससे सहज गेमप्ले और प्रतिक्रिया सुनिश्चित होती है। आप अपने गेम MVP को एक बार विकसित कर सकते हैं और इसे विभिन्न प्लेटफ़ॉर्म पर तैनात कर सकते हैं, जिससे समय और संसाधनों की बचत होती है। फ़्लटर और फ़्लेम प्लेटफ़ॉर्म के अंतर को संभालते हैं।


इसके अलावा, दोनों ही तकनीकें व्यापक दस्तावेज़ीकरण, ट्यूटोरियल और कोड उदाहरणों के साथ जीवंत समुदायों का दावा करती हैं। इसका मतलब है कि आपको कभी भी उत्तर या प्रेरणा की कमी नहीं होगी।

तो फिर ज्वाला क्या कर सकती है?

फ्लेम कम समय में और संसाधनों पर अधिक खर्च किए बिना MVP गेम सुविधाएँ बनाने के लिए संपूर्ण टूलसेट प्रदान करता है। यह क्रॉस-प्लेटफ़ॉर्म मॉडलिंग फ़्रेमवर्क विभिन्न उपयोग मामलों की एक विस्तृत श्रृंखला के लिए उपकरण प्रदान करता है:


  • स्प्राइट्स और एनिमेशन: आप अपने स्प्राइट्स को जल्दी से बना सकते हैं या उन्हें विभिन्न ऑनलाइन लाइब्रेरी से इस्तेमाल कर सकते हैं। फ्लेम स्केलेटल एनिमेशन का भी समर्थन करता है, जो आपको अधिक जटिल और यथार्थवादी एनिमेशन बनाने की अनुमति देता है।


  • टकराव का पता लगाना: इसमें एक अंतर्निहित टकराव का पता लगाने वाला सिस्टम है जो आपके अपने भौतिकी के साथ गेम बनाना आसान बनाता है। आप प्लेटफ़ॉर्म, दीवारें, संग्रहणीय वस्तुएँ और अन्य सामान बनाने के लिए टकराव का पता लगाने का उपयोग कर सकते हैं जिनके साथ आपके गेम के पात्र बातचीत कर सकते हैं।


  • भौतिकी सिमुलेशन: फ्लेम भौतिकी सिमुलेशन का भी समर्थन करता है, जो आपको अधिक गतिशील और आकर्षक गेमप्ले मैकेनिक्स बनाने की अनुमति देता है। आप गुरुत्वाकर्षण, कूदना और उछलने जैसी चीजें बनाने के लिए भौतिकी सिमुलेशन का उपयोग कर सकते हैं।


  • ऑडियो और ध्वनि प्रभाव: आप पृष्ठभूमि संगीत, ध्वनि प्रभाव (जैसे हिट, कूद, आदि) और यहां तक कि आवाज अभिनय बनाने के लिए ऑडियो का उपयोग कर सकते हैं।


  • स्टेट मैनेजमेंट: फ्लेम आपके गेम की स्थिति को प्रबंधित करने के लिए कई सुविधाएँ प्रदान करता है। इसमें स्कोरकीपिंग, लेवल मैनेजमेंट और प्लेयर डेटा जैसी चीज़ें शामिल हैं।


  • इनपुट डिवाइस: फ्लेम विभिन्न इनपुट डिवाइस जैसे टच स्क्रीन, कीबोर्ड और गेम कंट्रोलर को सपोर्ट करता है। यह इसे विभिन्न प्लेटफ़ॉर्म के लिए गेम विकसित करने के लिए एक बढ़िया विकल्प बनाता है।


  • पैरालैक्स स्क्रॉलिंग: यह पैरालैक्स स्क्रॉलिंग का समर्थन करता है, जो आपके गेम की दुनिया में गहराई और विसर्जन जोड़ सकता है। पैरालैक्स स्क्रॉलिंग पृष्ठभूमि की विभिन्न परतों को अलग-अलग गति से घुमाकर गहराई का भ्रम पैदा करता है।


  • कण प्रणालियाँ: फ्लेम कण प्रणालियों का भी समर्थन करता है, जिसका उपयोग विभिन्न प्रकार के दृश्य प्रभाव, जैसे विस्फोट, धुआं और बारिश बनाने के लिए किया जा सकता है।


  • मल्टीप्लेयर गेमप्ले: यह खिलाड़ियों को वास्तविक समय में एक-दूसरे के साथ प्रतिस्पर्धा या सहयोग करने की अनुमति देता है।


ऊपर बताई गई ज़्यादातर विशेषताएँ कई खेलों के लिए ज़रूरी हैं और इन्हें MVP विकास चरण में भी नज़रअंदाज़ नहीं किया जाना चाहिए। सबसे महत्वपूर्ण बात यह है कि फ़्लेम ऊपर बताई गई कार्यक्षमता को विकसित करने की गति को काफ़ी हद तक बढ़ा देता है, जिससे आप शुरुआती उत्पाद संस्करणों में भी ऐसी सुविधाएँ जारी कर सकते हैं।

चलो यह कोशिश करते हैं

अब, फ्लेम के बारे में बात करने के बजाय, आइए इस फ्रेमवर्क के साथ अपने खुद के गेम की बुनियादी विशेषताओं वाला एक MVP बनाएं। शुरू करने से पहले, आपके पास फ़्लटर 3.13 या उच्चतर, आपका पसंदीदा IDE और परीक्षण के लिए डिवाइस इंस्टॉल होना चाहिए।

एक आइडिया

यह गेम क्रोम डिनो से प्रेरित है। आह, प्रसिद्ध डिनो रन! यह क्रोम का एक गेम मात्र नहीं है। यह ब्राउज़र के ऑफ़लाइन मोड में छिपा हुआ एक प्यारा ईस्टर एग है।


हमारी परियोजना में निम्नलिखित गेमप्ले होगा:

  • आप जैक नामक एक साहसी व्यक्ति की भूमिका निभाते हैं, जो अंधेरे जंगल में अंतहीन रूप से दौड़ता रहता है।
  • नियंत्रण न्यूनतम हैं: आगे बढ़ने के लिए स्पेसबार को टैप करें या स्क्रीन पर क्लिक करें।
  • खेल धीमी गति से शुरू होता है, लेकिन धीरे-धीरे गति बढ़ाता है, जिससे आप सतर्क बने रहते हैं।
  • आपका लक्ष्य सरल है: बाधाओं से बचें और जितना संभव हो सके उतनी दूर तक दौड़ें, तथा रास्ते में अंक अर्जित करें।


और इसका नाम होगा “फॉरेस्ट रन!”

अपने आप को तैयार करें

एक खाली फ़्लटर प्रोजेक्ट बनाएँ जैसा कि आप हर बार नया ऐप शुरू करते समय करते हैं। शुरू करने के लिए, हमें अपने प्रोजेक्ट के लिए pubspec.yaml में निर्भरताएँ सेट करनी होंगी। इस पोस्ट को लिखते समय, Flame का नवीनतम संस्करण 1.14.0 है। साथ ही, आइए अब सभी एसेट पथों को परिभाषित करें, ताकि बाद में इस फ़ाइल पर वापस जाने की आवश्यकता न पड़े। और छवियों को निर्देशिका assets/images/ में डालें। हमें इसे यहाँ रखना होगा क्योंकि Flame बिल्कुल इसी पथ को स्कैन करेगा:


 environment: sdk: '>=3.2.3 <4.0.0' flutter: '>=3.13.0' dependencies: flutter: sdk: flutter flame: ^1.14.0 flutter: uses-material-design: true assets: - assets/images/ - assets/images/character/ - assets/images/background/ - assets/images/forest/ - assets/images/font/


सभी छवियों को assets/images/ के अंतर्गत रखना याद रखें क्योंकि फ्लेम अन्य निर्देशिकाओं को पार्स नहीं करेगा।


किसी भी गेम के लिए आपको बहुत सारी इमेज की ज़रूरत होगी। लेकिन अगर आप डिज़ाइन में अच्छे नहीं हैं तो क्या होगा? शुक्र है, बहुत सारे ओपन-सोर्स एसेट हैं जिनका इस्तेमाल आप अपने प्रोजेक्ट के लिए कर सकते हैं। इस गेम के लिए एसेट itch.io से लिए गए हैं। हम अपने प्रोजेक्ट के लिए इन संसाधनों का इस्तेमाल करेंगे:



आप उन लिंक पर जा सकते हैं, या इस परियोजना के लिए तैयार परिसंपत्तियों (लिंक टू एसेट्स आर्काइव) को डाउनलोड कर सकते हैं और सभी सामग्री को अपनी परियोजना में कॉपी कर सकते हैं।


फ़्लेम का दर्शन फ़्लटर के समान है। फ़्लटर में, सब कुछ एक विजेट है; फ़्लेम में, सब कुछ एक घटक है, यहाँ तक कि पूरा गेम भी। प्रत्येक घटक 2 विधियों को ओवरराइड कर सकता है: onLoad() और update()। onLoad() को केवल एक बार कॉल किया जाता है जब घटक को ComponentTree में माउंट किया जाता है और प्रत्येक फ़्रेम पर update() को फायर किया जाता है। फ़्लटर में StatefulWidget से initState() और build() के समान।


अब, चलिए कुछ कोड लिखते हैं। एक क्लास बनाएं जो FlameGame को एक्सटेंड करे और हमारे सभी एसेट्स को कैश में लोड करे।


 class ForestRunGame extends FlameGame { @override Future<void> onLoad() async { await super.onLoad(); await images.loadAllImages(); } }


इसके बाद, main.dart में ForestRunGame का उपयोग करें। इसके अलावा, आप डिवाइस ओरिएंटेशन को कॉन्फ़िगर करने के लिए Flame.device से विधियों का उपयोग कर सकते हैं। और GameWidget है, जो विजेट और घटकों के बीच एक पुल के रूप में कार्य करता है।


 Future<void> main() async { WidgetsFlutterBinding.ensureInitialized(); await Flame.device.fullScreen(); await Flame.device.setLandscape(); runApp(GameWidget(game: ForestRunGame())); }


इस बिंदु पर, हम पहले से ही गेम शुरू कर सकते हैं, लेकिन केवल एक काली स्क्रीन होगी। इसलिए, हमें अपने घटकों को जोड़ने की आवश्यकता है।

अंधकारमय जंगल

हम जंगल को दो घटकों में विभाजित करेंगे: पृष्ठभूमि और अग्रभूमि। सबसे पहले, हम पृष्ठभूमि को संभालेंगे। क्या आपने कभी ऐसे पृष्ठ पर स्क्रॉल किया है जो गतिशील लगता है? जैसे कि आप एक ही समय में एक से अधिक दृश्यों को स्क्रॉल कर रहे हों? यह एक लंबन प्रभाव है, और यह तब होता है जब पृष्ठ के विभिन्न तत्व अलग-अलग गति से चलते हैं, जिससे 3D गहराई प्रभाव पैदा होता है।


जैसा कि आप सोच सकते हैं, हम अपनी पृष्ठभूमि के लिए लंबन का उपयोग करेंगे। ParallaxComponent को विस्तारित करें और ParallaxImageData का उपयोग करके छवियों का एक स्टैक सेट करें। इसके अलावा, प्रारंभिक परतों की गति के लिए बेसवेलोसिटी और वेलोसिटीमल्टीप्लायरडेल्टा है, जो परतों के बीच गति में सापेक्ष अंतर के लिए है। और आखिरी बात, प्राथमिकता फ़ील्ड (z-index) को अन्य घटकों के पीछे ले जाने के लिए कॉन्फ़िगर करें।


 class ForestBackground extends ParallaxComponent<ForestRunGame> { @override Future<void> onLoad() async { priority = -10; parallax = await game.loadParallax( [ ParallaxImageData('background/plx-1.png'), ParallaxImageData('background/plx-2.png'), ParallaxImageData('background/plx-3.png'), ParallaxImageData('background/plx-4.png'), ParallaxImageData('background/plx-5.png'), ], baseVelocity: Vector2.zero(), velocityMultiplierDelta: Vector2(1.4, 1.0), ); } }


बैकग्राउंड तैयार हो गया है; अब, फोरग्राउंड जोड़ने का समय है। PositionComponent को आगे बढ़ाएं ताकि हम ग्राउंड को स्क्रीन के निचले हिस्से से जोड़ सकें। गेम कैश तक पहुंचने के लिए हमें HasGameReference मिक्सिन की भी आवश्यकता है।


ग्राउंड बनाने के लिए, आपको बस ग्राउंड ब्लॉक इमेज को कई बार लाइन में रखना होगा। फ्लेम में, इमेज घटकों को स्प्राइट्स कहा जाता है। स्प्राइट एक इमेज का एक क्षेत्र है जिसे कैनवस में रेंडर किया जा सकता है। यह पूरी इमेज का प्रतिनिधित्व कर सकता है या स्प्राइट शीट में शामिल टुकड़ों में से एक हो सकता है।


साथ ही, याद रखें कि X-अक्ष दाईं ओर उन्मुख है और Y-अक्ष नीचे की ओर उन्मुख है। अक्षों का केंद्र स्क्रीन के बाएं शीर्ष कोने में स्थित है।



 class ForestForeground extends PositionComponent with HasGameReference<ForestRunGame> { static final blockSize = Vector2(480, 96); late final Sprite groundBlock; late final Queue<SpriteComponent> ground; @override void onLoad() { super.onLoad(); groundBlock = Sprite(game.images.fromCache('forest/ground.png')); ground = Queue(); } @override void onGameResize(Vector2 size) { super.onGameResize(size); final newBlocks = _generateBlocks(); ground.addAll(newBlocks); addAll(newBlocks); y = size.y - blockSize.y; } List<SpriteComponent> _generateBlocks() { final number = 1 + (game.size.x / blockSize.x).ceil() - ground.length; final lastBlock = ground.lastOrNull; final lastX = lastBlock == null ? 0 : lastBlock.x + lastBlock.width; return List.generate( max(number, 0), (i) => SpriteComponent( sprite: groundBlock, size: blockSize, position: Vector2(lastX + blockSize.x * i, y), priority: -5, ), growable: false, ); } }


और आखिरी बात, इन घटकों को हमारे ForestRunGame में जोड़ें।


 class ForestRunGame extends FlameGame { late final foreground = ForestForeground(); late final background = ForestBackground(); @override Future<void> onLoad() async { await super.onLoad(); await images.loadAllImages(); add(foreground); add(background); } }


अब, गेम शुरू करने का प्रयास करें। इस समय हमारे पास पहले से ही हमारा जंगल है।


जंगल में एक अजनबी

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



जैक दौड़ सकता है, कूद सकता है और निष्क्रिय रह सकता है। उसकी स्थिति को दर्शाने के लिए, हम एक PlayerState एनम जोड़ सकते हैं। फिर एक प्लेयर बनाएं जो SpriteAnimationGroupComponent को एक्सटेंड करता है और PlayerState को जेनेरिक तर्क के रूप में पास करता है। इस घटक में एक एनिमेशन फ़ील्ड है जहाँ हर PlayerState के लिए एनिमेशन संग्रहीत किए जाते हैं और एक वर्तमान फ़ील्ड, जो खिलाड़ी की वर्तमान स्थिति को दर्शाता है, जिसे एनिमेटेड करने की आवश्यकता है।


 enum PlayerState { jumping, running, idle } class Player extends SpriteAnimationGroupComponent<PlayerState> { @override void onLoad() { super.onLoad(); animations = { PlayerState.running: SpriteAnimation.fromFrameData( game.images.fromCache('character/run.png'), SpriteAnimationData.sequenced( amount: 8, amountPerRow: 5, stepTime: 0.1, textureSize: Vector2(23, 34), ), ), PlayerState.idle: SpriteAnimation.fromFrameData( game.images.fromCache('character/idle.png'), SpriteAnimationData.sequenced( amount: 12, amountPerRow: 5, stepTime: 0.1, textureSize: Vector2(21, 35), ), ), PlayerState.jumping: SpriteAnimation.spriteList( [ Sprite(game.images.fromCache('character/jump.png')), Sprite(game.images.fromCache('character/land.png')), ], stepTime: 0.4, loop: false, ), }; current = PlayerState.idle; } }


खिलाड़ी की स्थिति तैयार है। अब, हमें खिलाड़ी को स्क्रीन पर आकार और स्थिति देने की आवश्यकता है। मैं उसका आकार 69x102 पिक्सेल पर सेट करने जा रहा हूँ, लेकिन आप इसे अपनी इच्छानुसार बदल सकते हैं। स्थिति के लिए, हमें ग्राउंड के निर्देशांक पता होने चाहिए। HasGameReference मिक्सिन को जोड़कर, हम अग्रभूमि क्षेत्र तक पहुँच सकते हैं और इसके निर्देशांक प्राप्त कर सकते हैं। अब, onGameResize विधि को ओवरराइड करते हैं, जिसे हर बार एप्लिकेशन का आकार बदलने पर कॉल किया जाता है, और वहाँ जैक की स्थिति सेट करते हैं।


 class Player extends SpriteAnimationGroupComponent<PlayerState> with HasGameReference<ForestRunGame> { static const startXPosition = 80.0; Player() : super(size: Vector2(69, 102)); double get groundYPosition => game.foreground.y - height + 20; // onLoad() {...} with animation setup @override void onGameResize(Vector2 size) { super.onGameResize(size); x = startXPosition; y = groundYPosition; } }


जैसा कि पहले किया गया था, खिलाड़ी को हमारे गेम में जोड़ें।


 class ForestRunGame extends FlameGame { // Earlier written code here... late final player = Player(); @override Future<void> onLoad() async { // Earlier written code here... add(player); } }


यदि आप खेल शुरू करते हैं, तो आप देखेंगे कि जैक पहले से ही जंगल में है!


भागो जैक, भागो!

हमारे गेम में तीन अवस्थाएँ हैं: इंट्रो, प्ले और गेम ओवर। इसलिए, हम इनको दर्शाने वाले enum GameState को जोड़ेंगे। जैक को चलाने के लिए, हमें गति और त्वरण चर की आवश्यकता है। साथ ही, हमें यात्रा की गई दूरी की गणना करने की आवश्यकता है (बाद में उपयोग किया जाएगा)।


जैसा कि पहले बताया गया था, घटक के दो मुख्य तरीके हैं: onLoad() और update()। हम पहले ही कुछ बार onLoad विधि का उपयोग कर चुके हैं। अब, चलिए update() के बारे में बात करते हैं। इस विधि में dt नामक एक पैरामीटर है। यह उस समय को दर्शाता है जो update() को अंतिम बार कॉल किए जाने के बाद से बीता है।


वर्तमान गति और तय की गई दूरी की गणना करने के लिए, हम update() विधि और कुछ बुनियादी किनेमेटिक्स सूत्रों का उपयोग करेंगे:

  • दूरी = गति * समय;
  • गति = त्वरण * समय;


 enum GameState { intro, playing, gameOver } class ForestRunGame extends FlameGame { static const acceleration = 10.0; static const maxSpeed = 2000.0; static const startSpeed = 400.0; GameState state = GameState.intro; double currentSpeed = 0; double traveledDistance = 0; // Earlier written code here... @override void update(double dt) { super.update(dt); if (state == GameState.playing) { traveledDistance += currentSpeed * dt; if (currentSpeed < maxSpeed) { currentSpeed += acceleration * dt; } } } }


दरअसल, हम विकास को सरल बनाने के लिए एक तरकीब का इस्तेमाल करेंगे: जैक स्थिर रहेगा, लेकिन जंगल जैक की ओर बढ़ेगा। इसलिए, हमें खेल की गति लागू करने के लिए अपने जंगल की आवश्यकता है।


पैरालैक्स बैकग्राउंड के लिए, हमें बस गेम की गति को पास करना होगा। और यह बाकी सब अपने आप संभाल लेगा।


 class ForestBackground extends ParallaxComponent<ForestRunGame> { // Earlier written code here... @override void update(double dt) { super.update(dt); parallax?.baseVelocity = Vector2(game.currentSpeed / 10, 0); } }


अग्रभूमि के लिए, हमें हर ग्राउंड ब्लॉक को शिफ्ट करना होगा। साथ ही, हमें यह भी जांचना होगा कि कतार में पहला ब्लॉक स्क्रीन से बाहर चला गया है या नहीं। अगर ऐसा है, तो उसे हटाकर कतार के अंत में रख दें;


 class ForestForeground extends PositionComponent with HasGameReference<ForestRunGame> { // Earlier written code here... @override void update(double dt) { super.update(dt); final shift = game.currentSpeed * dt; for (final block in ground) { block.x -= shift; } final firstBlock = ground.first; if (firstBlock.x <= -firstBlock.width) { firstBlock.x = ground.last.x + ground.last.width; ground.remove(firstBlock); ground.add(firstBlock); } } }


सब कुछ तैयार है, लेकिन एक ट्रिगर है। हम क्लिक पर चलना शुरू करना चाहते हैं। हमारे लक्ष्य मोबाइल और डेस्कटॉप दोनों हैं, इसलिए हम स्क्रीन टैप और कीबोर्ड इवेंट को संभालना चाहते हैं।


सौभाग्य से, फ्लेम के पास ऐसा करने का एक तरीका है। बस अपने इनपुट प्रकार के लिए एक मिक्सिन जोड़ें। कीबोर्ड के लिए, यह स्क्रीन टैपिंग के लिए कीबोर्डइवेंट्स और टैपकॉलबैक है। वे मिक्सिन आपको संबंधित विधियों को ओवरराइड करने और अपना तर्क प्रदान करने की संभावना देते हैं।


यदि उपयोगकर्ता स्पेसबार दबाता है या स्क्रीन पर टैप करता है तो गेम शुरू हो जाना चाहिए।


 class ForestRunGame extends FlameGame with KeyboardEvents, TapCallbacks { // Earlier written code here... @override KeyEventResult onKeyEvent( RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed, ) { if (keysPressed.contains(LogicalKeyboardKey.space)) { start(); } return KeyEventResult.handled; } @override void onTapDown(TapDownEvent event) { start(); } void start() { state = GameState.playing; player.current = PlayerState.running; currentSpeed = startSpeed; traveledDistance = 0; } }


परिणामस्वरूप, जैक अब क्लिक करने के बाद दौड़ सकता है।


अरे नहीं, एक बुश!

अब, हम सड़क पर बाधाएँ रखना चाहते हैं। हमारे मामले में, उन्हें ज़हरीली झाड़ियों के रूप में दर्शाया जाएगा। झाड़ी एनिमेटेड नहीं है, इसलिए हम SpriteComponent का उपयोग कर सकते हैं। साथ ही, हमें इसकी गति तक पहुँचने के लिए गेम संदर्भ की आवश्यकता है। और एक और बात; हम एक-एक करके झाड़ियाँ नहीं उगाना चाहते, क्योंकि इस दृष्टिकोण से ऐसी स्थिति पैदा हो सकती है जब जैक आसानी से झाड़ियों की एक पंक्ति को छलांग लगाकर पार नहीं कर सकता। यह सीमा से एक यादृच्छिक संख्या है, जो वर्तमान गेम गति पर निर्भर करती है।


 class Bush extends SpriteComponent with HasGameReference<ForestRunGame> { late double gap; Bush() : super(size: Vector2(200, 84)); bool get isVisible => x + width > 0; @override Future<void> onLoad() async { x = game.size.x + width; y = -height + 20; gap = _computeRandomGap(); sprite = Sprite(game.images.fromCache('forest/bush.png')); } double _computeRandomGap() { final minGap = width * game.currentSpeed * 100; final maxGap = minGap * 5; return (Random().nextDouble() * (maxGap - minGap + 1)).floor() + minGap; } @override void update(double dt) { super.update(dt); x -= game.currentSpeed * dt; if (!isVisible) { removeFromParent(); } } }


झाड़ियाँ कौन लगा रहा है? बेशक, प्रकृति। आइए हम प्रकृति का निर्माण करें जो हमारी झाड़ियों की पीढ़ी का प्रबंधन करेगी।


 class Nature extends Component with HasGameReference<ForestRunGame> { @override void update(double dt) { super.update(dt); if (game.currentSpeed > 0) { final plant = children.query<Bush>().lastOrNull; if (plant == null || (plant.x + plant.width + plant.gap) < game.size.x) { add(Bush()); } } } }


अब, आइए अपने ForestForeground में Nature को जोड़ें।


 class ForestForeground extends PositionComponent with HasGameReference<ForestRunGame> { // Earlier written code here... late final Nature nature; @override void onLoad() { // Earlier written code here... nature = Nature(); add(nature); }


अब, हमारे जंगल में झाड़ियाँ हैं। लेकिन रुकिए, जैक बस उनके बीच से भाग रहा है। ऐसा क्यों हो रहा है? ऐसा इसलिए है क्योंकि हमने अभी तक मारना लागू नहीं किया है।


यहाँ, हिटबॉक्स हमारी मदद करेगा। हिटबॉक्स फ्लेम के घटकों के चिड़ियाघर में एक और घटक है। यह टकराव का पता लगाने को समाहित करता है और आपको कस्टम लॉजिक के साथ इसे संभालने की संभावना देता है।


जैक के लिए एक जोड़ें। याद रखें कि घटक की स्थिति उसके बाएं-दाएं कोने को रखेगी, केंद्र को नहीं। और आकार के साथ, आप बाकी को संभालते हैं।


 class Player extends SpriteAnimationGroupComponent<PlayerState> with HasGameReference<ForestRunGame> { // Earlier written code here... @override void onLoad() { // Earlier written code here... add( RectangleHitbox( position: Vector2(2, 2), size: Vector2(60, 100), ), ); } }


और एक झाड़ी के लिए। यहाँ, हम कुछ अनुकूलन के लिए टकराव के प्रकार को निष्क्रिय पर सेट करेंगे। डिफ़ॉल्ट रूप से, प्रकार सक्रिय है, जिसका अर्थ है कि फ्लेम जाँच करेगा कि क्या यह हिटबॉक्स हर दूसरे हिटबॉक्स से टकराया है। हमारे पास केवल एक खिलाड़ी और झाड़ियाँ हैं। चूँकि खिलाड़ी के पास पहले से ही एक सक्रिय टकराव का प्रकार है और झाड़ियाँ एक दूसरे से नहीं टकरा सकती हैं, इसलिए हम प्रकार को निष्क्रिय पर सेट कर सकते हैं।


 class Bush extends SpriteComponent with HasGameReference<ForestRunGame> { // Earlier written code here... @override void onLoad() { // Earlier written code here... add( RectangleHitbox( position: Vector2(30, 30), size: Vector2(150, 54), collisionType: CollisionType.passive, ), ); } }


यह बढ़िया है, लेकिन मैं यह नहीं देख पा रहा हूँ कि हिटबॉक्स की स्थिति सही से समायोजित की गई है या नहीं। मैं इसका परीक्षण कैसे कर सकता हूँ?


खैर, आप प्लेयर और बुश के debugMode फ़ील्ड को true पर सेट कर सकते हैं। यह आपको यह देखने की अनुमति देगा कि आपके हिटबॉक्स किस तरह स्थित हैं। बैंगनी घटक के आकार को परिभाषित करता है और पीला हिटबॉक्स को इंगित करता है।


अब, हम यह पता लगाना चाहते हैं कि खिलाड़ी और झाड़ी के बीच टकराव कब होता है। इसके लिए, आपको गेम में HasCollisionDetection मिक्सिन और फिर उन घटकों के लिए CollisionCallbacks जोड़ने की आवश्यकता है, जिन्हें टकराव को संभालने की आवश्यकता है।


 class ForestRunGame extends FlameGame with KeyboardEvents, TapCallbacks, HasCollisionDetection { // Earlier written code here... }


अभी के लिए, जब भी टक्कर का पता चले, तो खेल को रोक दें।


 class Player extends SpriteAnimationGroupComponent<PlayerState> with HasGameReference<ForestRunGame>, CollisionCallbacks { // Earlier written code here... @override void onCollisionStart( Set<Vector2> intersectionPoints, PositionComponent other, ) { super.onCollisionStart(intersectionPoints, other); game.paused = true; } }


कूदो या मरो

अगर जैक उन झाड़ियों से बचना चाहता है, तो उसे कूदना होगा। चलो उसे सिखाते हैं। इस सुविधा के लिए, हमें गुरुत्वाकर्षण स्थिरांक और जैक की छलांग की प्रारंभिक ऊर्ध्वाधर गति की आवश्यकता है। उन मूल्यों को आँख से चुना गया था, इसलिए उन्हें समायोजित करने के लिए स्वतंत्र महसूस करें।


तो, गुरुत्वाकर्षण कैसे काम करता है? मूल रूप से, यह वही त्वरण है लेकिन ज़मीन की ओर उन्मुख है। इसलिए, हम ऊर्ध्वाधर स्थिति और गति के लिए समान सूत्रों का उपयोग कर सकते हैं। इसलिए, हमारी छलांग में 3 चरण होंगे:

  1. जंप शुरू हो जाता है, और जैक की ऊर्ध्वाधर गति शून्य से प्रारंभिक मान में बदल जाती है।
  2. वह ऊपर की ओर बढ़ रहा है और गुरुत्वाकर्षण धीरे-धीरे उसकी गति बदल रहा है। एक पल में, जैक ऊपर की ओर बढ़ना बंद कर देगा और नीचे की ओर बढ़ना शुरू कर देगा।
  3. जब जैक ज़मीन को छूता है, तो हमें उस पर गुरुत्वाकर्षण लागू करना बंद कर देना चाहिए और उसकी स्थिति को पुनः दौड़ने वाली स्थिति में लाना चाहिए।


 class Player extends SpriteAnimationGroupComponent<PlayerState> with HasGameReference<ForestRunGame>, CollisionCallbacks { static const gravity = 1400.0; static const initialJumpVelocity = -700.0; double jumpSpeed = 0; // Earlier written code here... void jump() { if (current != PlayerState.jumping) { current = PlayerState.jumping; jumpSpeed = initialJumpVelocity - (game.currentSpeed / 500); } } void reset() { y = groundYPos; jumpSpeed = 0; current = PlayerState.running; } @override void update(double dt) { super.update(dt); if (current == PlayerState.jumping) { y += jumpSpeed * dt; jumpSpeed += gravity * dt; if (y > groundYPos) { reset(); } } else { y = groundYPos; } } }


और अब ForestRunGame से क्लिक करके जंपिंग को ट्रिगर करते हैं


 class ForestRunGame extends FlameGame with KeyboardEvents, TapCallbacks, HasCollisionDetection { // Earlier written code here... @override KeyEventResult onKeyEvent( RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed, ) { if (keysPressed.contains(LogicalKeyboardKey.space)) { onAction(); } return KeyEventResult.handled; } @override void onTapDown(TapDownEvent event) { onAction(); } void onAction() { switch (state) { case GameState.intro: case GameState.gameOver: start(); break; case GameState.playing: player.jump(); break; } } }


अब, जैक झाड़ियों को संभाल सकता है।

खेल खत्म

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


इस गेम के लिए हमें केवल अंक और कैप्स अक्षरों की आवश्यकता है। तो, चलिए अपना फ़ॉन्ट बनाते हैं। ऐसा करने के लिए, आपको स्रोत छवि और ग्लिफ़ पास करना होगा। ग्लिफ़ क्या है? ग्लिफ़, स्रोत छवि में वर्ण, उसके आकार और स्थिति के बारे में जानकारी का एक संघ है।


 class StoneText extends TextBoxComponent { static const digits = '123456789'; static const letters = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'; StoneText({ required Image source, required super.position, super.text = '', }) : super( textRenderer: SpriteFontRenderer.fromFont( SpriteFont( source: source, size: 32, ascent: 32, glyphs: [ _buildGlyph(char: '0', left: 480, top: 0), for (var i = 0; i < digits.length; i++) _buildGlyph(char: digits[i], left: 32.0 * i, top: 32), for (var i = 0; i < letters.length; i++) _buildGlyph( char: letters[i], left: 32.0 * (i % 16), top: 64.0 + 32 * (i ~/ 16), ), ], ), letterSpacing: 2, ), ); static Glyph _buildGlyph({ required String char, required double left, required double top, }) => Glyph(char, left: left, top: top, height: 32, width: 32); }


अब, हम गेम ओवर पैनल बना सकते हैं और गेम में इसका उपयोग कर सकते हैं।


 class GameOverPanel extends PositionComponent with HasGameReference<ForestRunGame> { bool visible = false; @override Future<void> onLoad() async { final source = game.images.fromCache('font/keypound.png'); add(StoneText(text: 'GAME', source: source, position: Vector2(-144, -16))); add(StoneText(text: 'OVER', source: source, position: Vector2(16, -16))); } @override void renderTree(Canvas canvas) { if (visible) { super.renderTree(canvas); } } @override void onGameResize(Vector2 size) { super.onGameResize(size); x = size.x / 2; y = size.y / 2; } }


अब, हम अपना पैनल दिखा सकते हैं, जब जैक झाड़ी से टकराता है। साथ ही, आइए start() विधि को संशोधित करें, ताकि हम क्लिक करके गेम को पुनः आरंभ कर सकें। साथ ही, हमें जंगल से सभी झाड़ियों को साफ़ करने की आवश्यकता है।


 class ForestRunGame extends FlameGame with KeyboardEvents, TapCallbacks, HasCollisionDetection { // Earlier written code here... late final gameOverPanel = GameOverPanel(); @override Future<void> onLoad() async { // Earlier written code here... add(gameOverPanel); } void gameOver() { paused = true; gameOverPanel.visible = true; state = GameState.gameOver; currentSpeed = 0; } void start() { paused = false; state = GameState.playing; currentSpeed = startSpeed; traveledDistance = 0; player.reset(); foreground.nature.removeAll(foreground.nature.children); gameOverPanel.visible = false; } }


और अब, हमें प्लेयर में collision कॉलबैक को अपडेट करना होगा।


 class Player extends SpriteAnimationGroupComponent<PlayerState> with HasGameReference<ForestRunGame>, CollisionCallbacks { // Earlier written code here... @override void onCollisionStart( Set<Vector2> intersectionPoints, PositionComponent other, ) { super.onCollisionStart(intersectionPoints, other); game.gameOver(); } }


अब, जब जैक झाड़ी से टकराता है तो आप देख सकते हैं कि गेम खत्म हो गया है। और फिर से क्लिक करके गेम को फिर से शुरू करें।


मेरे स्कोर के बारे में क्या?

और अंतिम स्पर्श-स्कोर गणना।


 class ForestRunGame extends FlameGame with KeyboardEvents, TapCallbacks, HasCollisionDetection { late final StoneText scoreText; late final StoneText highText; late final StoneText highScoreText; int _score = 0; int _highScore = 0; // Earlier written code here... @override Future<void> onLoad() async { // Earlier written code here... final font = images.fromCache('font/keypound.png'); scoreText = StoneText(source: font, position: Vector2(20, 20)); highText = StoneText(text: 'HI', source: font, position: Vector2(256, 20)); highScoreText = StoneText( text: '00000', source: font, position: Vector2(332, 20), ); add(scoreText); add(highScoreText); add(highText); setScore(0); } void start() { // Earlier written code here... if (_score > _highScore) { _highScore = _score; highScoreText.text = _highScore.toString().padLeft(5, '0'); } _score = 0; } @override void update(double dt) { super.update(dt); if (state == GameState.playing) { traveledDistance += currentSpeed * dt; setScore(traveledDistance ~/ 50); if (currentSpeed < maxSpeed) { currentSpeed += acceleration * dt; } } } void setScore(int score) { _score = score; scoreText.text = _score.toString().padLeft(5, '0'); } }


कि सभी लोग!


अब, इसे आज़माएँ और मेरे उच्चतम स्कोर को मात देने का प्रयास करें। यह 2537 अंक है!

निष्कर्ष

यह बहुत कुछ था, लेकिन हमने इसे किया। हमने भौतिकी, एनिमेशन, स्कोर गणना और बहुत कुछ के साथ एक मोबाइल गेम के लिए न्यूनतम व्यवहार्य उत्पाद बनाया है। सुधार के लिए हमेशा जगह होती है, और, किसी भी अन्य एमवीपी की तरह, हमारे उत्पाद से भविष्य में नई सुविधाओं, यांत्रिकी और गेम मोड की उम्मीद की जा सकती है।


इसके अलावा, एक फ्लेम_ऑडियो पैकेज भी है, जिसका उपयोग आप कुछ पृष्ठभूमि संगीत, कूदने या मारने की आवाज़ आदि जोड़ने के लिए कर सकते हैं।


अभी तक, हमारा मुख्य उद्देश्य सीमित संसाधनों के आवंटन के साथ कम समय में बुनियादी उत्पाद कार्यक्षमता बनाना था। फ़्लटर और फ़्लेम का संयोजन एक गेम एमवीपी बनाने के लिए एकदम सही साबित हुआ जिसका उपयोग उपयोगकर्ता फ़ीडबैक एकत्र करने और भविष्य में ऐप को अपग्रेड करने के लिए किया जा सकता है।


आप हमारे प्रयास के परिणाम यहां देख सकते हैं।


अपनी शक्तिशाली विशेषताओं, उपयोग में आसानी और संपन्न समुदाय के साथ, फ़्लटर और फ़्लेम महत्वाकांक्षी गेम डेवलपर्स के लिए एक आकर्षक विकल्प है। चाहे आप एक अनुभवी प्रो हों या अभी शुरुआत कर रहे हों, यह संयोजन आपके गेम विचारों को जीवन में लाने के लिए उपकरण और क्षमता प्रदान करता है। तो, अपनी रचनात्मकता को पकड़ो, फ़्लटर और फ़्लेम की दुनिया में गोता लगाओ, और अगले मोबाइल गेमिंग सनसनी का निर्माण शुरू करो!


हमें उम्मीद है कि आपको यह लेख मज़ेदार और जानकारीपूर्ण लगा होगा। यदि आप सॉफ़्टवेयर विकास में अधिक जानकारी चाहते हैं या अपने स्वयं के MVP प्रोजेक्ट पर चर्चा करना चाहते हैं, तो Leobit का पता लगाने या हमारी तकनीकी टीम से संपर्क करने में संकोच न करें!