मैं हमेशा वीडियो गेम बनाना चाहता हूं। मेरा पहला एंड्रॉइड ऐप जिसने मुझे मेरी पहली नौकरी पाने में मदद की, वह एक साधारण गेम था, जिसे एंड्रॉइड व्यूज के साथ बनाया गया था। उसके बाद, गेम इंजन का उपयोग करके अधिक विस्तृत गेम बनाने के कई प्रयास हुए, लेकिन समय की कमी या ढांचे की जटिलता के कारण वे सभी विफल रहे। लेकिन जब मैंने पहली बार फ्लटर पर आधारित फ्लेम इंजन के बारे में सुना, तो मैं इसकी सादगी और क्रॉस-प्लेटफॉर्म सपोर्ट से तुरंत आकर्षित हुआ, इसलिए मैंने इसके साथ एक गेम बनाने की कोशिश करने का फैसला किया। मैं इंजन का अनुभव प्राप्त करने के लिए कुछ सरल, लेकिन अभी भी चुनौतीपूर्ण के साथ शुरू करना चाहता था। लेखों की यह श्रृंखला फ्लेम (और स्पंदन) सीखने और एक बुनियादी प्लेटफ़ॉर्मर गेम बनाने की मेरी यात्रा है। मैं इसे काफी विस्तृत बनाने की कोशिश करूँगा, इसलिए यह किसी के लिए भी उपयोगी होना चाहिए जो सामान्य रूप से फ्लेम या गेम देव में अपने पैर की उंगलियों को डुबो रहा है। 4 लेखों के दौरान, मैं एक 2डी साइड-स्क्रॉलिंग गेम बनाने जा रहा हूं, जिसमें शामिल हैं: एक ऐसा पात्र जो दौड़ सकता है और कूद सकता है एक कैमरा जो खिलाड़ी का अनुसरण करता है स्क्रॉल स्तर का नक्शा, जमीन और प्लेटफार्मों के साथ लंबन पृष्ठभूमि सिक्के जो खिलाड़ी एकत्र कर सकता है और HUD जो सिक्कों की संख्या प्रदर्शित करता है विन स्क्रीन पहले भाग में, हम एक नया फ्लेम प्रोजेक्ट बनाने जा रहे हैं, सभी संपत्तियों को लोड करेंगे, एक खिलाड़ी के चरित्र को जोड़ेंगे और उसे चलाना सिखाएंगे। प्रोजेक्ट सेटअप सबसे पहले, चलिए एक नया प्रोजेक्ट बनाते हैं। आधिकारिक ट्यूटोरियल ऐसा करने के लिए सभी चरणों का वर्णन करने का एक अच्छा काम करता है, इसलिए बस इसका पालन करें। बेयर फ्लेम गेम जोड़ने के लिए एक चीज़: जब आप फ़ाइल सेट अप कर रहे हों, तो आप लाइब्रेरी संस्करणों को नवीनतम उपलब्ध संस्करण में अपडेट कर सकते हैं, या इसे वैसे ही छोड़ सकते हैं, क्योंकि संस्करण से पहले कैरेट चिह्न (^) यह सुनिश्चित करेगा कि आपका ऐप नवीनतम गैर का उपयोग करता है -ब्रेकिंग वर्जन। ( ) pubspec.yaml कैरेट सिंटैक्स यदि आपने सभी चरणों का पालन किया है, तो आपकी फ़ाइल इस तरह दिखनी चाहिए: main.dart import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; void main() { final game = FlameGame(); runApp(GameWidget(game: game)); } संपत्ति इससे पहले कि हम जारी रखें, हमें ऐसे संसाधन तैयार करने होंगे जिनका उपयोग खेल के लिए किया जाएगा। एसेट्स चित्र, एनिमेशन, ध्वनियाँ आदि हैं। इस श्रृंखला के प्रयोजनों के लिए, हम केवल उन छवियों का उपयोग करेंगे जिन्हें गेम देव में स्प्राइट्स भी कहा जाता है। प्लैटफ़ॉर्मर स्तर बनाने का सबसे आसान तरीका टाइल मैप और टाइल स्प्राइट का उपयोग करना है। इसका मतलब है कि स्तर मूल रूप से एक ग्रिड है, जहां प्रत्येक सेल इंगित करता है कि यह किस वस्तु / जमीन / मंच का प्रतिनिधित्व करता है। बाद में, जब खेल चल रहा होता है, तो प्रत्येक सेल की जानकारी को संबंधित टाइल स्प्राइट में मैप किया जाता है। इस तकनीक का उपयोग करके बनाए गए गेम ग्राफ़िक्स वास्तव में विस्तृत या बहुत सरल हो सकते हैं। उदाहरण के लिए, सुपर मारियो ब्रदर्स में, आप देखते हैं कि बहुत सारे तत्व दोहराए जा रहे हैं। ऐसा इसलिए, क्योंकि गेम ग्रिड में प्रत्येक ग्राउंड टाइल के लिए, केवल एक ग्राउंड इमेज होती है जो इसका प्रतिनिधित्व करती है। हम उसी दृष्टिकोण का पालन करेंगे, और हमारे पास मौजूद प्रत्येक स्थिर वस्तु के लिए एक छवि तैयार करेंगे। हम कुछ वस्तुओं को भी चाहते हैं, जैसे कि खिलाड़ी का चरित्र और सिक्के एनिमेटेड हों। एनीमेशन आमतौर पर स्थिर छवियों की एक श्रृंखला के रूप में संग्रहीत होता है, प्रत्येक एक फ्रेम का प्रतिनिधित्व करता है। जब एनीमेशन चल रहा होता है, तो फ्रेम एक के बाद एक चलते हैं, जिससे वस्तु के हिलने का भ्रम पैदा होता है। अब सबसे अहम सवाल यह है कि संपत्ति कहां से लाएं। बेशक, आप उन्हें स्वयं बना सकते हैं, या उन्हें किसी कलाकार को सौंप सकते हैं। साथ ही, बहुत सारे भयानक कलाकार हैं जिन्होंने ओपन-सोर्स में गेम एसेट्स का योगदान दिया है। मैं द्वारा उपयोग करूंगा। GrafxKid आर्केड प्लेटफॉर्मर एसेट्स पैक का आम तौर पर, इमेज एसेट दो रूपों में आते हैं: स्प्राइट शीट और सिंगल स्प्राइट। पूर्व एक बड़ी छवि है, जिसमें सभी खेल संपत्तियां एक में हैं। फिर गेम डेवलपर आवश्यक स्प्राइट की सटीक स्थिति निर्दिष्ट करते हैं, और गेम इंजन इसे शीट से काट देता है। इस गेम के लिए, मैं सिंगल स्प्राइट्स का उपयोग करूंगा (एनिमेशन को छोड़कर, उन्हें एक छवि के रूप में रखना आसान है) क्योंकि मुझे स्प्राइट शीट में प्रदान की गई सभी संपत्तियों की आवश्यकता नहीं है। चाहे आप स्वयं स्प्राइट्स बना रहे हों या उन्हें किसी कलाकार से प्राप्त कर रहे हों, आपको गेम इंजन के लिए उन्हें अधिक उपयुक्त बनाने के लिए उन्हें स्लाइस करने की आवश्यकता हो सकती है। आप उस उद्देश्य के लिए विशेष रूप से बनाए गए टूल (जैसे या किसी ग्राफिकल एडिटर का उपयोग कर सकते हैं। मैंने Adobe Photoshop का उपयोग किया, क्योंकि इस स्प्राइट शीट में, स्प्राइट्स के बीच असमान स्थान होता है, जिससे छवियों को निकालने के लिए स्वचालित टूल के लिए यह कठिन हो जाता है, इसलिए मुझे इसे मैन्युअल रूप से करना पड़ा। टेक्सचर पैकर) आप संपत्ति का आकार भी बढ़ाना चाह सकते हैं, लेकिन यदि यह सदिश छवि नहीं है, तो परिणामी स्प्राइट धुंधली हो सकती है। एक वर्कअराउंड मैंने पाया कि पिक्सेल कला के लिए बहुत अच्छा काम करता है, फ़ोटोशॉप में आकार बदलने की विधि का उपयोग करना है (या Gimp में कोई भी इंटरपोलेशन सेट नहीं है)। लेकिन यदि आपका एसेट अधिक विस्तृत है, तो यह संभवत: काम नहीं करेगा। Nearest Neighbour (hard edges) रास्ते से बाहर स्पष्टीकरण के साथ, डाउनलोड करें या अपनी खुद की तैयार करें और उन्हें अपनी परियोजना के फ़ोल्डर में जोड़ें। मेरे द्वारा तैयार की गई संपत्ति को assets/images जब भी आप नई संपत्तियां जोड़ते हैं, तो आपको उन्हें फ़ाइल में इस प्रकार पंजीकृत करना होगा: pubspec.yaml flutter: assets: - assets/images/ और भविष्य के लिए युक्ति: यदि आप पहले से पंजीकृत संपत्तियों को अपडेट कर रहे हैं तो आपको परिवर्तनों को देखने के लिए गेम को पुनरारंभ करना होगा। अब आइए वास्तव में एसेट्स को गेम में लोड करें। मुझे सभी संपत्तियों के नाम एक ही स्थान पर रखना पसंद है, जो एक छोटे से खेल के लिए बहुत अच्छा काम करता है, क्योंकि हर चीज पर नज़र रखना और ज़रूरत पड़ने पर संशोधित करना आसान है। तो, चलिए डायरेक्टरी में एक नई फाइल बनाते हैं: lib assets.dart const String THE_BOY = "theboy.png"; const String GROUND = "ground.png"; const String PLATFORM = "platform.png"; const String MIST = "mist.png"; const String CLOUDS = "clouds.png"; const String HILLS = "hills.png"; const String COIN = "coin.png"; const String HUD = "hud.png"; const List<String> SPRITES = [THE_BOY, GROUND, PLATFORM, MIST, CLOUDS, HILLS, COIN, HUD]; और फिर एक और फाइल बनाएं, जिसमें भविष्य में सभी गेम लॉजिक होंगे: game.dart import 'package:flame/game.dart'; import 'assets.dart' as Assets; class PlatformerGame extends FlameGame { @override Future<void> onLoad() async { await images.loadAll(Assets.SPRITES); } } मुख्य वर्ग है जो हमारे खेल का प्रतिनिधित्व करता है, यह विस्तार करता है, जो कि Flame इंजन में उपयोग की जाने वाली बेस गेम क्लास है। जो बदले में - फ्लेम के बेसिक बिल्डिंग ब्लॉक का विस्तार करता है। छवियों, इंटरफेस या प्रभावों सहित आपके गेम में सब कुछ घटक हैं। प्रत्येक में एक async विधि होती है, जिसे घटक आरंभीकरण पर कहा जाता है। आमतौर पर, सभी घटक सेटअप लॉजिक वहां जाते हैं। PlatformerGame FlameGame Component Component onLoad अंत में, हमने अपनी फ़ाइल आयात की जिसे हमने पहले बनाया था और जोड़ा गया था ताकि स्पष्ट रूप से घोषित किया जा सके कि हमारी संपत्ति स्थिरांक कहां से आ रहे हैं। और सूची में सूचीबद्ध सभी संपत्तियों को गेम इमेज कैश में लोड करने के लिए विधि का उपयोग किया। assets.dart as Assets SPRITES images.loadAll फिर, हमें से अपना नया बनाना होगा। फ़ाइल को निम्नानुसार संशोधित करें: main.dart PlatformerGame import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; import 'game.dart'; void main() { runApp( const GameWidget<PlatformerGame>.controlled( gameFactory: PlatformerGame.new, ), ); } सारी तैयारी हो चुकी है, और मज़ेदार हिस्सा शुरू होता है। खिलाड़ी चरित्र जोड़ना एक नया फोल्डर बनाएं और उसके अंदर एक नई फाइल बनाएं। यह वह घटक होगा जो खिलाड़ी के चरित्र का प्रतिनिधित्व करता है: द बॉय। lib/actors/ theboy.dart import '../game.dart'; import '../assets.dart' as Assets; import 'package:flame/components.dart'; class TheBoy extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { TheBoy({ required super.position, // Position on the screen }) : super( size: Vector2.all(48), // Size of the component anchor: Anchor.bottomCenter // ); @override Future<void> onLoad() async { animation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, // For now we only need idle animation, so we load only 1 frame textureSize: Vector2.all(20), // Size of a single sprite in the sprite sheet stepTime: 0.12, // Time between frames, since it's a single frame not that important ), ); } } वर्ग का विस्तार करता है जो एनिमेटेड स्प्राइट्स के लिए उपयोग किया जाने वाला एक घटक है और इसमें एक मिक्सिन है जो हमें गेम कैश से छवियों को लोड करने या बाद में वैश्विक चर प्राप्त करने के लिए गेम ऑब्जेक्ट को संदर्भित करने की अनुमति देता है। SpriteAnimationComponent HasGameRef हमारे विधि में हम स्प्राइट शीट से एक नया बनाते हैं जिसे हमने फ़ाइल में घोषित किया था। onLoad THE_BOY SpriteAnimation assets.dart अब चलो हमारे खिलाड़ी को खेल में जोड़ें! फ़ाइल पर वापस लौटें और विधि के निचले भाग में निम्नलिखित जोड़ें: game.dart onLoad final theBoy = TheBoy(position: Vector2(size.x / 2, size.y / 2)); add(theBoy); यदि आप अभी खेल चलाते हैं, तो हमें द बॉय से मिलने में सक्षम होना चाहिए! खिलाड़ी आंदोलन सबसे पहले, हमें कीबोर्ड से द बॉय को नियंत्रित करने की क्षमता जोड़ने की जरूरत है। चलिए को फ़ाइल में मिलाते हैं। HasKeyboardHandlerComponents game.dart class PlatformerGame extends FlameGame with HasKeyboardHandlerComponents इसके बाद, और मिश्रण पर वापस आते हैं: theboy.dart KeyboardHandler class TheBoy extends SpriteAnimationComponent with KeyboardHandler, HasGameRef<PlatformerGame> फिर, घटक में कुछ नए वर्ग चर जोड़ें: TheBoy final double _moveSpeed = 300; // Max player's move speed int _horizontalDirection = 0; // Current direction the player is facing final Vector2 _velocity = Vector2.zero(); // Current player's speed अंत में, विधि को ओवरराइड करते हैं जो कीबोर्ड इनपुट सुनने की अनुमति देता है: onKeyEvent @override bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { _horizontalDirection = 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyA) || keysPressed.contains(LogicalKeyboardKey.arrowLeft)) ? -1 : 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyD) || keysPressed.contains(LogicalKeyboardKey.arrowRight)) ? 1 : 0; return true; } अब 1 के बराबर है अगर खिलाड़ी दाईं ओर जाता है, -1 अगर खिलाड़ी बाईं ओर जाता है, और 0 अगर खिलाड़ी नहीं चलता है। हालाँकि, हम अभी तक इसे स्क्रीन पर नहीं देख सकते हैं, क्योंकि खिलाड़ी की स्थिति अभी तक नहीं बदली है। आइए विधि जोड़कर इसे ठीक करें। _horizontalDirection update अब मुझे यह समझाने की जरूरत है कि गेम लूप क्या है। मूल रूप से, इसका मतलब है कि खेल को अंतहीन लूप में चलाया जा रहा है। प्रत्येक पुनरावृत्ति में, वर्तमान स्थिति को विधि में प्रस्तुत किया जाता है और फिर विधि में एक नई स्थिति की गणना की जाती है। विधि के हस्ताक्षर में पैरामीटर अंतिम राज्य अद्यतन के बाद से मिलीसेकंड में समय है। इसे ध्यान में रखते हुए, में निम्नलिखित जोड़ें: Component's render update dt theboy.dart @override void update(double dt) { super.update(dt); _velocity.x = _horizontalDirection * _moveSpeed; position += _velocity * dt; } प्रत्येक गेम लूप चक्र के लिए, हम वर्तमान दिशा और अधिकतम गति का उपयोग करके क्षैतिज वेग को अपडेट करते हैं। फिर हम स्प्राइट स्थिति को से गुणा करके अद्यतन मान के साथ बदलते हैं। dt हमें अंतिम भाग की आवश्यकता क्यों है? ठीक है, यदि आप केवल वेग के साथ स्थिति को अपडेट करते हैं, तो प्रेत अंतरिक्ष में उड़ जाएगा। लेकिन क्या हम छोटे गति मूल्य का उपयोग कर सकते हैं, आप पूछ सकते हैं? हम कर सकते हैं, लेकिन जिस तरह से खिलाड़ी चलता है वह अलग-अलग फ्रेम प्रति सेकंड (एफपीएस) दर के साथ अलग होगा। प्रति सेकंड फ़्रेम (या गेम लूप) की संख्या गेम के प्रदर्शन और उसके द्वारा चलाए जा रहे हार्डवेयर पर निर्भर करती है। डिवाइस का प्रदर्शन जितना बेहतर होगा, एफपीएस उतना ही अधिक होगा और खिलाड़ी उतनी ही तेजी से आगे बढ़ेगा। इससे बचने के लिए, हम गति को अंतिम फ्रेम से पारित समय पर निर्भर करते हैं। इस तरह स्प्राइट किसी भी FPS पर समान रूप से चलेगा। ठीक है, अगर हम अभी खेल चलाते हैं, तो हमें यह देखना चाहिए: बहुत बढ़िया, अब लड़के को बायीं ओर घुमाते हैं। इसे विधि के नीचे जोड़ें: update if ((_horizontalDirection < 0 && scale.x > 0) || (_horizontalDirection > 0 && scale.x < 0)) { flipHorizontally(); } काफी आसान तर्क: हम जांचते हैं कि क्या वर्तमान दिशा (उपयोगकर्ता जिस तीर को दबा रहा है) स्प्राइट की दिशा से अलग है, फिर हम स्प्राइट को क्षैतिज अक्ष के साथ फ़्लिप करते हैं। अब चल रहे एनीमेशन को भी जोड़ते हैं। पहले दो नए वर्ग चर परिभाषित करें: late final SpriteAnimation _runAnimation; late final SpriteAnimation _idleAnimation; फिर इस तरह अपडेट करें: onLoad @override Future<void> onLoad() async { _idleAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, textureSize: Vector2.all(20), stepTime: 0.12, ), ); _runAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(20), stepTime: 0.12, ), ); animation = _idleAnimation; } यहां हमने क्लास वेरिएबल में पहले जोड़ा गया निष्क्रिय एनीमेशन निकाला और एक नया रन एनीमेशन वैरिएबल परिभाषित किया। अगला, आइए एक नया तरीका जोड़ें: updateAnimation void updateAnimation() { if (_horizontalDirection == 0) { animation = _idleAnimation; } else { animation = _runAnimation; } } और अंत में, इस विधि को विधि के निचले भाग में लागू करें और खेल को चलाएं। update निष्कर्ष यह पहले भाग के लिए है। हमने सीखा कि फ्लेम गेम को कैसे सेट अप किया जाए, एसेट्स को कहां खोजा जाए, उन्हें अपने गेम में कैसे लोड किया जाए, और कैसे एक शानदार एनिमेटेड कैरेक्टर बनाया जाए और कीबोर्ड इनपुट के आधार पर इसे स्थानांतरित किया जाए। इस भाग का कोड पाया जा सकता है। मेरे जीथब पर अगले लेख में, मैं कवर करूंगा कि टाइल का उपयोग करके गेम स्तर कैसे बनाया जाए, फ्लेम कैमरा को कैसे नियंत्रित किया जाए और एक लंबन पृष्ठभूमि को कैसे जोड़ा जाए। बने रहें! संसाधन प्रत्येक भाग के अंत में, मैं उन बेहतरीन क्रिएटर्स और संसाधनों की सूची जोड़ूंगा जिनसे मैंने सीखा है। GrafxKid के आर्केड प्लेटफार्म एसेट्स https://opengameart.org/content/arcade-platformer-assets देवकेज फ्लेम गेम डेवलपमेंट सीरीज़: https://youtu.be/mSPalRqZQS8 क्रेग ओडीए चैनल https://youtu.be/hwQpBuZoV9s एम्बर क्वेस्ट गेम ट्यूटोरियल https://github.com/flame-engine/flame/blob/main/doc/tutorials/platformer/platformer.md लौ इंजन प्रलेखन https://docs.flame-engine.org/1.6.0/flame/flame.html