1. परिचय
Flame, Flutter पर आधारित 2D गेम इंजन है. इस कोडलैब में आपको एक गेम बनाना है, जो 70 के दशक के क्लासिक वीडियो गेम में से एक, स्टीव वॉज़्निएक के ब्रेकआउट से प्रेरित होगा. चमगादड़, बॉल, और ईंटें बनाने के लिए, आपको फ़्लेम के कॉम्पोनेंट इस्तेमाल करने होंगे. चमगादड़ की गतिविधि को ऐनिमेट करने के लिए, आप Flame के इफ़ेक्ट का इस्तेमाल करेंगे और यह देखेंगे कि Flutter के स्टेट मैनेजमेंट सिस्टम के साथ Flame को कैसे इंटिग्रेट किया जाए.
गेम पूरा होने पर, आपका गेम इस ऐनिमेटेड GIF की तरह दिखना चाहिए. भले ही, यह थोड़ा धीमा हो जाए.
आप इन चीज़ों के बारे में जानेंगे
- फ़्लेम की बुनियादी बातें कैसे काम करती हैं, इसकी शुरुआत
GameWidget
से होती है. - गेम लूप इस्तेमाल करने का तरीका.
- फ़्लेम की
Component
कैसे काम करती है. वे Flutter केWidget
से मिलते-जुलते हैं. - टकरावों को कैसे हैंडल करें.
Component
को ऐनिमेट करने के लिएEffect
का इस्तेमाल करने का तरीका.- Flutter
Widget
s को Flame गेम पर ओवरले करने का तरीका. - Flutter के स्टेट मैनेजमेंट के साथ Flame को इंटिग्रेट करने का तरीका.
आपको क्या बनाना होगा
इस कोडलैब में, Flutter और Flame का इस्तेमाल करके 2D गेम बनाया जा सकता है. गेम पूरा होने पर, आपके गेम को ये ज़रूरी शर्तें पूरी करनी होंगी
- Flutter के साथ काम करने वाले सभी छह प्लैटफ़ॉर्म पर फ़ंक्शन मौजूद है: Android, iOS, Linux, macOS, Windows, और वेब
- Flame के गेम लूप का इस्तेमाल करके, कम से कम 60 FPS (फ़्रेम प्रति सेकंड) बनाए रखें.
google_fonts
पैकेज औरflutter_animate
जैसी Flutter सुविधाओं का इस्तेमाल करके, 80 के दशक के आर्केड गेमिंग का आनंद फिर से लें.
2. Flutter एनवायरमेंट को सेट अप करें
संपादक
इस कोडलैब को आसान बनाने के लिए, यह माना जाता है कि विज़ुअल स्टूडियो कोड (वीएस कोड) आपका डेवलपमेंट एनवायरमेंट है. बनाम कोड के लिए कोई शुल्क नहीं लिया जाता और यह सभी बड़े प्लैटफ़ॉर्म पर काम करता है. हम इस कोडलैब के लिए वीएस कोड का इस्तेमाल करते हैं, क्योंकि निर्देशों में डिफ़ॉल्ट रूप से वीएस कोड के हिसाब से बने शॉर्टकट होते हैं. अब टास्क और भी आसान हो जाते हैं: "इस बटन पर क्लिक करें" या "X करने के लिए यह बटन दबाएं" के बजाय "X करने के लिए अपने संपादक में उचित कार्रवाई करें" का उपयोग करें.
अपनी पसंद के किसी भी एडिटर का इस्तेमाल किया जा सकता है: Android Studio, अन्य IntelliJ IDEs, Emacs, Vim या Notepad++. वे सभी Flutter के साथ काम करते हैं.
डेवलपमेंट टारगेट चुनें
Flutter इस्तेमाल करें और कई प्लैटफ़ॉर्म के लिए ऐप्लिकेशन बनाएं. आपका ऐप्लिकेशन इनमें से किसी भी ऑपरेटिंग सिस्टम पर चल सकता है:
- iOS
- Android
- Windows
- macOS
- Linux
- वेब
अपने डेवलपमेंट टारगेट के तौर पर किसी एक ऑपरेटिंग सिस्टम को चुनना आम बात है. यह वह ऑपरेटिंग सिस्टम है जिस पर आपका ऐप्लिकेशन डेवलपमेंट के दौरान चलता है.
उदाहरण के लिए: मान लें कि अपना Flutter ऐप्लिकेशन डेवलप करने के लिए Windows लैपटॉप का इस्तेमाल किया जा रहा है. इसके बाद, डेवलपमेंट टारगेट के तौर पर Android को चुना जाता है. अपने ऐप्लिकेशन की झलक देखने के लिए, किसी Android डिवाइस को यूएसबी केबल की मदद से अपने Windows लैपटॉप से जोड़ा जाता है. इसके बाद, आपका ऐप्लिकेशन जिस Android डिवाइस से अटैच किया गया है या Android एम्युलेटर पर चलता है. आप डेवलपमेंट टारगेट के तौर पर Windows को चुन सकते थे, जो आपके ऐप्लिकेशन के डेवलपमेंट में, एडिटर के साथ-साथ Windows ऐप्लिकेशन के तौर पर भी काम करता था.
आप शायद वेब को अपने डेवलपमेंट टारगेट के तौर पर चुनना चाहें. डेवलपमेंट के दौरान इसकी एक समस्या है: Flutter की Stateful Hot Reload सुविधा ऐक्सेस नहीं की जा सकती. Flutter फ़िलहाल वेब ऐप्लिकेशन को हॉट-रीलोड नहीं कर सकता.
जारी रखने से पहले अपनी पसंद तय करें. अन्य ऑपरेटिंग सिस्टम पर अपने ऐप्लिकेशन को बाद में कभी भी चलाया जा सकता है. डेवलपमेंट टारगेट चुनने पर, अगला चरण आसान हो जाता है.
Flutter इंस्टॉल करें
Flutter SDK टूल इंस्टॉल करने के अप-टू-डेट निर्देश docs.flutter.dev पर मिल सकते हैं.
Flutter वेबसाइट पर दिए गए निर्देशों में SDK टूल, डेवलपमेंट टारगेट से जुड़े टूल, और एडिटर प्लगिन को इंस्टॉल करने के बारे में बताया गया है. इस कोडलैब के लिए, नीचे दिए गए सॉफ़्टवेयर इंस्टॉल करें:
- Flutter SDK टूल
- Flutter प्लगिन के साथ विज़ुअल स्टूडियो कोड
- आपके चुने गए डेवलपमेंट टारगेट के लिए कंपाइलर सॉफ़्टवेयर. (macOS या iOS को टारगेट करने के लिए, आपको Windows या Xcode को टारगेट करने के लिए Visual Studio की ज़रूरत होगी)
अगले सेक्शन में, आपको अपना पहला Flutter प्रोजेक्ट बनाना होगा.
अगर आपको किसी समस्या को हल करना है, तो इनमें से कुछ सवाल और जवाब (StackOverflow से) आपको मिल सकते हैं.
अक्सर पूछे जाने वाले सवाल
- मुझे Flutter SDK टूल का पाथ कैसे मिलेगा?
- Flutter निर्देश न मिलने पर, मैं क्या करूं?
- "स्टार्टअप लॉक को रिलीज़ करने के लिए, एक और फ़्लटर कमांड का इंतज़ार किया जा रहा है" गड़बड़ी को कैसे ठीक करूं कोई समस्या है?
- मैं Flutter को कैसे बताऊं कि मेरा Android SDK इंस्टॉलेशन कहां है?
flutter doctor --android-licenses
को चलाते समय, मैं Java की गड़बड़ी से कैसे निपटूं?- मैं Android
sdkmanager
टूल न मिलने की समस्या का कैसे हल करूं? - मैं "
cmdline-tools
कॉम्पोनेंट मौजूद नहीं है" समस्या से जुड़ी समस्या को कैसे हल करूं में गड़बड़ी है? - मैं Apple Silicon (M1) पर CocoaPods कैसे चलाऊं?
- मैं बनाम कोड में सेव करने पर अपने-आप फ़ॉर्मैट होने की सुविधा को कैसे बंद करूं?
3. प्रोजेक्ट बनाना
अपना पहला Flutter प्रोजेक्ट बनाएं
इसमें, VS कोड को खोलना और अपनी चुनी गई डायरेक्ट्री में Flutter ऐप्लिकेशन टेंप्लेट बनाना शामिल है.
- विज़ुअल स्टूडियो कोड लॉन्च करें.
- कमांड पटल (
F1
याCtrl+Shift+P
याShift+Cmd+P
) खोलें. इसके बाद, "fltter new" टाइप करें. स्क्रीन पर दिखने पर, Flutter: नया प्रोजेक्ट कमांड चुनें.
- ऐप्लिकेशन खाली करें चुनें. वह डायरेक्ट्री चुनें जिसमें आपको प्रोजेक्ट बनाना है. यह कोई भी ऐसी डायरेक्ट्री होनी चाहिए जिसके लिए खास सुविधाओं की ज़रूरत न हो या पाथ में कोई स्पेस न हो. उदाहरण के लिए, आपकी होम डायरेक्ट्री या
C:\src\
.
- अपने प्रोजेक्ट को
brick_breaker
नाम दें. इस कोडलैब के बाकी हिस्से को यह मानकर चलता है कि आपने अपने ऐप्लिकेशन का नामbrick_breaker
रखा है.
Flutter अब आपका प्रोजेक्ट फ़ोल्डर बनाता है और VS Code खोलता है. अब आप ऐप्लिकेशन के बुनियादी स्कैफ़ोल्ड से दो फ़ाइलों की सामग्री को ओवरराइट कर देंगे.
कॉपी करें और शुरुआती ऐप्लिकेशन चिपकाएं
ऐसा करने से, इस कोडलैब में दिया गया कोड आपके ऐप्लिकेशन में जुड़ जाता है.
- वीएस कोड के बाएं पैनल में, Explorer पर क्लिक करें और
pubspec.yaml
फ़ाइल खोलें.
- इस फ़ाइल की सामग्री को इनसे बदलें:
pubspec.yaml
name: brick_breaker
description: "Re-implementing Woz's Breakout"
publish_to: 'none'
version: 0.1.0
environment:
sdk: '>=3.3.0 <4.0.0'
dependencies:
flame: ^1.16.0
flutter:
sdk: flutter
flutter_animate: ^4.5.0
google_fonts: ^6.1.0
dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^3.0.1
flutter:
uses-material-design: true
pubspec.yaml
फ़ाइल आपके ऐप्लिकेशन के बारे में बुनियादी जानकारी के बारे में बताती है. जैसे, ऐप्लिकेशन का मौजूदा वर्शन, उसकी डिपेंडेंसी, और वे एसेट जिनसे ऐप्लिकेशन शिप किया जाएगा.
main.dart
फ़ाइल कोlib/
डायरेक्ट्री में खोलें.
- इस फ़ाइल की सामग्री को इनसे बदलें:
lib/main.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
void main() {
final game = FlameGame();
runApp(GameWidget(game: game));
}
- सब कुछ काम कर रहा है, इसकी पुष्टि करने के लिए इस कोड को चलाएं. इसमें एक नई विंडो दिखाई देगी, जिसमें सिर्फ़ खाली काला बैकग्राउंड होगा. दुनिया का सबसे खराब वीडियो गेम अब 60fps पर रेंडर हो रहा है!
4. गेम बनाएं
गेम का साइज़ बढ़ाएं
दो डाइमेंशन (2D) में खेले जाने वाले गेम के लिए, खेलने की जगह की ज़रूरत होती है. आपको खास डाइमेंशन का एक एरिया बनाना होगा. इसके बाद, इन डाइमेंशन का इस्तेमाल गेम के दूसरे आसपेक्ट को साइज़ देने के लिए किया जाएगा.
खेल की जगह में निर्देशांक बनाने के कई तरीके हैं. एक तरीके से, स्क्रीन के बीच से दिशा को मापा जा सकता है. ऐसा ऑरिजिन (0,0)
स्क्रीन के बीच में होता है. पॉज़िटिव वैल्यू आइटम को x ऐक्सिस पर दाईं ओर और y ऐक्सिस पर ऊपर की ओर ले जाती हैं. यह मानक इन दिनों ज़्यादातर मौजूदा गेम पर लागू होता है, खासकर तब, जब थ्री डाइमेंशन वाले गेम होते हैं.
मूल ब्रेकआउट गेम बनाए जाने के दौरान, सबसे पहले बाएं कोने में ऑरिजिन सेट किया गया था. पॉज़िटिव x दिशा में कोई बदलाव नहीं हुआ है. हालांकि, y को फ़्लिप किया गया है. x सकारात्मक x दिशा सही थी और y नीचे थी. उस दौर के हिसाब से बने रहने के लिए, यह गेम ऑरिजिन को सबसे ऊपर बाएं कोने पर सेट करता है.
lib/src
नाम की नई डायरेक्ट्री में config.dart
नाम की फ़ाइल बनाएं. इस फ़ाइल में, नीचे दिए गए तरीके के हिसाब से ज़्यादा कॉन्सटेंट मिलेंगे.
lib/src/config.dart
const gameWidth = 820.0;
const gameHeight = 1600.0;
इस गेम की चौड़ाई 820 पिक्सल और ऊंचाई 1600 पिक्सल होगी. गेम एरिया उस विंडो में फ़िट हो जाता है जिसमें वह दिखता है, लेकिन स्क्रीन पर जोड़े गए सभी कॉम्पोनेंट, इस ऊंचाई और चौड़ाई के मुताबिक होते हैं.
PlayArea बनाएं
ब्रेकआउट गेम में, बॉल खेल की जगह की दीवारों से उछलती है. टकरावों को मैनेज करने के लिए, आपको पहले PlayArea
कॉम्पोनेंट की ज़रूरत होगी.
lib/src/components
नाम की नई डायरेक्ट्री मेंplay_area.dart
नाम की फ़ाइल बनाएं.- इस फ़ाइल में इन्हें जोड़ें.
lib/src/components/play_area.dart
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
class PlayArea extends RectangleComponent with HasGameReference<BrickBreaker> {
PlayArea()
: super(
paint: Paint()..color = const Color(0xfff2e8cf),
);
@override
FutureOr<void> onLoad() async {
super.onLoad();
size = Vector2(game.width, game.height);
}
}
जहां Flutter में Widget
s हैं, वहीं Lame में Component
s हैं. Flutter ऐप्लिकेशन में विजेट के ट्री बनाए जाते हैं, जबकि फ़्लेम गेम में अलग-अलग कॉम्पोनेंट के पेड़ों का रखरखाव किया जाता है.
फ़्लटर और फ़्लेम के बीच दिलचस्प अंतर है. Flutter का विजेट ट्री, कुछ समय के लिए बनाया जाता है. इसका इस्तेमाल, स्थायी और बदलाव किए जा सकने वाले RenderObject
लेयर को अपडेट करने के लिए किया जाता है. फ़्लेम के कॉम्पोनेंट स्थायी होते हैं और उनमें बदलाव किया जा सकता है. इसलिए, यह माना जाता है कि डेवलपर, सिम्युलेशन सिस्टम के हिस्से के तौर पर इन कॉम्पोनेंट का इस्तेमाल करेगा.
फ़्लेम के कॉम्पोनेंट, गेम को बेहतर तरीके से दिखाने के लिए ऑप्टिमाइज़ किए जाते हैं. यह कोडलैब, गेम लूप से शुरू होगा. इसे अगले चरण में दिखाया जाएगा.
- ग़ैर-ज़रूरी चीज़ों को कंट्रोल करने के लिए, कोई ऐसी फ़ाइल जोड़ें जिसमें इस प्रोजेक्ट के सभी कॉम्पोनेंट शामिल हों.
lib/src/components
मेंcomponents.dart
फ़ाइल बनाएं और यह कॉन्टेंट जोड़ें.
lib/src/components/components.dart
export 'play_area.dart';
export
डायरेक्टिव, import
की इन्वर्स भूमिका निभाता है. यह बताता है कि किसी दूसरी फ़ाइल में इंपोर्ट करने पर, यह फ़ाइल किस फ़ंक्शन से जुड़ी जानकारी दिखाती है. नीचे दिए गए चरणों में नए कॉम्पोनेंट जोड़ने पर, इस फ़ाइल में एंट्री बढ़ जाएंगी.
एक Flame गेम बनाएं
पिछले चरण से लाल रंग के स्क्विगल को दूर करने के लिए, फ़्लेम की FlameGame
के लिए एक नई सब-क्लास बनाएं.
lib/src
मेंbrick_breaker.dart
नाम की फ़ाइल बनाएं और यह कोड जोड़ें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'components/components.dart';
import 'config.dart';
class BrickBreaker extends FlameGame {
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
double get width => size.x;
double get height => size.y;
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
}
}
यह फ़ाइल गेम की कार्रवाइयों को कंट्रोल करती है. गेम इंस्टेंस बनाते समय, यह कोड गेम को फ़िक्स्ड रिज़ॉल्यूशन रेंडरिंग का इस्तेमाल करने के लिए कॉन्फ़िगर करता है. गेम, स्क्रीन पर दिखने वाली स्क्रीन के साइज़ में बदलाव करता है और ज़रूरत के हिसाब से लेटरबॉक्स का इस्तेमाल करता है.
गेम की चौड़ाई और ऊंचाई को बिना अनुमति के सार्वजनिक किया जाता है, ताकि PlayArea
जैसे चाइल्ड कॉम्पोनेंट खुद को सही साइज़ पर सेट कर सकें.
ओवरराइड किए गए onLoad
तरीके में, आपका कोड दो कार्रवाइयां करता है.
- व्यूफ़ाइंडर के लिए ऐंकर के तौर पर सबसे ऊपर बाईं ओर कॉन्फ़िगर करता है. डिफ़ॉल्ट रूप से, व्यूफ़ाइंडर
(0,0)
के लिए ऐंकर के तौर पर एरिया के बीच का इस्तेमाल करता है. PlayArea
कोworld
में जोड़ता है. दुनिया, गेम की दुनिया का प्रतिनिधित्व करती है. यह अपने सभी बच्चों को,CameraComponent
के ट्रांसफ़ॉर्मेशन व्यू के ज़रिए दिखाता है.
गेम को स्क्रीन पर दिखाएं
इस चरण में किए गए सभी बदलाव देखने के लिए, अपनी lib/main.dart
फ़ाइल में ये बदलाव करें.
lib/main.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'src/brick_breaker.dart'; // Add this import
void main() {
final game = BrickBreaker(); // Modify this line
runApp(GameWidget(game: game));
}
ये बदलाव करने के बाद, गेम फिर से शुरू करें. गेम नीचे दिए गए डायग्राम में दिखना चाहिए.
अगले चरण में, आपको दुनिया के सामने एक बॉल जोड़नी होगी और उसे हिलाना होगा!
5. बॉल दिखाएं
बॉल कॉम्पोनेंट बनाना
स्क्रीन पर एक हिलती हुई बॉल को दिखाने के लिए एक और कॉम्पोनेंट बनाना और उसे गेम की दुनिया में शामिल करना ज़रूरी है.
lib/src/config.dart
फ़ाइल के कॉन्टेंट में, दिए गए तरीके का इस्तेमाल करके बदलाव करें.
lib/src/config.dart
const gameWidth = 820.0;
const gameHeight = 1600.0;
const ballRadius = gameWidth * 0.02; // Add this constant
इस कोडलैब में, कॉन्सटेंट को डिराइव्ड वैल्यू के तौर पर तय करने वाला डिज़ाइन पैटर्न कई बार दिखेगा. इससे आपको टॉप लेवल gameWidth
और gameHeight
में बदलाव करने की सुविधा मिलती है. इससे, यह पता लगाया जा सकता है कि गेम कैसा दिखता है और उसमें क्या बदलाव होते हैं.
lib/src/components
कीball.dart
नाम वाली फ़ाइल में,Ball
कॉम्पोनेंट बनाएं.
lib/src/components/ball.dart
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
class Ball extends CircleComponent {
Ball({
required this.velocity,
required super.position,
required double radius,
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill);
final Vector2 velocity;
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}
}
पहले, आपने RectangleComponent
का इस्तेमाल करके PlayArea
तय किया था, इसलिए इससे पता चलता है कि अन्य आकृतियां भी मौजूद हैं. RectangleComponent
की तरह, CircleComponent
को PositionedComponent
से लिया गया है, ताकि आप स्क्रीन पर बॉल को रख सकें. इससे भी अहम बात यह है कि इसकी जगह को अपडेट किया जा सकता है.
यह कॉम्पोनेंट, velocity
के बारे में जानकारी देता है या समय के साथ इसकी जगह बदलता है. वेलोसिटी एक Vector2
ऑब्जेक्ट है, क्योंकि वेलोसिटी, स्पीड और दिशा, दोनों है. पोज़िशन अपडेट करने के लिए, update
तरीका बदलें. गेम इंजन, हर फ़्रेम के लिए इस तरीके का इस्तेमाल करता है. dt
पिछले फ़्रेम और इस फ़्रेम के बीच की अवधि है. इसकी मदद से, बहुत ज़्यादा कंप्यूटेशन (हिसाब लगाना) की वजह से, अलग-अलग फ़्रेम रेट (60 हर्ट्ज़ या 120 हर्ट्ज़) या लंबे फ़्रेम जैसी चीज़ों के हिसाब से खुद को ढाला जा सकता है.
position += velocity * dt
के अपडेट पर खास ध्यान दें. इस तरह आप समय के साथ, मोशन के अलग-अलग सिम्युलेशन को अपडेट करने का तरीका लागू करते हैं.
- कॉम्पोनेंट की सूची में
Ball
कॉम्पोनेंट को शामिल करने के लिए,lib/src/components/components.dart
फ़ाइल में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/components.dart
export 'ball.dart'; // Add this export
export 'play_area.dart';
दुनिया में गेंद का आनंद लेना
तुम्हारे पास गेंद है. आइए, इसे दुनिया में जगह पर रखते हैं और खेल की जगह में इधर-उधर जाने के लिए इसे सेट अप करते हैं.
lib/src/brick_breaker.dart
फ़ाइल में, दिए गए तरीके से बदलाव करें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math; // Add this import
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'components/components.dart';
import 'config.dart';
class BrickBreaker extends FlameGame {
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final rand = math.Random(); // Add this variable
double get width => size.x;
double get height => size.y;
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
world.add(Ball( // Add from here...
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
debugMode = true; // To here.
}
}
इस बदलाव से, Ball
कॉम्पोनेंट को world
में जोड़ दिया जाता है. बॉल के position
को डिसप्ले एरिया के बीच में सेट करने के लिए, कोड पहले गेम के साइज़ को आधा कर देता है. ऐसा इसलिए, क्योंकि Vector2
में ऑपरेटर ओवरलोड (*
और /
) होते हैं, ताकि Vector2
को अदिश मान से स्केल किया जा सके.
बॉल के velocity
को सेट करने में ज़्यादा मुश्किल होना ज़रूरी है. इसका मकसद बॉल को सही रफ़्तार पर स्क्रीन पर किसी भी क्रम में लगाना है. normalized
तरीके को कॉल करने पर, एक Vector2
ऑब्जेक्ट बनता है. यह ऑब्जेक्ट, मूल Vector2
की दिशा पर सेट होता है, लेकिन इसका स्केल 1 होता है. इससे बॉल की रफ़्तार एक जैसी रहती है, भले ही बॉल किसी भी दिशा में जाए. इसके बाद, बॉल की रफ़्तार को खेल की ऊंचाई के 1/4 तक बढ़ाया जाता है.
इन अलग-अलग वैल्यू को सही तरीके से हासिल करने के लिए बार-बार बदलाव करना पड़ता है. इसे इंडस्ट्री में प्लेटेस्टिंग भी कहा जाता है.
आखिरी लाइन, डीबगिंग डिसप्ले को चालू कर देती है. इससे डिसप्ले में और जानकारी जुड़ जाती है, ताकि डीबग करने में मदद मिल सके.
अब गेम चलाने पर, यह निम्न डिस्प्ले जैसा दिखना चाहिए.
PlayArea
कॉम्पोनेंट और Ball
कॉम्पोनेंट, दोनों में डीबग करने की जानकारी मौजूद है. हालांकि, बैकग्राउंड मैट, PlayArea
के नंबर को काट देते हैं. हर चीज़ में डीबग करने की जानकारी दिखने की वजह यह है कि आपने पूरे कॉम्पोनेंट ट्री के लिए debugMode
को चालू किया है. अगर यह ज़्यादा काम का है, तो सिर्फ़ चुने गए कॉम्पोनेंट के लिए भी डीबग करने की सुविधा चालू की जा सकती है.
गेम को कई बार रीस्टार्ट करने पर, ऐसा हो सकता है कि बॉल उम्मीद के मुताबिक दीवारों से इंटरैक्ट न करती हो. उस प्रभाव को पूरा करने के लिए, आपको टक्कर का पता लगाने की सुविधा को जोड़ना होगा, जो आपको अगले चरण में करना होगा.
6. बाउंसिंग इफ़ेक्ट
टक्कर का पता लगाने की सुविधा जोड़ें
टक्कर का पता लगाने की सुविधा से, आपका गेम इस बात की पहचान कर लेता है कि दो ऑब्जेक्ट एक-दूसरे के संपर्क में आए.
गेम में टक्कर लगने का पता लगाने की सुविधा जोड़ने के लिए, BrickBreaker
गेम में HasCollisionDetection
मिक्सिन जोड़ें, जैसा कि इस कोड में दिखाया गया है.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'components/components.dart';
import 'config.dart';
class BrickBreaker extends FlameGame with HasCollisionDetection { // Modify this line
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final rand = math.Random();
double get width => size.x;
double get height => size.y;
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
world.add(Ball(
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
debugMode = true;
}
}
यह कॉम्पोनेंट के हिटबॉक्स को ट्रैक करता है और हर गेम टिक पर कोलिज़न कॉलबैक ट्रिगर करता है.
गेम के हिटबॉक्स की जानकारी अपने-आप भरने के लिए, नीचे बताए गए तरीके से PlayArea
कॉम्पोनेंट में बदलाव करें.
lib/src/components/play_area.dart
import 'dart:async';
import 'package:flame/collisions.dart'; // Add this import
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
class PlayArea extends RectangleComponent with HasGameReference<BrickBreaker> {
PlayArea()
: super(
paint: Paint()..color = const Color(0xfff2e8cf),
children: [RectangleHitbox()], // Add this parameter
);
@override
FutureOr<void> onLoad() async {
super.onLoad();
size = Vector2(game.width, game.height);
}
}
RectangleComponent
के चाइल्ड के रूप में RectangleHitbox
कॉम्पोनेंट जोड़ने से, टक्कर का पता लगाने के लिए एक हिट बॉक्स बनेगा, जो पैरंट कॉम्पोनेंट के साइज़ से मेल खाता है. कई बार RectangleHitbox
के लिए relative
नाम का एक फ़ैक्ट्री कंस्ट्रक्टर मौजूद है, जब आपको ऐसा हिटबॉक्स चाहिए जो पैरंट कॉम्पोनेंट से छोटा या बड़ा हो.
बॉल बाउंस करें
अब तक, टक्कर का पता लगाने की सुविधा जोड़ने से गेमप्ले पर कोई असर नहीं पड़ा है. Ball
कॉम्पोनेंट में बदलाव करने के बाद, यह बदलाव होता है. जब बॉल PlayArea
से टकराती है, तब इसका व्यवहार भी बदलता है.
Ball
कॉम्पोनेंट में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/ball.dart
import 'package:flame/collisions.dart'; // Add this import
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart'; // And this import
import 'play_area.dart'; // And this one too
class Ball extends CircleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> { // Add these mixins
Ball({
required this.velocity,
required super.position,
required double radius,
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill,
children: [CircleHitbox()]); // Add this parameter
final Vector2 velocity;
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}
@override // Add from here...
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is PlayArea) {
if (intersectionPoints.first.y <= 0) {
velocity.y = -velocity.y;
} else if (intersectionPoints.first.x <= 0) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.x >= game.width) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.y >= game.height) {
removeFromParent();
}
} else {
debugPrint('collision with $other');
}
} // To here.
}
यह उदाहरण, onCollisionStart
कॉलबैक को जोड़ने के साथ एक बड़ा बदलाव करता है. पिछले उदाहरण में BrickBreaker
में जोड़ा गया टकराव की पहचान करने वाला सिस्टम इस कॉलबैक को कॉल करता है.
सबसे पहले, कोड यह जांच करता है कि Ball
और PlayArea
के बीच क्या टकराव हुआ है. फ़िलहाल, ऐसा लगता है कि इस गेम में कोई और कॉम्पोनेंट नहीं है. यह अगले चरण में बदल जाएगा, जब आप दुनिया के लिए एक बैट जोड़ेंगे. इसके बाद, जब बॉल किसी ऐसी चीज़ से टकराती है जो बल्ले की नहीं है, तो हैंडल करने के लिए else
शर्त भी जोड़ी जाती है. बाकी लॉजिक को लागू करने के लिए रिमाइंडर.
जब बॉल नीचे की दीवार से टकराती है, तो वह खेलने की सतह से गायब हो जाती है और बहुत ज़्यादा दिखती है. आप अगले चरण में फ़्लेम के इफ़ेक्ट की मदद से इस आर्टफ़ैक्ट को संभालते हैं.
अब जब आपके पास बॉल, गेम की दीवारों से टकरा रही है, तो यह पक्का है कि खिलाड़ी को बॉल को हिट करने के लिए बैट देना मददगार होगा...
7. बॉल पर बल्लेबाज़ी करें
बैट बनाएं
गेम के अंदर बॉल को खेल में रखने के लिए बैट जोड़ने के लिए,
lib/src/config.dart
फ़ाइल में कुछ कॉन्सटेंट नीचे दिए गए तरीके से डालें.
lib/src/config.dart
const gameWidth = 820.0;
const gameHeight = 1600.0;
const ballRadius = gameWidth * 0.02;
const batWidth = gameWidth * 0.2; // Add from here...
const batHeight = ballRadius * 2;
const batStep = gameWidth * 0.05; // To here.
batHeight
और batWidth
कॉन्सटेंट अपने-आप विस्तार से समझाए जा सकते हैं. वहीं दूसरी ओर, batStep
कॉन्स्टेंट को दिखाने के लिए, एक्सप्लेनेशन की ज़रूरत होती है. इस गेम में बॉल से इंटरैक्ट करने के लिए, खिलाड़ी प्लैटफ़ॉर्म के हिसाब से माउस या उंगली से बैट को खींच सकता है या कीबोर्ड का इस्तेमाल कर सकता है. batStep
कॉन्सटेंट यह कॉन्फ़िगर करता है कि लेफ़्ट या राइट ऐरो वाले हर बटन को दबाकर, बैट को कितनी दूर तक चलाया जा सकता है.
Bat
कॉम्पोनेंट क्लास को इस तरह परिभाषित करें.
lib/src/components/bat.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
class Bat extends PositionComponent
with DragCallbacks, HasGameReference<BrickBreaker> {
Bat({
required this.cornerRadius,
required super.position,
required super.size,
}) : super(
anchor: Anchor.center,
children: [RectangleHitbox()],
);
final Radius cornerRadius;
final _paint = Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill;
@override
void render(Canvas canvas) {
super.render(canvas);
canvas.drawRRect(
RRect.fromRectAndRadius(
Offset.zero & size.toSize(),
cornerRadius,
),
_paint);
}
@override
void onDragUpdate(DragUpdateEvent event) {
super.onDragUpdate(event);
position.x = (position.x + event.localDelta.x).clamp(0, game.width);
}
void moveBy(double dx) {
add(MoveToEffect(
Vector2((position.x + dx).clamp(0, game.width), position.y),
EffectController(duration: 0.1),
));
}
}
इस कॉम्पोनेंट में कुछ नई सुविधाएं जोड़ी गई हैं.
सबसे पहले, बैट कॉम्पोनेंट PositionComponent
है. यह RectangleComponent
या CircleComponent
नहीं है. इसका मतलब है कि इस कोड को स्क्रीन पर Bat
रेंडर करना होगा. ऐसा करने के लिए, यह render
कॉलबैक को बदल देता है.
canvas.drawRRect
(राउंड रेक्टैंगल बनाएं) कॉल को करीब से देखने पर, आपके मन में यह सवाल आ सकता है, "रेक्टैंगल कहां है?" Offset.zero & size.toSize()
, dart:ui
Offset
क्लास पर operator &
ओवरलोड का फ़ायदा उठाता है, जिससे Rect
s बनते हैं. इस शॉर्टहैंड की मदद से हो सकता है कि शुरुआत में आपको भ्रम हो जाए, लेकिन इसे बार-बार लोअर लेवल के फ़्लटर और फ़्लेम कोड में देखा जा सकता है.
दूसरा विकल्प, प्लैटफ़ॉर्म के हिसाब से इस Bat
कॉम्पोनेंट को उंगली या माउस से खींचा जा सकता है. इस सुविधा को लागू करने के लिए, DragCallbacks
मिक्सइन जोड़ें और onDragUpdate
इवेंट को बदलें.
आखिर में, Bat
कॉम्पोनेंट को कीबोर्ड कंट्रोल का जवाब देना होगा. moveBy
फ़ंक्शन की मदद से अन्य कोड, इस बैट को वर्चुअल पिक्सल की एक तय संख्या तक बाईं या दाईं ओर जाने के लिए कह सकता है. यह फ़ंक्शन, फ़्लेम गेम इंजन की एक नई सुविधा उपलब्ध कराता है: Effect
. इस कॉम्पोनेंट के चाइल्ड के तौर पर MoveToEffect
ऑब्जेक्ट जोड़ने पर, प्लेयर को नई पोज़िशन में बैट मिलेगा. अलग-अलग तरह के इफ़ेक्ट इस्तेमाल करने के लिए, फ़्लेम में Effect
का कलेक्शन उपलब्ध है.
इफ़ेक्ट के कंस्ट्रक्टर के आर्ग्युमेंट में, game
गैटर का रेफ़रंस शामिल होता है. इसलिए, आपने इस क्लास में HasGameReference
मिक्सिन को शामिल किया है. यह मिक्सिन, कॉम्पोनेंट ट्री के सबसे ऊपर मौजूद BrickBreaker
इंस्टेंस को ऐक्सेस करने के लिए, इस कॉम्पोनेंट में एक तरह का सुरक्षित game
ऐक्सेसर जोड़ता है.
BrickBreaker
कोBat
उपलब्ध कराने के लिए,lib/src/components/components.dart
फ़ाइल को नीचे बताए गए तरीके से अपडेट करें.
lib/src/components/components.dart
export 'ball.dart';
export 'bat.dart'; // Add this export
export 'play_area.dart';
दुनिया को बैट दिखाएं
Bat
कॉम्पोनेंट को गेम की दुनिया में जोड़ने के लिए, BrickBreaker
को नीचे बताए गए तरीके से अपडेट करें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/events.dart'; // Add this import
import 'package:flame/game.dart';
import 'package:flutter/material.dart'; // And this import
import 'package:flutter/services.dart'; // And this
import 'components/components.dart';
import 'config.dart';
class BrickBreaker extends FlameGame
with HasCollisionDetection, KeyboardEvents { // Modify this line
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final rand = math.Random();
double get width => size.x;
double get height => size.y;
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
world.add(Ball(
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
world.add(Bat( // Add from here...
size: Vector2(batWidth, batHeight),
cornerRadius: const Radius.circular(ballRadius / 2),
position: Vector2(width / 2, height * 0.95))); // To here
debugMode = true;
}
@override // Add from here...
KeyEventResult onKeyEvent(
KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
super.onKeyEvent(event, keysPressed);
switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft:
world.children.query<Bat>().first.moveBy(-batStep);
case LogicalKeyboardKey.arrowRight:
world.children.query<Bat>().first.moveBy(batStep);
}
return KeyEventResult.handled;
} // To here
}
KeyboardEvents
मिक्सिन और ओवरराइड किया गया onKeyEvent
तरीका जोड़ने से, कीबोर्ड इनपुट हैंडल होता है. बैट को सही तरीके से मूव करने के लिए, पहले जोड़े गए कोड को याद रखें.
जोड़े गए कोड का बचा हुआ हिस्सा, बैट को सही जगह और सही अनुपात में खेलता है. इस फ़ाइल में ये सभी सेटिंग सार्वजनिक करने से, आपके लिए बैट और बॉल के साइज़ में बदलाव करना आसान हो जाता है. इससे, आपको गेम के हिसाब से सही अनुभव मिलता है.
इस समय गेम खेलने पर, आपको पता चलता है कि बॉल को इंटरसेप्ट करने के लिए, बैट को हिला-डुलाया जा सकता है. हालांकि, आपको कोई जवाब नहीं दिखेगा. हालांकि, Ball
के कोलिज़न डिटेक्शन कोड में आपने डीबग लॉग किया था.
इसे अभी ठीक करने का समय आ गया है. Ball
कॉम्पोनेंट में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/ball.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart'; // Add this import
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import 'bat.dart'; // And this import
import 'play_area.dart';
class Ball extends CircleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Ball({
required this.velocity,
required super.position,
required double radius,
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill,
children: [CircleHitbox()]);
final Vector2 velocity;
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is PlayArea) {
if (intersectionPoints.first.y <= 0) {
velocity.y = -velocity.y;
} else if (intersectionPoints.first.x <= 0) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.x >= game.width) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.y >= game.height) {
add(RemoveEffect( // Modify from here...
delay: 0.35,
));
}
} else if (other is Bat) {
velocity.y = -velocity.y;
velocity.x = velocity.x +
(position.x - other.position.x) / other.size.x * game.width * 0.3;
} else { // To here.
debugPrint('collision with $other');
}
}
}
कोड में किए गए ये बदलाव, दो अलग-अलग समस्याओं को ठीक करते हैं.
सबसे पहले, यह स्क्रीन के निचले हिस्से से टच करते ही बॉल की मौजूदगी को ठीक कर देता है. इस समस्या को हल करने के लिए, आपको removeFromParent
कॉल को RemoveEffect
से बदलना होगा. जब बॉल देखने लायक जगह से बाहर निकल जाती है, तो RemoveEffect
उसे गेम की दुनिया से हटा देता है.
दूसरा, इन बदलावों से बैट और बॉल के बीच टकराव की स्थिति ठीक हो जाती है. यह हैंडलिंग कोड खिलाड़ी के लिए काफ़ी फ़ायदेमंद है. जब तक खिलाड़ी बल्ले से बॉल को छूता रहेगा, तब तक बॉल स्क्रीन पर ऊपर वापस चली जाती है. अगर आपको लगता है कि आपको यह गेम नहीं लगता और आपको असली दिखने वाली चीज़ों को समझना है, तो इस तरीके में बदलाव करें, ताकि यह आपके गेम के हिसाब से सही हो.
velocity
के अपडेट की जटिलता के बारे में बताना ज़रूरी है. यह सिर्फ़ वेलोसिटी के y
कॉम्पोनेंट को रिवर्स नहीं करता, जैसा कि दीवार से होने वाले टकरावों के लिए किया गया था. यह x
कॉम्पोनेंट को भी इस तरह अपडेट करता है, जो संपर्क के समय बल्ले और बॉल की स्थिति पर निर्भर करता है. इससे खिलाड़ी को बॉल पर ज़्यादा कंट्रोल मिलता है. हालांकि, यह जानकारी सिर्फ़ खेलने के अलावा, खिलाड़ी को किसी और तरीके से नहीं दी जाती.
अब जब आपके पास एक ऐसा बैट है जिससे बॉल को हिट करना है, तो अच्छी बात होगी कि बॉल के साथ कुछ ईंटों को तोड़ा जा सके!
8. विज्ञापन की बारीकियां
ईंटें बनाना
गेम में ब्रिक्स जोड़ने के लिए,
lib/src/config.dart
फ़ाइल में कुछ कॉन्सटेंट नीचे दिए गए तरीके से डालें.
lib/src/config.dart
import 'package:flutter/material.dart'; // Add this import
const brickColors = [ // Add this const
Color(0xfff94144),
Color(0xfff3722c),
Color(0xfff8961e),
Color(0xfff9844a),
Color(0xfff9c74f),
Color(0xff90be6d),
Color(0xff43aa8b),
Color(0xff4d908e),
Color(0xff277da1),
Color(0xff577590),
];
const gameWidth = 820.0;
const gameHeight = 1600.0;
const ballRadius = gameWidth * 0.02;
const batWidth = gameWidth * 0.2;
const batHeight = ballRadius * 2;
const batStep = gameWidth * 0.05;
const brickGutter = gameWidth * 0.015; // Add from here...
final brickWidth =
(gameWidth - (brickGutter * (brickColors.length + 1)))
/ brickColors.length;
const brickHeight = gameHeight * 0.03;
const difficultyModifier = 1.03; // To here.
Brick
कॉम्पोनेंट को नीचे बताए गए तरीके से डालें.
lib/src/components/brick.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import '../config.dart';
import 'ball.dart';
import 'bat.dart';
class Brick extends RectangleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Brick({required super.position, required Color color})
: super(
size: Vector2(brickWidth, brickHeight),
anchor: Anchor.center,
paint: Paint()
..color = color
..style = PaintingStyle.fill,
children: [RectangleHitbox()],
);
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
removeFromParent();
if (game.world.children.query<Brick>().length == 1) {
game.world.removeAll(game.world.children.query<Ball>());
game.world.removeAll(game.world.children.query<Bat>());
}
}
}
अब तक, इस कोड के ज़्यादातर हिस्से से जुड़ी जानकारी मिल चुकी होगी. यह कोड, RectangleComponent
का इस्तेमाल करता है. इसमें कॉम्पोनेंट ट्री के सबसे ऊपर, टक्कर का पता लगाने की सुविधा और BrickBreaker
गेम का टाइप-सेफ़ रेफ़रंस होता है.
इस कोड की ओर से पेश किया जाने वाला सबसे अहम नया सिद्धांत यह है कि खिलाड़ी जीत की शर्त कैसे हासिल करता है. जीत की स्थिति की जांच के बाद, दुनिया भर से ईंटों के बारे में पता चलता है और यह पक्का हो जाता है कि सिर्फ़ एक ईंट बची है. यह थोड़ा भ्रामक हो सकता है, क्योंकि पिछली पंक्ति इस ईंट को अपने मूल स्थान से निकाल देती है.
मुख्य बात यह है कि कॉम्पोनेंट हटाने के लिए, सूची में एक निर्देश दिया जाता है. यह कोड इस कोड के चलने के बाद और गेम की अगली टिक से पहले ब्रिक को हटा देता है.
Brick
कॉम्पोनेंट को BrickBreaker
का ऐक्सेस देने के लिए, lib/src/components/components.dart
में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/components.dart
export 'ball.dart';
export 'bat.dart';
export 'brick.dart'; // Add this export
export 'play_area.dart';
दुनिया को ईंटों से दिखाएं
Ball
कॉम्पोनेंट को अपडेट करने के लिए, यहां दिया गया तरीका अपनाएं.
lib/src/components/ball.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import 'bat.dart';
import 'brick.dart'; // Add this import
import 'play_area.dart';
class Ball extends CircleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Ball({
required this.velocity,
required super.position,
required double radius,
required this.difficultyModifier, // Add this parameter
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill,
children: [CircleHitbox()]);
final Vector2 velocity;
final double difficultyModifier; // Add this member
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is PlayArea) {
if (intersectionPoints.first.y <= 0) {
velocity.y = -velocity.y;
} else if (intersectionPoints.first.x <= 0) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.x >= game.width) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.y >= game.height) {
add(RemoveEffect(
delay: 0.35,
));
}
} else if (other is Bat) {
velocity.y = -velocity.y;
velocity.x = velocity.x +
(position.x - other.position.x) / other.size.x * game.width * 0.3;
} else if (other is Brick) { // Modify from here...
if (position.y < other.position.y - other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.y > other.position.y + other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.x < other.position.x) {
velocity.x = -velocity.x;
} else if (position.x > other.position.x) {
velocity.x = -velocity.x;
}
velocity.setFrom(velocity * difficultyModifier); // To here.
}
}
}
इससे सिर्फ़ एक नए पहलू के बारे में पता चलता है. कठिनाई से जुड़ी कार्रवाई करने वाला ऐसा टूल जो ईंट से हर एक टकराने के बाद, बॉल की वेलोसिटी को बढ़ाता है. इस ट्यून किए जा सकने वाले पैरामीटर को टेस्ट करना ज़रूरी है, ताकि आपके गेम के हिसाब से सही कठिनाई का कर्व का पता लगाया जा सके.
BrickBreaker
गेम में नीचे बताए गए तरीके से बदलाव करें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'components/components.dart';
import 'config.dart';
class BrickBreaker extends FlameGame
with HasCollisionDetection, KeyboardEvents {
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final rand = math.Random();
double get width => size.x;
double get height => size.y;
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
world.add(Ball(
difficultyModifier: difficultyModifier, // Add this argument
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
world.add(Bat(
size: Vector2(batWidth, batHeight),
cornerRadius: const Radius.circular(ballRadius / 2),
position: Vector2(width / 2, height * 0.95)));
await world.addAll([ // Add from here...
for (var i = 0; i < brickColors.length; i++)
for (var j = 1; j <= 5; j++)
Brick(
position: Vector2(
(i + 0.5) * brickWidth + (i + 1) * brickGutter,
(j + 2.0) * brickHeight + j * brickGutter,
),
color: brickColors[i],
),
]); // To here.
debugMode = true;
}
@override
KeyEventResult onKeyEvent(
KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
super.onKeyEvent(event, keysPressed);
switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft:
world.children.query<Bat>().first.moveBy(-batStep);
case LogicalKeyboardKey.arrowRight:
world.children.query<Bat>().first.moveBy(batStep);
}
return KeyEventResult.handled;
}
}
अगर मौजूदा समय में गेम को पहले की तरह खेला जाता है, तो गेम की सभी मुख्य तकनीक दिखाई जाती हैं. आप डीबग करने की सुविधा को बंद कर सकते हैं और इसे 'हो गया' कह सकते हैं, लेकिन इसमें कुछ कमी लग रही है.
वेलकम स्क्रीन, ओवर स्क्रीन गेम, और स्कोर के बारे में आपका क्या ख्याल है? Flutter में इन सुविधाओं को गेम में जोड़ा जा सकता है. ऐसा करने पर, आपको इन सुविधाओं को बेहतर बनाने में मदद मिलेगी.
9. गेम जीतें
Play की स्थितियां जोड़ें
इस स्टेप में, आपको Flame गेम को Flutter रैपर में एम्बेड करना होगा. इसके बाद, वेलकम, गेम ओवर, और जीती गई स्क्रीन के लिए, Flutter ओवरले जोड़ना होगा.
सबसे पहले, गेम और उसके कॉम्पोनेंट की फ़ाइलों में बदलाव किया जाता है, ताकि उन्हें प्ले की स्थिति के तौर पर लागू किया जा सके. इससे यह पता चलेगा कि ओवरले दिखाना है या नहीं. अगर है, तो किसकी.
BrickBreaker
गेम में इस तरीके से बदलाव करें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'components/components.dart';
import 'config.dart';
enum PlayState { welcome, playing, gameOver, won } // Add this enumeration
class BrickBreaker extends FlameGame
with HasCollisionDetection, KeyboardEvents, TapDetector { // Modify this line
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final rand = math.Random();
double get width => size.x;
double get height => size.y;
late PlayState _playState; // Add from here...
PlayState get playState => _playState;
set playState(PlayState playState) {
_playState = playState;
switch (playState) {
case PlayState.welcome:
case PlayState.gameOver:
case PlayState.won:
overlays.add(playState.name);
case PlayState.playing:
overlays.remove(PlayState.welcome.name);
overlays.remove(PlayState.gameOver.name);
overlays.remove(PlayState.won.name);
}
} // To here.
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
playState = PlayState.welcome; // Add from here...
}
void startGame() {
if (playState == PlayState.playing) return;
world.removeAll(world.children.query<Ball>());
world.removeAll(world.children.query<Bat>());
world.removeAll(world.children.query<Brick>());
playState = PlayState.playing; // To here.
world.add(Ball(
difficultyModifier: difficultyModifier,
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
world.add(Bat(
size: Vector2(batWidth, batHeight),
cornerRadius: const Radius.circular(ballRadius / 2),
position: Vector2(width / 2, height * 0.95)));
world.addAll([ // Drop the await
for (var i = 0; i < brickColors.length; i++)
for (var j = 1; j <= 5; j++)
Brick(
position: Vector2(
(i + 0.5) * brickWidth + (i + 1) * brickGutter,
(j + 2.0) * brickHeight + j * brickGutter,
),
color: brickColors[i],
),
]);
} // Drop the debugMode
@override // Add from here...
void onTap() {
super.onTap();
startGame();
} // To here.
@override
KeyEventResult onKeyEvent(
KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
super.onKeyEvent(event, keysPressed);
switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft:
world.children.query<Bat>().first.moveBy(-batStep);
case LogicalKeyboardKey.arrowRight:
world.children.query<Bat>().first.moveBy(batStep);
case LogicalKeyboardKey.space: // Add from here...
case LogicalKeyboardKey.enter:
startGame(); // To here.
}
return KeyEventResult.handled;
}
@override
Color backgroundColor() => const Color(0xfff2e8cf); // Add this override
}
यह कोड BrickBreaker
गेम की एक बड़ी डील को बदल देता है. playState
की गणना करने में बहुत मेहनत लगती है. इससे पता चलता है कि खिलाड़ी किस लेवल पर पहुंच रहा है, खेल रहा है, और गेम में हार या जीत हासिल कर रहा है. फ़ाइल के सबसे ऊपर, एन्युमरेशन तय किया जाता है. इसके बाद, उसे छिपी हुई स्थिति के तौर पर इंस्टैंशिएट करके, उससे मेल खाने वाले गैटर और सेटर की मदद ली जाती है. जब गेम के अलग-अलग हिस्से, गेम की स्थिति में बदलाव करते हैं, तब ये गैटर और सेटर ओवरले में बदलाव करने की सुविधा देते हैं.
इसके बाद, आपने onLoad
में मौजूद कोड को onLoad और नए startGame
तरीके में बांटा. इस बदलाव से पहले, गेम को रीस्टार्ट करके सिर्फ़ नया गेम शुरू किया जा सकता था. इन सुविधाओं के साथ, खिलाड़ी अब इन मुश्किल तरीकों के बिना भी नया गेम शुरू कर सकता है.
खिलाड़ी को नया गेम शुरू करने की अनुमति देने के लिए, आपने गेम के लिए दो नए हैंडलर कॉन्फ़िगर किए हैं. आपने एक टैप हैंडलर जोड़ा और कीबोर्ड हैंडलर को बढ़ाया है, ताकि उपयोगकर्ता कई मोडलिटी में नया गेम शुरू कर सके. खेलने की स्थिति के मॉडल के साथ, खिलाड़ी के जीतने या हारने पर, खेलने की स्थिति के ट्रांज़िशन को ट्रिगर करने के लिए कॉम्पोनेंट को अपडेट करना ज़रूरी होता है.
Ball
कॉम्पोनेंट में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/ball.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import 'bat.dart';
import 'brick.dart';
import 'play_area.dart';
class Ball extends CircleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Ball({
required this.velocity,
required super.position,
required double radius,
required this.difficultyModifier,
}) : super(
radius: radius,
anchor: Anchor.center,
paint: Paint()
..color = const Color(0xff1e6091)
..style = PaintingStyle.fill,
children: [CircleHitbox()]);
final Vector2 velocity;
final double difficultyModifier;
@override
void update(double dt) {
super.update(dt);
position += velocity * dt;
}
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
if (other is PlayArea) {
if (intersectionPoints.first.y <= 0) {
velocity.y = -velocity.y;
} else if (intersectionPoints.first.x <= 0) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.x >= game.width) {
velocity.x = -velocity.x;
} else if (intersectionPoints.first.y >= game.height) {
add(RemoveEffect(
delay: 0.35,
onComplete: () { // Modify from here
game.playState = PlayState.gameOver;
})); // To here.
}
} else if (other is Bat) {
velocity.y = -velocity.y;
velocity.x = velocity.x +
(position.x - other.position.x) / other.size.x * game.width * 0.3;
} else if (other is Brick) {
if (position.y < other.position.y - other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.y > other.position.y + other.size.y / 2) {
velocity.y = -velocity.y;
} else if (position.x < other.position.x) {
velocity.x = -velocity.x;
} else if (position.x > other.position.x) {
velocity.x = -velocity.x;
}
velocity.setFrom(velocity * difficultyModifier);
}
}
}
इस छोटे से बदलाव से RemoveEffect
में onComplete
कॉलबैक जुड़ जाता है, जो gameOver
प्ले स्टेट को ट्रिगर करता है. यह सही लगता है अगर खिलाड़ी बॉल को स्क्रीन के नीचे तक जाने देता है.
Brick
कॉम्पोनेंट में नीचे बताए गए तरीके से बदलाव करें.
lib/src/components/brick.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import '../config.dart';
import 'ball.dart';
import 'bat.dart';
class Brick extends RectangleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Brick({required super.position, required Color color})
: super(
size: Vector2(brickWidth, brickHeight),
anchor: Anchor.center,
paint: Paint()
..color = color
..style = PaintingStyle.fill,
children: [RectangleHitbox()],
);
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
removeFromParent();
if (game.world.children.query<Brick>().length == 1) {
game.playState = PlayState.won; // Add this line
game.world.removeAll(game.world.children.query<Ball>());
game.world.removeAll(game.world.children.query<Bat>());
}
}
}
वहीं दूसरी ओर, अगर खिलाड़ी सभी ईंटें तोड़ सकता है, तो इसका मतलब है कि उसने "गेम जीता" है स्क्रीन. बहुत अच्छे खिलाड़ी, बहुत अच्छे!
Flutter रैपर जोड़ें
गेम को एम्बेड करने और प्ले स्टेट ओवरले जोड़ने के लिए, Flutter शेल जोड़ें.
lib/src
में एकwidgets
डायरेक्ट्री बनाएं.- कोई
game_app.dart
फ़ाइल जोड़ें और उस फ़ाइल में यह कॉन्टेंट शामिल करें.
lib/src/widgets/game_app.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../brick_breaker.dart';
import '../config.dart';
class GameApp extends StatelessWidget {
const GameApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
textTheme: GoogleFonts.pressStart2pTextTheme().apply(
bodyColor: const Color(0xff184e77),
displayColor: const Color(0xff184e77),
),
),
home: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffa9d6e5),
Color(0xfff2e8cf),
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: FittedBox(
child: SizedBox(
width: gameWidth,
height: gameHeight,
child: GameWidget.controlled(
gameFactory: BrickBreaker.new,
overlayBuilderMap: {
PlayState.welcome.name: (context, game) => Center(
child: Text(
'TAP TO PLAY',
style:
Theme.of(context).textTheme.headlineLarge,
),
),
PlayState.gameOver.name: (context, game) => Center(
child: Text(
'G A M E O V E R',
style:
Theme.of(context).textTheme.headlineLarge,
),
),
PlayState.won.name: (context, game) => Center(
child: Text(
'Y O U W O N ! ! !',
style:
Theme.of(context).textTheme.headlineLarge,
),
),
},
),
),
),
),
),
),
),
),
);
}
}
इस फ़ाइल का ज़्यादातर कॉन्टेंट, Flutter विजेट ट्री बिल्ड के बाद मिलता है. फ़्लेम के खास पार्ट में, BrickBreaker
गेम इंस्टेंस को बनाने और मैनेज करने के लिए, GameWidget.controlled
का इस्तेमाल किया जाता है. साथ ही, GameWidget
के लिए नया overlayBuilderMap
आर्ग्युमेंट बनाया गया है.
इस overlayBuilderMap
की कुंजियां उन ओवरले के साथ अलाइन होनी चाहिए जिन्हें BrickBreaker
में playState
सेटर ने जोड़ा या हटाया है. जो ओवरले इस मैप में नहीं है उसे सेट करने का प्रयास करने पर चारों ओर दुखी चेहरे दिखाई देते हैं.
- स्क्रीन पर यह नई सुविधा पाने के लिए,
lib/main.dart
फ़ाइल को इस कॉन्टेंट से बदलें.
lib/main.dart
import 'package:flutter/material.dart';
import 'src/widgets/game_app.dart';
void main() {
runApp(const GameApp());
}
अगर इस कोड को iOS, Linux, Windows या वेब पर चलाया जाता है, तो गेम में इसके हिसाब से आउटपुट दिखता है. अगर macOS या Android को टारगेट किया जाता है, तो google_fonts
को दिखाने के लिए आपको आखिरी बार एक ट्वीक करना होगा.
फ़ॉन्ट का ऐक्सेस चालू करना
Android के लिए इंटरनेट की अनुमति जोड़ें
Android के लिए, आपको इंटरनेट की अनुमति देनी होगी. अपने AndroidManifest.xml
में दिए गए तरीके का इस्तेमाल करें.
android/app/src/main/AndroidManifest.xml
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- Add the following line -->
<uses-permission android:name="android.permission.INTERNET" />
<application
android:label="brick_breaker"
android:name="${applicationName}"
android:icon="@mipmap/ic_launcher">
<activity
android:name=".MainActivity"
android:exported="true"
android:launchMode="singleTop"
android:taskAffinity=""
android:theme="@style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="@style/NormalTheme"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
<!-- Required to query activities that can process text, see:
https://developer.android.com/training/package-visibility and
https://developer.android.com/reference/android/content/Intent#ACTION_PROCESS_TEXT.
In particular, this is used by the Flutter engine in io.flutter.plugin.text.ProcessTextPlugin. -->
<queries>
<intent>
<action android:name="android.intent.action.PROCESS_TEXT"/>
<data android:mimeType="text/plain"/>
</intent>
</queries>
</manifest>
macOS के लिए एनटाइटलमेंट फ़ाइलों में बदलाव करना
macOS के लिए, आपके पास बदलाव करने के लिए दो फ़ाइलें होती हैं.
DebugProfile.entitlements
फ़ाइल में बदलाव करें, ताकि वह नीचे दिए गए कोड से मेल खाए.
macos/Runner/DebugProfile.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<key>com.apple.security.cs.allow-jit</key>
<true/>
<key>com.apple.security.network.server</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- to here. -->
</dict>
</plist>
Release.entitlements
फ़ाइल में बदलाव करें, ताकि वह नीचे दिए गए कोड से मेल खाए
macos/Runner/Release.entitlements
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>com.apple.security.app-sandbox</key>
<true/>
<!-- Add from here... -->
<key>com.apple.security.network.client</key>
<true/>
<!-- to here. -->
</dict>
</plist>
इस मोड को चालू करने पर, सभी प्लैटफ़ॉर्म पर वेलकम स्क्रीन और गेम खत्म होने वाली या जीती गई स्क्रीन दिखेगी. वे स्क्रीन थोड़ी सामान्य हो सकती हैं और स्कोर पाना अच्छा होगा. इसलिए, यह अनुमान लगाएं कि आप अगले चरण में क्या करने वाले हैं!
10. स्कोर बनाए रखें
गेम में स्कोर जोड़ें
इस चरण में, Flutter के कॉन्टेक्स्ट के साथ गेम के स्कोर को सार्वजनिक किया जाता है. इस चरण में, आपको फ़्लेम गेम से लेकर, आस-पास के Flutter स्टेट मैनेजमेंट तक स्थिति के बारे में पता चलता है. इससे, जब भी खिलाड़ी ब्रिक तोड़ता है, तो गेम कोड स्कोर को अपडेट कर पाता है.
BrickBreaker
गेम में इस तरीके से बदलाव करें.
lib/src/brick_breaker.dart
import 'dart:async';
import 'dart:math' as math;
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'components/components.dart';
import 'config.dart';
enum PlayState { welcome, playing, gameOver, won }
class BrickBreaker extends FlameGame
with HasCollisionDetection, KeyboardEvents, TapDetector {
BrickBreaker()
: super(
camera: CameraComponent.withFixedResolution(
width: gameWidth,
height: gameHeight,
),
);
final ValueNotifier<int> score = ValueNotifier(0); // Add this line
final rand = math.Random();
double get width => size.x;
double get height => size.y;
late PlayState _playState;
PlayState get playState => _playState;
set playState(PlayState playState) {
_playState = playState;
switch (playState) {
case PlayState.welcome:
case PlayState.gameOver:
case PlayState.won:
overlays.add(playState.name);
case PlayState.playing:
overlays.remove(PlayState.welcome.name);
overlays.remove(PlayState.gameOver.name);
overlays.remove(PlayState.won.name);
}
}
@override
FutureOr<void> onLoad() async {
super.onLoad();
camera.viewfinder.anchor = Anchor.topLeft;
world.add(PlayArea());
playState = PlayState.welcome;
}
void startGame() {
if (playState == PlayState.playing) return;
world.removeAll(world.children.query<Ball>());
world.removeAll(world.children.query<Bat>());
world.removeAll(world.children.query<Brick>());
playState = PlayState.playing;
score.value = 0; // Add this line
world.add(Ball(
difficultyModifier: difficultyModifier,
radius: ballRadius,
position: size / 2,
velocity: Vector2((rand.nextDouble() - 0.5) * width, height * 0.2)
.normalized()
..scale(height / 4)));
world.add(Bat(
size: Vector2(batWidth, batHeight),
cornerRadius: const Radius.circular(ballRadius / 2),
position: Vector2(width / 2, height * 0.95)));
world.addAll([
for (var i = 0; i < brickColors.length; i++)
for (var j = 1; j <= 5; j++)
Brick(
position: Vector2(
(i + 0.5) * brickWidth + (i + 1) * brickGutter,
(j + 2.0) * brickHeight + j * brickGutter,
),
color: brickColors[i],
),
]);
}
@override
void onTap() {
super.onTap();
startGame();
}
@override
KeyEventResult onKeyEvent(
KeyEvent event, Set<LogicalKeyboardKey> keysPressed) {
super.onKeyEvent(event, keysPressed);
switch (event.logicalKey) {
case LogicalKeyboardKey.arrowLeft:
world.children.query<Bat>().first.moveBy(-batStep);
case LogicalKeyboardKey.arrowRight:
world.children.query<Bat>().first.moveBy(batStep);
case LogicalKeyboardKey.space:
case LogicalKeyboardKey.enter:
startGame();
}
return KeyEventResult.handled;
}
@override
Color backgroundColor() => const Color(0xfff2e8cf);
}
score
को गेम में जोड़ने पर, गेम की स्थिति को Flutter के स्टेट मैनेजमेंट से जोड़ दिया जाता है.
- अगर कोई खिलाड़ी ब्रिक्स तोड़ता है, तो स्कोर में पॉइंट जोड़ने के लिए,
Brick
क्लास में बदलाव करें.
lib/src/components/brick.dart
import 'package:flame/collisions.dart';
import 'package:flame/components.dart';
import 'package:flutter/material.dart';
import '../brick_breaker.dart';
import '../config.dart';
import 'ball.dart';
import 'bat.dart';
class Brick extends RectangleComponent
with CollisionCallbacks, HasGameReference<BrickBreaker> {
Brick({required super.position, required Color color})
: super(
size: Vector2(brickWidth, brickHeight),
anchor: Anchor.center,
paint: Paint()
..color = color
..style = PaintingStyle.fill,
children: [RectangleHitbox()],
);
@override
void onCollisionStart(
Set<Vector2> intersectionPoints, PositionComponent other) {
super.onCollisionStart(intersectionPoints, other);
removeFromParent();
game.score.value++; // Add this line
if (game.world.children.query<Brick>().length == 1) {
game.playState = PlayState.won;
game.world.removeAll(game.world.children.query<Ball>());
game.world.removeAll(game.world.children.query<Bat>());
}
}
}
एक आकर्षक गेम बनाएं
अब आप Flutter में स्कोर बनाए रख सकते हैं. इसलिए, अब समय आ गया है कि सभी विजेट एक साथ रखें, ताकि यह अच्छा दिखे.
lib/src/widgets
मेंscore_card.dart
बनाएं और इन्हें जोड़ें.
lib/src/widgets/score_card.dart
import 'package:flutter/material.dart';
class ScoreCard extends StatelessWidget {
const ScoreCard({
super.key,
required this.score,
});
final ValueNotifier<int> score;
@override
Widget build(BuildContext context) {
return ValueListenableBuilder<int>(
valueListenable: score,
builder: (context, score, child) {
return Padding(
padding: const EdgeInsets.fromLTRB(12, 6, 12, 18),
child: Text(
'Score: $score'.toUpperCase(),
style: Theme.of(context).textTheme.titleLarge!,
),
);
},
);
}
}
lib/src/widgets
मेंoverlay_screen.dart
बनाएं और यह कोड जोड़ें.
इससे ओवरले स्क्रीन पर मूवमेंट और स्टाइल जोड़ने के लिए, flutter_animate
पैकेज की पावर का इस्तेमाल करके ओवरले को ज़्यादा बेहतर बनाया जा सकता है.
lib/src/widgets/overlay_screen.dart
import 'package:flutter/material.dart';
import 'package:flutter_animate/flutter_animate.dart';
class OverlayScreen extends StatelessWidget {
const OverlayScreen({
super.key,
required this.title,
required this.subtitle,
});
final String title;
final String subtitle;
@override
Widget build(BuildContext context) {
return Container(
alignment: const Alignment(0, -0.15),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
title,
style: Theme.of(context).textTheme.headlineLarge,
).animate().slideY(duration: 750.ms, begin: -3, end: 0),
const SizedBox(height: 16),
Text(
subtitle,
style: Theme.of(context).textTheme.headlineSmall,
)
.animate(onPlay: (controller) => controller.repeat())
.fadeIn(duration: 1.seconds)
.then()
.fadeOut(duration: 1.seconds),
],
),
);
}
}
flutter_animate
की सुविधाओं के बारे में ज़्यादा जानने के लिए, Flutter में अगली-पीढ़ी की टेक्नोलॉजी के यूज़र इंटरफ़ेस (यूआई) बनाना कोडलैब (कोड बनाना सीखना) देखें.
यह कोड GameApp
कॉम्पोनेंट में काफ़ी बदल गया है. सबसे पहले, score
को ऐक्सेस करने के लिए ScoreCard
को चालू करने के लिए , आपको इसे StatelessWidget
से StatefulWidget
में बदलना होगा. स्कोर कार्ड जोड़ने के लिए गेम के ऊपर स्कोर को स्टैक करने के लिए Column
जोड़ना ज़रूरी है.
दूसरा, वेलकम, गेम ओवर, और जीतने वाले अनुभवों को बेहतर बनाने के लिए, आपने नया OverlayScreen
विजेट जोड़ा है.
lib/src/widgets/game_app.dart
import 'package:flame/game.dart';
import 'package:flutter/material.dart';
import 'package:google_fonts/google_fonts.dart';
import '../brick_breaker.dart';
import '../config.dart';
import 'overlay_screen.dart'; // Add this import
import 'score_card.dart'; // And this one too
class GameApp extends StatefulWidget { // Modify this line
const GameApp({super.key});
@override // Add from here...
State<GameApp> createState() => _GameAppState();
}
class _GameAppState extends State<GameApp> {
late final BrickBreaker game;
@override
void initState() {
super.initState();
game = BrickBreaker();
} // To here.
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
theme: ThemeData(
useMaterial3: true,
textTheme: GoogleFonts.pressStart2pTextTheme().apply(
bodyColor: const Color(0xff184e77),
displayColor: const Color(0xff184e77),
),
),
home: Scaffold(
body: Container(
decoration: const BoxDecoration(
gradient: LinearGradient(
begin: Alignment.topCenter,
end: Alignment.bottomCenter,
colors: [
Color(0xffa9d6e5),
Color(0xfff2e8cf),
],
),
),
child: SafeArea(
child: Padding(
padding: const EdgeInsets.all(16),
child: Center(
child: Column( // Modify from here...
children: [
ScoreCard(score: game.score),
Expanded(
child: FittedBox(
child: SizedBox(
width: gameWidth,
height: gameHeight,
child: GameWidget(
game: game,
overlayBuilderMap: {
PlayState.welcome.name: (context, game) =>
const OverlayScreen(
title: 'TAP TO PLAY',
subtitle: 'Use arrow keys or swipe',
),
PlayState.gameOver.name: (context, game) =>
const OverlayScreen(
title: 'G A M E O V E R',
subtitle: 'Tap to Play Again',
),
PlayState.won.name: (context, game) =>
const OverlayScreen(
title: 'Y O U W O N ! ! !',
subtitle: 'Tap to Play Again',
),
},
),
),
),
),
],
), // To here.
),
),
),
),
),
);
}
}
ये सभी चीज़ें हो जाने के बाद, अब इस गेम को Flutter के छह टारगेट प्लैटफ़ॉर्म में से किसी भी प्लैटफ़ॉर्म पर चलाया जा सकता है. गेम इससे मिलता-जुलता होना चाहिए.
11. बधाई हो
बधाई हो, आपने Flutter और Flame के साथ गेम बनाने में कामयाब रहे!
आपने Fire 2D गेम इंजन का इस्तेमाल करके गेम बनाया है और उसे Flutter रैपर में एम्बेड किया है. आपने कॉम्पोनेंट को ऐनिमेट और हटाने के लिए, फ़्लेम इफ़ेक्ट का इस्तेमाल किया है. पूरे गेम को अच्छी तरह से डिज़ाइन करने के लिए आपने Google Fonts and Flutter Animate पैकेज का इस्तेमाल किया है.
आगे क्या करना है?
इनमें से कुछ कोडलैब देखें...
- Flutter में अगली-पीढ़ी की टेक्नोलॉजी के यूआई बनाना
- Flutter ऐप्लिकेशन को बोरिंग से खूबसूरत बनाएं
- अपने Flutter ऐप्लिकेशन में इन-ऐप्लिकेशन खरीदारी जोड़ना