1. ভূমিকা
শিখা হল একটি ফ্লটার-ভিত্তিক 2D গেম ইঞ্জিন। এই কোডল্যাবে, আপনি 70 এর দশকের ভিডিও গেমের একটি ক্লাসিক স্টিভ ওজনিয়াকের ব্রেকআউট দ্বারা অনুপ্রাণিত হয়ে একটি গেম তৈরি করবেন। আপনি ব্যাট, বল এবং ইট আঁকতে শিখার উপাদান ব্যবহার করবেন। আপনি ব্যাটের নড়াচড়াকে অ্যানিমেট করতে ফ্লেমের প্রভাবগুলি ব্যবহার করবেন এবং ফ্লাটারের স্টেট ম্যানেজমেন্ট সিস্টেমের সাথে শিখাকে কীভাবে একীভূত করবেন তা দেখতে পাবেন।
সম্পূর্ণ হয়ে গেলে, আপনার গেমটি এই অ্যানিমেটেড জিআইএফের মতো হওয়া উচিত, যদিও একটু ধীর।
আপনি কি শিখবেন
- কিভাবে শিখার মূল বিষয়গুলি কাজ করে,
GameWidget
দিয়ে শুরু করে। - কিভাবে একটি গেম লুপ ব্যবহার করতে হয়।
- শিখার
Component
কিভাবে কাজ করে। তারা Flutter'sWidget
এর মত। - সংঘর্ষগুলি কীভাবে পরিচালনা করবেন।
-
Component
s অ্যানিমেট করার জন্যEffect
s কিভাবে ব্যবহার করবেন। - কিভাবে একটি ফ্ল্যাম গেমের উপরে Flutter
Widget
s ওভারলে করবেন। - ফ্লাটারের স্টেট ম্যানেজমেন্টের সাথে শিখাকে কীভাবে একীভূত করা যায়।
আপনি কি নির্মাণ করবেন
এই কোডল্যাবে, আপনি ফ্লটার এবং ফ্লেম ব্যবহার করে একটি 2D গেম তৈরি করতে যাচ্ছেন। সম্পূর্ণ হলে, আপনার গেমটি নিম্নলিখিত প্রয়োজনীয়তাগুলি পূরণ করবে
- Flutter সমর্থন করে এমন ছয়টি প্ল্যাটফর্মে ফাংশন: Android, iOS, Linux, macOS, Windows এবং ওয়েব
- Flame এর গেম লুপ ব্যবহার করে কমপক্ষে 60 fps বজায় রাখুন।
- 80-এর দশকের আর্কেড গেমিংয়ের অনুভূতি পুনরায় তৈরি করতে
google_fonts
প্যাকেজ এবংflutter_animate
এর মতো Flutter ক্ষমতাগুলি ব্যবহার করুন৷
2. আপনার ফ্লটার পরিবেশ সেট আপ করুন
সম্পাদক
এই কোডল্যাবটিকে সহজ করার জন্য, এটি অনুমান করে যে ভিজ্যুয়াল স্টুডিও কোড (ভিএস কোড) আপনার বিকাশের পরিবেশ। ভিএস কোড বিনামূল্যে এবং সমস্ত প্রধান প্ল্যাটফর্মে কাজ করে। আমরা এই কোডল্যাবের জন্য VS কোড ব্যবহার করি কারণ নির্দেশাবলী ডিফল্ট VS কোড-নির্দিষ্ট শর্টকাট। কাজগুলি আরও সোজা হয়ে যায়: "এক্স করতে আপনার এডিটরে উপযুক্ত ক্রিয়া করুন" এর পরিবর্তে "এই বোতামটি ক্লিক করুন" বা "এক্স করতে এই কী টিপুন"।
আপনি আপনার পছন্দের যেকোনো সম্পাদক ব্যবহার করতে পারেন: Android Studio, অন্যান্য IntelliJ IDEs, Emacs, Vim, অথবা Notepad++। তারা সবাই ফ্লটারের সাথে কাজ করে।
একটি উন্নয়ন লক্ষ্য নির্বাচন করুন
ফ্লাটার একাধিক প্ল্যাটফর্মের জন্য অ্যাপ তৈরি করে। আপনার অ্যাপটি নিচের যেকোনো অপারেটিং সিস্টেমে চলতে পারে:
- iOS
- অ্যান্ড্রয়েড
- উইন্ডোজ
- macOS
- লিনাক্স
- ওয়েব
আপনার বিকাশের লক্ষ্য হিসাবে একটি অপারেটিং সিস্টেম বেছে নেওয়া সাধারণ অভ্যাস। এটি সেই অপারেটিং সিস্টেম যা আপনার অ্যাপ বিকাশের সময় চলে।
উদাহরণস্বরূপ: বলুন আপনি আপনার ফ্লাটার অ্যাপটি বিকাশ করতে একটি উইন্ডোজ ল্যাপটপ ব্যবহার করছেন৷ তারপরে আপনি আপনার ডেভেলপমেন্ট টার্গেট হিসেবে অ্যান্ড্রয়েড বেছে নিন। আপনার অ্যাপের পূর্বরূপ দেখতে, আপনি একটি USB কেবল দিয়ে আপনার Windows ল্যাপটপে একটি Android ডিভাইস সংযুক্ত করুন এবং আপনার অ্যাপ-ইন-ডেভেলপমেন্ট সেই সংযুক্ত Android ডিভাইসে বা একটি Android এমুলেটরে চলে। আপনি উইন্ডোজকে ডেভেলপমেন্ট টার্গেট হিসেবে বেছে নিতে পারতেন, যা আপনার এডিটরের পাশাপাশি উইন্ডোজ অ্যাপ হিসেবে আপনার অ্যাপ-ইন-ডেভেলপমেন্ট চালায়।
আপনি আপনার ডেভেলপমেন্ট টার্গেট হিসাবে ওয়েব বেছে নিতে প্রলুব্ধ হতে পারেন। বিকাশের সময় এটির একটি খারাপ দিক রয়েছে: আপনি ফ্লটারের স্টেটফুল হট রিলোড ক্ষমতা হারাবেন। ফ্লটার বর্তমানে ওয়েব অ্যাপ্লিকেশনগুলিকে হট-রিলোড করতে পারে না৷
চালিয়ে যাওয়ার আগে আপনার পছন্দ করুন। আপনি পরে সবসময় অন্য অপারেটিং সিস্টেমে আপনার অ্যাপ চালাতে পারেন। একটি উন্নয়ন লক্ষ্য নির্বাচন পরবর্তী ধাপ মসৃণ করে তোলে।
Flutter ইনস্টল করুন
Flutter SDK ইনস্টল করার সবচেয়ে আপ-টু-ডেট নির্দেশাবলী docs.flutter.dev এ পাওয়া যাবে।
Flutter ওয়েবসাইটের নির্দেশাবলী SDK এর ইনস্টলেশন এবং ডেভেলপমেন্ট টার্গেট-সম্পর্কিত টুল এবং এডিটর প্লাগইনগুলিকে কভার করে। এই কোডল্যাবের জন্য, নিম্নলিখিত সফ্টওয়্যারটি ইনস্টল করুন:
- ফ্লটার SDK
- ফ্লাটার প্লাগইন সহ ভিজ্যুয়াল স্টুডিও কোড
- আপনার নির্বাচিত উন্নয়ন লক্ষ্যের জন্য কম্পাইলার সফ্টওয়্যার। (মাকওএস বা আইওএসকে টার্গেট করতে আপনার উইন্ডোজ বা এক্সকোড টার্গেট করতে ভিজ্যুয়াল স্টুডিও প্রয়োজন)
পরবর্তী বিভাগে, আপনি আপনার প্রথম ফ্লাটার প্রকল্প তৈরি করবেন।
আপনার যদি কোনো সমস্যা সমাধানের প্রয়োজন হয়, তাহলে আপনি এই প্রশ্ন ও উত্তরগুলির কিছু খুঁজে পেতে পারেন (স্ট্যাকওভারফ্লো থেকে) সমস্যা সমাধানের জন্য সহায়ক।
প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী
- আমি কিভাবে ফ্লটার SDK পাথ খুঁজে পাব?
- Flutter কমান্ড পাওয়া না গেলে আমি কি করব?
- আমি কিভাবে "স্টার্টআপ লক রিলিজ করার জন্য অন্য ফ্লটার কমান্ডের জন্য অপেক্ষা করছি" সমস্যাটি ঠিক করব?
- আমার অ্যান্ড্রয়েড এসডিকে ইনস্টলেশন কোথায় আছে তা আমি কীভাবে ফ্লটারকে বলব?
-
flutter doctor --android-licenses
চালানোর সময় আমি কীভাবে জাভা ত্রুটি মোকাবেলা করব? - আমি কিভাবে অ্যান্ড্রয়েড
sdkmanager
টুল খুঁজে পাওয়া যায়নি মোকাবেলা করব? - কিভাবে আমি "
cmdline-tools
উপাদান অনুপস্থিত" ত্রুটি মোকাবেলা করব? - অ্যাপল সিলিকন (M1) এ আমি কীভাবে কোকোপড চালাব?
- ভিএস কোডে সেভ করার সময় আমি কীভাবে অটো ফরম্যাটিং অক্ষম করব?
3. একটি প্রকল্প তৈরি করুন
আপনার প্রথম ফ্লাটার প্রকল্প তৈরি করুন
এতে VS কোড খোলা এবং আপনার বেছে নেওয়া একটি ডিরেক্টরিতে Flutter অ্যাপ টেমপ্লেট তৈরি করা জড়িত।
- ভিজ্যুয়াল স্টুডিও কোড চালু করুন।
- কমান্ড প্যালেট খুলুন (
F1
বাCtrl+Shift+P
বাShift+Cmd+P
) তারপর "flutter new" টাইপ করুন। এটি প্রদর্শিত হলে, Flutter: New Project কমান্ড নির্বাচন করুন।
- খালি অ্যাপ্লিকেশন নির্বাচন করুন। আপনার প্রকল্প তৈরি করতে একটি ডিরেক্টরি নির্বাচন করুন। এটি এমন কোনও ডিরেক্টরি হওয়া উচিত যার জন্য উন্নত সুবিধার প্রয়োজন নেই বা এর পথে কোনও স্থান নেই। উদাহরণগুলির মধ্যে আপনার হোম ডিরেক্টরি বা
C:\src\
অন্তর্ভুক্ত রয়েছে।
- আপনার প্রকল্পের নাম দিন
brick_breaker
। এই কোডল্যাবের অবশিষ্টাংশ অনুমান করে আপনি আপনার অ্যাপের নাম দিয়েছেনbrick_breaker
।
ফ্লটার এখন আপনার প্রোজেক্ট ফোল্ডার তৈরি করে এবং VS কোড এটি খোলে। আপনি এখন অ্যাপের একটি বেসিক স্ক্যাফোল্ড সহ দুটি ফাইলের বিষয়বস্তু ওভাররাইট করবেন।
প্রাথমিক অ্যাপটি কপি এবং পেস্ট করুন
এটি আপনার অ্যাপে এই কোডল্যাবে দেওয়া উদাহরণ কোড যোগ করে।
- VS কোডের বাম প্যানে, এক্সপ্লোরারে ক্লিক করুন এবং
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
ফাইলটি আপনার অ্যাপ সম্পর্কে প্রাথমিক তথ্য নির্দিষ্ট করে, যেমন এর বর্তমান সংস্করণ, এর নির্ভরতা এবং এটি যে সম্পদের সাথে পাঠানো হবে।
-
lib/
ডিরেক্টরিতেmain.dart
ফাইল খুলুন।
- এই ফাইলের বিষয়বস্তু নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন:
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 আছে, Flame এর Component
s আছে। যেখানে ফ্লাটার অ্যাপগুলি উইজেটগুলির গাছ তৈরি করে, ফ্লেম গেমগুলি উপাদানগুলির গাছগুলি বজায় রাখে৷
এতে ফ্লটার এবং ফ্লেমের মধ্যে একটি আকর্ষণীয় পার্থক্য রয়েছে। ফ্লটারের উইজেট ট্রি হল একটি ক্ষণস্থায়ী বর্ণনা যা স্থায়ী এবং পরিবর্তনযোগ্য RenderObject
স্তর আপডেট করার জন্য ব্যবহার করা হয়েছে। শিখার উপাদানগুলি স্থায়ী এবং পরিবর্তনযোগ্য, একটি প্রত্যাশা সহ যে বিকাশকারী এই উপাদানগুলিকে একটি সিমুলেশন সিস্টেমের অংশ হিসাবে ব্যবহার করবে৷
শিখার উপাদানগুলি গেম মেকানিক্স প্রকাশ করার জন্য অপ্টিমাইজ করা হয়। এই কোডল্যাবটি পরবর্তী ধাপে বৈশিষ্ট্যযুক্ত গেম লুপ দিয়ে শুরু হবে।
- বিশৃঙ্খলতা নিয়ন্ত্রণ করতে, এই প্রকল্পের সমস্ত উপাদান ধারণকারী একটি ফাইল যোগ করুন।
lib/src/components
এ একটিcomponents.dart
ফাইল তৈরি করুন এবং নিম্নলিখিত বিষয়বস্তু যোগ করুন।
lib/src/components/components.dart
export 'play_area.dart';
export
নির্দেশিকা import
বিপরীত ভূমিকা পালন করে। এটি অন্য ফাইলে আমদানি করার সময় এই ফাইলটি কী কার্যকারিতা প্রকাশ করে তা ঘোষণা করে। আপনি নিম্নলিখিত ধাপে নতুন উপাদান যোগ করার সাথে সাথে এই ফাইলটি আরও এন্ট্রি বাড়াবে।
একটি শিখা খেলা তৈরি করুন
পূর্ববর্তী ধাপ থেকে লাল স্কুইগলগুলি নিভানোর জন্য, Flame's 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)
এর জন্য নোঙ্গর হিসাবে এলাকার মাঝখানে ব্যবহার করে। -
world
PlayArea
যোগ করে। বিশ্ব খেলা বিশ্বের প্রতিনিধিত্ব করে. এটি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
সংজ্ঞায়িত করেছেন, তাই এটি যুক্তিযুক্ত যে আরও আকার বিদ্যমান। CircleComponent
, যেমন RectangleComponent
, PositionedComponent
থেকে উদ্ভূত হয়, তাই আপনি পর্দায় বলটি স্থাপন করতে পারেন। আরও গুরুত্বপূর্ণ, এর অবস্থান আপডেট করা যেতে পারে।
এই উপাদানটি velocity
ধারণাকে প্রবর্তন করে, বা সময়ের সাথে অবস্থানের পরিবর্তন করে। বেগ হল একটি Vector2
বস্তু কারণ বেগ হল গতি এবং দিক । অবস্থান আপডেট করতে, update
পদ্ধতিটি ওভাররাইড করুন, যা গেম ইঞ্জিন প্রতিটি ফ্রেমের জন্য কল করে। dt
হল পূর্ববর্তী ফ্রেম এবং এই ফ্রেমের মধ্যে সময়কাল। এটি আপনাকে বিভিন্ন ফ্রেম রেট (60hz বা 120hz) বা অত্যধিক গণনার কারণে দীর্ঘ ফ্রেমের মতো বিষয়গুলির সাথে মানিয়ে নিতে সক্ষম করে।
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.
}
}
এই পরিবর্তন world
Ball
উপাদান যোগ করে। ডিসপ্লে এলাকার কেন্দ্রে বলের 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()
একটি operator &
dart:ui
Offset
ক্লাসে ওভারলোডের সুবিধা দেয় যা 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');
}
}
}
এই কোড পরিবর্তন দুটি পৃথক সমস্যা সমাধান.
প্রথমত, এটি পর্দার নীচে স্পর্শ করার মুহুর্তে অস্তিত্ব থেকে বেরিয়ে আসা বলটিকে ঠিক করে। এই সমস্যাটি সমাধান করতে, আপনি RemoveEffect
দিয়ে removeFromParent
কলটি প্রতিস্থাপন করুন। 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;
}
}
আপনি যদি গেমটি বর্তমানে যেমন দাঁড়িয়ে আছে তেমন চালান, এটি সমস্ত মূল গেম মেকানিক্স প্রদর্শন করে। আপনি ডিবাগিং বন্ধ করতে পারেন এবং এটিকে সম্পন্ন বলতে পারেন, কিন্তু কিছু অনুপস্থিত বোধ করে।
একটি স্বাগত স্ক্রীন, একটি গেম ওভার স্ক্রীন এবং সম্ভবত একটি স্কোর সম্পর্কে কী? ফ্লাটার এই বৈশিষ্ট্যগুলিকে গেমটিতে যুক্ত করতে পারে এবং সেখানেই আপনি পরবর্তীতে আপনার দৃষ্টি আকর্ষণ করবেন।
9. খেলা জয়
খেলার অবস্থা যোগ করুন
এই ধাপে, আপনি ফ্লাটার র্যাপারের ভিতরে ফ্ল্যাম গেমটি এম্বেড করুন এবং তারপরে স্বাগত, গেম ওভার এবং জিতে নেওয়া স্ক্রীনের জন্য ফ্লাটার ওভারলে যোগ করুন।
প্রথমত, আপনি একটি প্লে স্টেট বাস্তবায়নের জন্য গেম এবং কম্পোনেন্ট ফাইলগুলি পরিবর্তন করেন যা প্রতিফলিত করে যে একটি ওভারলে দেখাতে হবে কিনা এবং যদি তাই হয়, কোনটি।
-
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
এবং একটি নতুন 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 wrapper যোগ করুন
গেমটি এম্বেড করার জন্য কোথাও প্রদান করতে এবং প্লে স্টেট ওভারলে যোগ করতে, 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,
),
),
},
),
),
),
),
),
),
),
),
);
}
}
এই ফাইলের বেশিরভাগ বিষয়বস্তু একটি আদর্শ ফ্লাটার উইজেট ট্রি বিল্ড অনুসরণ করে। Flame-এর নির্দিষ্ট অংশগুলির মধ্যে রয়েছে 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
প্রদর্শন করতে সক্ষম করতে আপনার একটি শেষ টুইকের প্রয়োজন।
ফন্ট অ্যাক্সেস সক্ষম করা হচ্ছে
অ্যান্ড্রয়েডের জন্য ইন্টারনেট অনুমতি যোগ করুন
অ্যান্ড্রয়েডের জন্য, আপনাকে অবশ্যই ইন্টারনেট অনুমতি যোগ করতে হবে। নিম্নরূপ আপনার 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. স্কোর রাখুন
গেমটিতে স্কোর যোগ করুন
এই ধাপে, আপনি আশেপাশের ফ্লাটার প্রসঙ্গে গেমের স্কোর প্রকাশ করেন। এই ধাপে আপনি ফ্লেম গেম থেকে আশেপাশের ফ্লাটার স্টেট ম্যানেজমেন্টে স্টেট এক্সপোজ করেন। এটি প্রতিবার খেলোয়াড় একটি ইট ভাঙার সময় স্কোর আপডেট করতে গেম কোডকে সক্ষম করে।
-
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
যোগ করার মাধ্যমে, আপনি গেমের অবস্থাকে ফ্লটার স্টেট ম্যানেজমেন্টের সাথে সংযুক্ত করেন।
- প্লেয়ার যখন ইট ভাঙ্গে তখন স্কোরে একটি পয়েন্ট যোগ করতে
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>());
}
}
}
একটি সুদর্শন খেলা করুন
এখন যেহেতু আপনি ফ্লটারে স্কোর রাখতে পারেন, এটি সুন্দর দেখানোর জন্য উইজেটগুলিকে একত্রিত করার সময়।
-
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 কোডল্যাবে বিল্ডিং পরবর্তী প্রজন্মের UIs দেখুন।
এই কোডটি 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.
),
),
),
),
),
);
}
}
এই সমস্ত জায়গায়, আপনি এখন এই গেমটি ছয়টি ফ্লাটার টার্গেট প্ল্যাটফর্মের যে কোনওটিতে চালাতে সক্ষম হবেন। খেলা নিম্নলিখিত অনুরূপ করা উচিত.
11. অভিনন্দন
অভিনন্দন, আপনি ফ্লটার এবং ফ্লেম দিয়ে একটি গেম তৈরি করতে সফল হয়েছেন!
আপনি Flame 2D গেম ইঞ্জিন ব্যবহার করে একটি গেম তৈরি করেছেন এবং এটি একটি Flutter wrapper এ এমবেড করেছেন৷ আপনি উপাদানগুলিকে অ্যানিমেট করতে এবং অপসারণ করতে শিখার প্রভাবগুলি ব্যবহার করেছেন৷ আপনি Google ফন্ট এবং ফ্লটার অ্যানিমেট প্যাকেজ ব্যবহার করেছেন যাতে পুরো গেমটি ভালভাবে ডিজাইন করা হয়।
এরপর কি?
এই কোডল্যাবগুলির কিছু পরীক্ষা করে দেখুন...
- ফ্লটারে পরবর্তী প্রজন্মের UI তৈরি করা
- আপনার ফ্লটার অ্যাপটিকে বিরক্তিকর থেকে সুন্দরের দিকে নিয়ে যান
- আপনার Flutter অ্যাপে অ্যাপ-মধ্যস্থ কেনাকাটা যোগ করা হচ্ছে