ফ্লাটার সহ শিখার পরিচিতি

1. ভূমিকা

শিখা হল একটি ফ্লটার-ভিত্তিক 2D গেম ইঞ্জিন। এই কোডল্যাবে, আপনি 70 এর দশকের ভিডিও গেমের একটি ক্লাসিক স্টিভ ওজনিয়াকের ব্রেকআউট দ্বারা অনুপ্রাণিত হয়ে একটি গেম তৈরি করবেন। আপনি ব্যাট, বল এবং ইট আঁকতে শিখার উপাদান ব্যবহার করবেন। আপনি ব্যাটের নড়াচড়াকে অ্যানিমেট করতে ফ্লেমের প্রভাবগুলি ব্যবহার করবেন এবং ফ্লাটারের স্টেট ম্যানেজমেন্ট সিস্টেমের সাথে শিখাকে কীভাবে একীভূত করবেন তা দেখতে পাবেন।

সম্পূর্ণ হয়ে গেলে, আপনার গেমটি এই অ্যানিমেটেড জিআইএফের মতো হওয়া উচিত, যদিও একটু ধীর।

খেলার একটি স্ক্রীন রেকর্ডিং। গেমটি উল্লেখযোগ্যভাবে গতিশীল হয়েছে।

আপনি কি শিখবেন

  • কিভাবে শিখার মূল বিষয়গুলি কাজ করে, GameWidget দিয়ে শুরু করে।
  • কিভাবে একটি গেম লুপ ব্যবহার করতে হয়।
  • শিখার Component কিভাবে কাজ করে। তারা Flutter's Widget এর মত।
  • সংঘর্ষগুলি কীভাবে পরিচালনা করবেন।
  • 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++। তারা সবাই ফ্লটারের সাথে কাজ করে।

কিছু ফ্লটার কোড সহ VS কোডের একটি স্ক্রিনশট

একটি উন্নয়ন লক্ষ্য নির্বাচন করুন

ফ্লাটার একাধিক প্ল্যাটফর্মের জন্য অ্যাপ তৈরি করে। আপনার অ্যাপটি নিচের যেকোনো অপারেটিং সিস্টেমে চলতে পারে:

  • iOS
  • অ্যান্ড্রয়েড
  • উইন্ডোজ
  • macOS
  • লিনাক্স
  • ওয়েব

আপনার বিকাশের লক্ষ্য হিসাবে একটি অপারেটিং সিস্টেম বেছে নেওয়া সাধারণ অভ্যাস। এটি সেই অপারেটিং সিস্টেম যা আপনার অ্যাপ বিকাশের সময় চলে।

একটি অঙ্কন যা একটি ল্যাপটপ এবং একটি ফোনকে একটি ক্যাবল দ্বারা ল্যাপটপের সাথে সংযুক্ত করে৷ ল্যাপটপ হিসাবে লেবেল করা হয়

উদাহরণস্বরূপ: বলুন আপনি আপনার ফ্লাটার অ্যাপটি বিকাশ করতে একটি উইন্ডোজ ল্যাপটপ ব্যবহার করছেন৷ তারপরে আপনি আপনার ডেভেলপমেন্ট টার্গেট হিসেবে অ্যান্ড্রয়েড বেছে নিন। আপনার অ্যাপের পূর্বরূপ দেখতে, আপনি একটি USB কেবল দিয়ে আপনার Windows ল্যাপটপে একটি Android ডিভাইস সংযুক্ত করুন এবং আপনার অ্যাপ-ইন-ডেভেলপমেন্ট সেই সংযুক্ত Android ডিভাইসে বা একটি Android এমুলেটরে চলে। আপনি উইন্ডোজকে ডেভেলপমেন্ট টার্গেট হিসেবে বেছে নিতে পারতেন, যা আপনার এডিটরের পাশাপাশি উইন্ডোজ অ্যাপ হিসেবে আপনার অ্যাপ-ইন-ডেভেলপমেন্ট চালায়।

আপনি আপনার ডেভেলপমেন্ট টার্গেট হিসাবে ওয়েব বেছে নিতে প্রলুব্ধ হতে পারেন। বিকাশের সময় এটির একটি খারাপ দিক রয়েছে: আপনি ফ্লটারের স্টেটফুল হট রিলোড ক্ষমতা হারাবেন। ফ্লটার বর্তমানে ওয়েব অ্যাপ্লিকেশনগুলিকে হট-রিলোড করতে পারে না৷

চালিয়ে যাওয়ার আগে আপনার পছন্দ করুন। আপনি পরে সবসময় অন্য অপারেটিং সিস্টেমে আপনার অ্যাপ চালাতে পারেন। একটি উন্নয়ন লক্ষ্য নির্বাচন পরবর্তী ধাপ মসৃণ করে তোলে।

Flutter ইনস্টল করুন

Flutter SDK ইনস্টল করার সবচেয়ে আপ-টু-ডেট নির্দেশাবলী docs.flutter.dev এ পাওয়া যাবে।

Flutter ওয়েবসাইটের নির্দেশাবলী SDK এর ইনস্টলেশন এবং ডেভেলপমেন্ট টার্গেট-সম্পর্কিত টুল এবং এডিটর প্লাগইনগুলিকে কভার করে। এই কোডল্যাবের জন্য, নিম্নলিখিত সফ্টওয়্যারটি ইনস্টল করুন:

  1. ফ্লটার SDK
  2. ফ্লাটার প্লাগইন সহ ভিজ্যুয়াল স্টুডিও কোড
  3. আপনার নির্বাচিত উন্নয়ন লক্ষ্যের জন্য কম্পাইলার সফ্টওয়্যার। (মাকওএস বা আইওএসকে টার্গেট করতে আপনার উইন্ডোজ বা এক্সকোড টার্গেট করতে ভিজ্যুয়াল স্টুডিও প্রয়োজন)

পরবর্তী বিভাগে, আপনি আপনার প্রথম ফ্লাটার প্রকল্প তৈরি করবেন।

আপনার যদি কোনো সমস্যা সমাধানের প্রয়োজন হয়, তাহলে আপনি এই প্রশ্ন ও উত্তরগুলির কিছু খুঁজে পেতে পারেন (স্ট্যাকওভারফ্লো থেকে) সমস্যা সমাধানের জন্য সহায়ক।

প্রায়শই জিজ্ঞাসিত প্রশ্নাবলী

3. একটি প্রকল্প তৈরি করুন

আপনার প্রথম ফ্লাটার প্রকল্প তৈরি করুন

এতে VS কোড খোলা এবং আপনার বেছে নেওয়া একটি ডিরেক্টরিতে Flutter অ্যাপ টেমপ্লেট তৈরি করা জড়িত।

  1. ভিজ্যুয়াল স্টুডিও কোড চালু করুন।
  2. কমান্ড প্যালেট খুলুন ( F1 বা Ctrl+Shift+P বা Shift+Cmd+P ) তারপর "flutter new" টাইপ করুন। এটি প্রদর্শিত হলে, Flutter: New Project কমান্ড নির্বাচন করুন।

সাথে ভিএস কোডের একটি স্ক্রিনশট

  1. খালি অ্যাপ্লিকেশন নির্বাচন করুন। আপনার প্রকল্প তৈরি করতে একটি ডিরেক্টরি নির্বাচন করুন। এটি এমন কোনও ডিরেক্টরি হওয়া উচিত যার জন্য উন্নত সুবিধার প্রয়োজন নেই বা এর পথে কোনও স্থান নেই। উদাহরণগুলির মধ্যে আপনার হোম ডিরেক্টরি বা C:\src\ অন্তর্ভুক্ত রয়েছে।

খালি অ্যাপ্লিকেশন সহ VS কোডের একটি স্ক্রিনশট নতুন অ্যাপ্লিকেশন প্রবাহের অংশ হিসাবে নির্বাচিত হিসাবে দেখানো হয়েছে৷

  1. আপনার প্রকল্পের নাম দিন brick_breaker । এই কোডল্যাবের অবশিষ্টাংশ অনুমান করে আপনি আপনার অ্যাপের নাম দিয়েছেন brick_breaker

সাথে ভিএস কোডের একটি স্ক্রিন শট

ফ্লটার এখন আপনার প্রোজেক্ট ফোল্ডার তৈরি করে এবং VS কোড এটি খোলে। আপনি এখন অ্যাপের একটি বেসিক স্ক্যাফোল্ড সহ দুটি ফাইলের বিষয়বস্তু ওভাররাইট করবেন।

প্রাথমিক অ্যাপটি কপি এবং পেস্ট করুন

এটি আপনার অ্যাপে এই কোডল্যাবে দেওয়া উদাহরণ কোড যোগ করে।

  1. VS কোডের বাম প্যানে, এক্সপ্লোরারে ক্লিক করুন এবং pubspec.yaml ফাইলটি খুলুন।

Pubspec.yaml ফাইলের অবস্থান হাইলাইট করে তীর সহ VS কোডের একটি আংশিক স্ক্রিন শট

  1. এই ফাইলের বিষয়বস্তু নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন:

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 ফাইলটি আপনার অ্যাপ সম্পর্কে প্রাথমিক তথ্য নির্দিষ্ট করে, যেমন এর বর্তমান সংস্করণ, এর নির্ভরতা এবং এটি যে সম্পদের সাথে পাঠানো হবে।

  1. lib/ ডিরেক্টরিতে main.dart ফাইল খুলুন।

একটি তীর সহ VS কোডের একটি আংশিক স্ক্রিন শট যা main.dart ফাইলের অবস্থান দেখাচ্ছে৷

  1. এই ফাইলের বিষয়বস্তু নিম্নলিখিত দিয়ে প্রতিস্থাপন করুন:

lib/main.dart

import 'package:flame/game.dart';
import 'package:flutter/material.dart';

void main() {
  final game = FlameGame();
  runApp(GameWidget(game: game));
}
  1. সবকিছু কাজ করছে যাচাই করতে এই কোডটি চালান। এটি শুধুমাত্র একটি ফাঁকা কালো পটভূমি সহ একটি নতুন উইন্ডো প্রদর্শন করা উচিত। বিশ্বের সবচেয়ে খারাপ ভিডিও গেমটি এখন 60fps এ রেন্ডার হচ্ছে!

একটি স্ক্রিন শট একটি brick_breaker অ্যাপ্লিকেশন উইন্ডো দেখাচ্ছে যা সম্পূর্ণ কালো।

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 উপাদান প্রয়োজন।

  1. lib/src/components নামে একটি নতুন ডিরেক্টরিতে play_area.dart নামে একটি ফাইল তৈরি করুন।
  2. এই ফাইলে নিম্নলিখিত যোগ করুন.

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 স্তর আপডেট করার জন্য ব্যবহার করা হয়েছে। শিখার উপাদানগুলি স্থায়ী এবং পরিবর্তনযোগ্য, একটি প্রত্যাশা সহ যে বিকাশকারী এই উপাদানগুলিকে একটি সিমুলেশন সিস্টেমের অংশ হিসাবে ব্যবহার করবে৷

শিখার উপাদানগুলি গেম মেকানিক্স প্রকাশ করার জন্য অপ্টিমাইজ করা হয়। এই কোডল্যাবটি পরবর্তী ধাপে বৈশিষ্ট্যযুক্ত গেম লুপ দিয়ে শুরু হবে।

  1. বিশৃঙ্খলতা নিয়ন্ত্রণ করতে, এই প্রকল্পের সমস্ত উপাদান ধারণকারী একটি ফাইল যোগ করুন। lib/src/components এ একটি components.dart ফাইল তৈরি করুন এবং নিম্নলিখিত বিষয়বস্তু যোগ করুন।

lib/src/components/components.dart

export 'play_area.dart';

export নির্দেশিকা import বিপরীত ভূমিকা পালন করে। এটি অন্য ফাইলে আমদানি করার সময় এই ফাইলটি কী কার্যকারিতা প্রকাশ করে তা ঘোষণা করে। আপনি নিম্নলিখিত ধাপে নতুন উপাদান যোগ করার সাথে সাথে এই ফাইলটি আরও এন্ট্রি বাড়াবে।

একটি শিখা খেলা তৈরি করুন

পূর্ববর্তী ধাপ থেকে লাল স্কুইগলগুলি নিভানোর জন্য, Flame's FlameGame এর জন্য একটি নতুন সাবক্লাস তৈরি করুন।

  1. lib/srcbrick_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 ওভাররাইড পদ্ধতিতে, আপনার কোড দুটি ক্রিয়া সম্পাদন করে।

  1. ভিউফাইন্ডারের জন্য নোঙ্গর হিসাবে উপরের বাম কনফিগার করে। ডিফল্টরূপে, ভিউফাইন্ডার (0,0) এর জন্য নোঙ্গর হিসাবে এলাকার মাঝখানে ব্যবহার করে।
  2. 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));
}

আপনি এই পরিবর্তনগুলি করার পরে, গেমটি পুনরায় চালু করুন। গেমটি নিম্নলিখিত চিত্রের অনুরূপ হওয়া উচিত।

একটি স্ক্রিন শট অ্যাপ উইন্ডোর মাঝখানে একটি বালির রঙের আয়তক্ষেত্র সহ একটি brick_breaker অ্যাপ্লিকেশন উইন্ডো দেখাচ্ছে

পরবর্তী ধাপে, আপনি বিশ্বের একটি বল যোগ করবেন, এবং এটি চলন্ত পেতে!

5. বল প্রদর্শন

বল উপাদান তৈরি করুন

স্ক্রিনে একটি চলমান বল রাখার সাথে অন্য একটি উপাদান তৈরি করা এবং এটিকে গেমের জগতে যুক্ত করা জড়িত।

  1. 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 পরিবর্তন করতে সক্ষম করে যাতে গেমটি কীভাবে দেখায় এবং ফলাফল হিসাবে পরিবর্তনগুলি অনুভব করে।

  1. lib/src/componentsball.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 আপডেটের প্রতি গভীর মনোযোগ দিন। এইভাবে আপনি সময়ের সাথে গতির একটি বিচ্ছিন্ন সিমুলেশন আপডেট করা বাস্তবায়ন করেন।

  1. কম্পোনেন্টের তালিকায় 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. বল হাতে ব্যাট পান

ব্যাট তৈরি করুন

খেলার মধ্যে বল রাখার জন্য একটি ব্যাট যোগ করতে,

  1. নিম্নরূপ 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 ধ্রুবক কনফিগার করে যে ব্যাটটি প্রতিটি বাম বা ডান তীর কী প্রেসের জন্য কতদূর এগিয়েছে।

  1. 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 অ্যাক্সেসর যোগ করে।

  1. BrickBreakerBat উপলব্ধ করতে, 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. প্রাচীর ভেঙে ফেলুন

ইট তৈরি করা হচ্ছে

খেলায় ইট যোগ করতে,

  1. নিম্নরূপ 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.
  1. নিম্নরূপ 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. খেলা জয়

খেলার অবস্থা যোগ করুন

এই ধাপে, আপনি ফ্লাটার র‍্যাপারের ভিতরে ফ্ল্যাম গেমটি এম্বেড করুন এবং তারপরে স্বাগত, গেম ওভার এবং জিতে নেওয়া স্ক্রীনের জন্য ফ্লাটার ওভারলে যোগ করুন।

প্রথমত, আপনি একটি প্লে স্টেট বাস্তবায়নের জন্য গেম এবং কম্পোনেন্ট ফাইলগুলি পরিবর্তন করেন যা প্রতিফলিত করে যে একটি ওভারলে দেখাতে হবে কিনা এবং যদি তাই হয়, কোনটি।

  1. 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 পদ্ধতিতে বিভক্ত করুন। এই পরিবর্তনের আগে, আপনি শুধুমাত্র গেমটি পুনরায় চালু করার মাধ্যমে একটি নতুন গেম শুরু করতে পারেন৷ এই নতুন সংযোজনগুলির সাথে, খেলোয়াড় এখন এই ধরনের কঠোর ব্যবস্থা ছাড়াই একটি নতুন গেম শুরু করতে পারে।

প্লেয়ারকে একটি নতুন গেম শুরু করার অনুমতি দিতে, আপনি গেমটির জন্য দুটি নতুন হ্যান্ডলার কনফিগার করেছেন৷ আপনি একটি ট্যাপ হ্যান্ডলার যোগ করেছেন এবং ব্যবহারকারীকে একাধিক পদ্ধতিতে একটি নতুন গেম শুরু করতে সক্ষম করতে কীবোর্ড হ্যান্ডলারটি প্রসারিত করেছেন৷ প্লে স্টেট মডেলের সাথে, প্লেয়ার জিতলে বা হারলে প্লে স্টেট ট্রানজিশন ট্রিগার করার জন্য কম্পোনেন্ট আপডেট করাটা বোধগম্য হবে।

  1. নিম্নরূপ 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 প্লে স্টেটকে ট্রিগার করে। প্লেয়ার যদি বলটিকে স্ক্রিনের নীচের দিক থেকে পালানোর অনুমতি দেয় তবে এটি সঠিক মনে করা উচিত।

  1. নিম্নরূপ 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 শেল যোগ করুন।

  1. lib/src অধীনে একটি widgets ডিরেক্টরি তৈরি করুন।
  2. একটি 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 সেটার যোগ করা বা সরানো ওভারলেগুলির সাথে সারিবদ্ধ হওয়া উচিত। এই মানচিত্রে নেই এমন একটি ওভারলে সেট করার প্রচেষ্টা চারদিকে অসুখী মুখের দিকে নিয়ে যায়।

  1. স্ক্রিনে এই নতুন কার্যকারিতা পেতে, 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-এর জন্য, আপনার কাছে সম্পাদনা করার জন্য দুটি ফাইল আছে।

  1. নিম্নলিখিত কোডের সাথে মেলে 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>
  1. নিম্নলিখিত কোডের সাথে মেলে 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. স্কোর রাখুন

গেমটিতে স্কোর যোগ করুন

এই ধাপে, আপনি আশেপাশের ফ্লাটার প্রসঙ্গে গেমের স্কোর প্রকাশ করেন। এই ধাপে আপনি ফ্লেম গেম থেকে আশেপাশের ফ্লাটার স্টেট ম্যানেজমেন্টে স্টেট এক্সপোজ করেন। এটি প্রতিবার খেলোয়াড় একটি ইট ভাঙার সময় স্কোর আপডেট করতে গেম কোডকে সক্ষম করে।

  1. 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 যোগ করার মাধ্যমে, আপনি গেমের অবস্থাকে ফ্লটার স্টেট ম্যানেজমেন্টের সাথে সংযুক্ত করেন।

  1. প্লেয়ার যখন ইট ভাঙ্গে তখন স্কোরে একটি পয়েন্ট যোগ করতে 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>());
    }
  }
}

একটি সুদর্শন খেলা করুন

এখন যেহেতু আপনি ফ্লটারে স্কোর রাখতে পারেন, এটি সুন্দর দেখানোর জন্য উইজেটগুলিকে একত্রিত করার সময়।

  1. lib/src/widgetsscore_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!,
          ),
        );
      },
    );
  }
}
  1. lib/src/widgetsoverlay_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.
              ),
            ),
          ),
        ),
      ),
    );
  }
}

এই সমস্ত জায়গায়, আপনি এখন এই গেমটি ছয়টি ফ্লাটার টার্গেট প্ল্যাটফর্মের যে কোনওটিতে চালাতে সক্ষম হবেন। খেলা নিম্নলিখিত অনুরূপ করা উচিত.

brick_breaker-এর একটি স্ক্রিন শট যা প্রাক-গেম স্ক্রীন দেখাচ্ছে যা ব্যবহারকারীকে গেমটি খেলতে স্ক্রীনে আলতো চাপ দিতে আমন্ত্রণ জানায়

brick_breaker এর একটি স্ক্রিন শট একটি ব্যাট এবং কিছু ইটের উপরে স্ক্রীনের উপর দিয়ে খেলা দেখাচ্ছে

11. অভিনন্দন

অভিনন্দন, আপনি ফ্লটার এবং ফ্লেম দিয়ে একটি গেম তৈরি করতে সফল হয়েছেন!

আপনি Flame 2D গেম ইঞ্জিন ব্যবহার করে একটি গেম তৈরি করেছেন এবং এটি একটি Flutter wrapper এ এমবেড করেছেন৷ আপনি উপাদানগুলিকে অ্যানিমেট করতে এবং অপসারণ করতে শিখার প্রভাবগুলি ব্যবহার করেছেন৷ আপনি Google ফন্ট এবং ফ্লটার অ্যানিমেট প্যাকেজ ব্যবহার করেছেন যাতে পুরো গেমটি ভালভাবে ডিজাইন করা হয়।

এরপর কি?

এই কোডল্যাবগুলির কিছু পরীক্ষা করে দেখুন...

আরও পড়া