আমি সবসময় ভিডিও গেম বানাতে চেয়েছিলাম। আমার প্রথম অ্যান্ড্রয়েড অ্যাপ যেটি আমাকে আমার প্রথম কাজ পেতে সাহায্য করেছিল সেটি ছিল একটি সাধারণ গেম, যা অ্যান্ড্রয়েড ভিউ দিয়ে তৈরি। এর পরে, গেম ইঞ্জিন ব্যবহার করে আরও বিস্তৃত গেম তৈরি করার অনেক প্রচেষ্টা হয়েছিল, কিন্তু সময়ের অভাব বা কাঠামোর জটিলতার কারণে সেগুলি সবই ব্যর্থ হয়েছিল। কিন্তু যখন আমি প্রথম ফ্লাটারের উপর ভিত্তি করে ফ্লেম ইঞ্জিনের কথা শুনি, তখন আমি এর সরলতা এবং ক্রস-প্ল্যাটফর্ম সমর্থন দ্বারা আকৃষ্ট হয়েছিলাম, তাই আমি এটির সাথে একটি গেম তৈরি করার চেষ্টা করার সিদ্ধান্ত নিয়েছিলাম। আমি ইঞ্জিনের অনুভূতি পেতে সহজ কিছু দিয়ে শুরু করতে চেয়েছিলাম, কিন্তু এখনও চ্যালেঞ্জিং। প্রবন্ধের এই সিরিজটি হল আমার ফ্লেম (এবং ফ্লাটার) শেখার এবং একটি মৌলিক প্ল্যাটফর্মার গেম তৈরি করার যাত্রা। আমি এটিকে বেশ বিশদভাবে তৈরি করার চেষ্টা করব, তাই এটি যে কারো জন্য উপযোগী হওয়া উচিত যারা কেবলমাত্র তাদের পায়ের আঙ্গুলগুলি ফ্লেম বা গেম ডেভের মধ্যে ডুবিয়ে দিচ্ছেন। 4টি নিবন্ধের মধ্যে, আমি একটি 2d সাইড-স্ক্রলিং গেম তৈরি করতে যাচ্ছি, যার মধ্যে রয়েছে: একটি চরিত্র যা দৌড়াতে এবং লাফ দিতে পারে একটি ক্যামেরা যা প্লেয়ারকে অনুসরণ করে গ্রাউন্ড এবং প্ল্যাটফর্ম সহ স্ক্রোলিং লেভেল ম্যাপ প্যারালাক্স ব্যাকগ্রাউন্ড কয়েন যা প্লেয়ার সংগ্রহ করতে পারে এবং HUD যা কয়েনের সংখ্যা প্রদর্শন করে পর্দা জয় প্রথম অংশে, আমরা একটি নতুন ফ্লেম প্রকল্প তৈরি করতে যাচ্ছি, সমস্ত সম্পদ লোড করব, একজন খেলোয়াড়ের চরিত্র যোগ করব এবং তাকে কীভাবে চালাতে হবে তা শেখাবো। প্রকল্প সেটআপ প্রথমত, একটি নতুন প্রকল্প তৈরি করা যাক। অফিসিয়াল টিউটোরিয়ালটি এটি করার সমস্ত পদক্ষেপ বর্ণনা করার জন্য একটি দুর্দান্ত কাজ করে, তাই এটি অনুসরণ করুন। বেয়ার ফ্লেম গেম যোগ করার জন্য একটি জিনিস: আপনি যখন ফাইল সেট আপ করছেন, আপনি লাইব্রেরি সংস্করণগুলিকে সর্বশেষ উপলব্ধ সংস্করণে আপডেট করতে পারেন, বা এটিকে যেমন আছে তেমনই রেখে দিতে পারেন, কারণ একটি সংস্করণের আগে ক্যারেট সাইন (^) নিশ্চিত করবে যে আপনার অ্যাপটি সর্বশেষ নন ব্যবহার করে -ব্রেকিং সংস্করণ। ( ) pubspec.yaml ক্যারেট সিনট্যাক্স আপনি যদি সমস্ত পদক্ষেপ অনুসরণ করেন তবে আপনার ফাইলটি এইরকম হওয়া উচিত: main.dart import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; void main() { final game = FlameGame(); runApp(GameWidget(game: game)); } সম্পদ আমরা চালিয়ে যাওয়ার আগে, গেমের জন্য ব্যবহার করা হবে এমন সম্পদ প্রস্তুত করতে হবে। সম্পদ হল ছবি, অ্যানিমেশন, শব্দ, ইত্যাদি একটি প্ল্যাটফর্মার স্তর তৈরি করার সবচেয়ে সহজ উপায় হল টাইল মানচিত্র এবং টাইল স্প্রিট ব্যবহার করা। এর মানে হল যে স্তরটি মূলত একটি গ্রিড, যেখানে প্রতিটি সেল নির্দেশ করে কোন বস্তু/গ্রাউন্ড/প্ল্যাটফর্মটি প্রতিনিধিত্ব করে। পরে, যখন গেমটি চলছে, প্রতিটি ঘর থেকে তথ্য সংশ্লিষ্ট টাইল স্প্রাইটে ম্যাপ করা হয়। এই কৌশলটি ব্যবহার করে নির্মিত গেম গ্রাফিক্স সত্যিই বিস্তৃত বা খুব সহজ হতে পারে। উদাহরণস্বরূপ, সুপার মারিও ব্রোসে, আপনি দেখতে পাচ্ছেন যে প্রচুর উপাদান পুনরাবৃত্তি হচ্ছে। এর কারণ, গেম গ্রিডে প্রতিটি গ্রাউন্ড টাইলের জন্য, শুধুমাত্র একটি গ্রাউন্ড ইমেজ এটিকে উপস্থাপন করে। আমরা একই পদ্ধতি অনুসরণ করব এবং আমাদের কাছে থাকা প্রতিটি স্থির বস্তুর জন্য একটি একক চিত্র প্রস্তুত করব। আমরা কিছু বস্তুও চাই, যেমন প্লেয়ার চরিত্র এবং কয়েন অ্যানিমেটেড হোক। অ্যানিমেশন সাধারণত স্থির চিত্রগুলির একটি সিরিজ হিসাবে সংরক্ষণ করা হয়, প্রতিটি একটি একক ফ্রেমের প্রতিনিধিত্ব করে। যখন অ্যানিমেশন বাজানো হয়, ফ্রেমগুলি একের পর এক চলে যায়, বস্তুর চলমানতার বিভ্রম তৈরি করে। এখন সবচেয়ে গুরুত্বপূর্ণ প্রশ্ন হল সম্পদ কোথায় পাওয়া যাবে। অবশ্যই, আপনি সেগুলি নিজেই আঁকতে পারেন বা কোনও শিল্পীর কাছে তাদের কমিশন করতে পারেন। এছাড়াও, অনেক দুর্দান্ত শিল্পী আছেন যারা ওপেন সোর্সে গেমের সম্পদগুলি অবদান রেখেছেন। আমি এর ব্যবহার করব। GrafxKid- Arcade Platformer Assets প্যাক সাধারণত, ইমেজ সম্পদ দুটি আকারে আসে: স্প্রাইট শীট এবং একক স্প্রাইট। প্রাক্তনটি একটি বৃহৎ চিত্র, একটিতে সমস্ত গেম সম্পদ রয়েছে৷ তারপরে গেম ডেভেলপাররা প্রয়োজনীয় স্প্রাইটের সঠিক অবস্থান নির্দিষ্ট করে এবং গেম ইঞ্জিন এটিকে শীট থেকে কেটে দেয়। এই গেমটির জন্য, আমি একক স্প্রাইট ব্যবহার করব (অ্যানিমেশন বাদে, তাদের একটি চিত্র হিসাবে রাখা সহজ) কারণ আমার স্প্রাইট শীটে দেওয়া সমস্ত সম্পদের প্রয়োজন নেই। আপনি নিজে স্প্রাইট তৈরি করছেন বা সেগুলি কোনও শিল্পীর কাছ থেকে নিয়ে আসছেন, গেম ইঞ্জিনের জন্য সেগুলিকে আরও উপযুক্ত করার জন্য আপনাকে সেগুলিকে টুকরো টুকরো করতে হবে৷ আপনি সেই উদ্দেশ্যে বিশেষভাবে তৈরি করা সরঞ্জামগুলি ব্যবহার করতে পারেন (যেমন বা কোনও গ্রাফিকাল সম্পাদক। আমি অ্যাডোব ফটোশপ ব্যবহার করেছি, কারণ, এই স্প্রাইট শীটে, স্প্রাইটগুলির মধ্যে অসম স্থান রয়েছে, যা স্বয়ংক্রিয় সরঞ্জামগুলির জন্য চিত্রগুলি বের করা কঠিন করে তুলেছিল, তাই আমাকে এটি ম্যানুয়ালি করতে হয়েছিল। টেক্সচার প্যাকার) আপনি সম্পদের আকার বাড়াতেও চাইতে পারেন, কিন্তু যদি এটি একটি ভেক্টর চিত্র না হয়, ফলে স্প্রাইটটি ঝাপসা হয়ে যেতে পারে। একটি সমাধান আমি খুঁজে পেয়েছি যে পিক্সেল শিল্পের জন্য দুর্দান্ত কাজ করে তা হল ফটোশপে রিসাইজিং পদ্ধতি ব্যবহার করা (বা জিম্পে ইন্টারপোলেশন সেট করা নেই)। কিন্তু যদি আপনার সম্পদ আরো বিস্তারিত হয়, তাহলে সম্ভবত এটি কাজ করবে না। Nearest Neighbour (hard edges) ব্যাখ্যা ছাড়া, ডাউনলোড করুন বা আপনার নিজের তৈরি করুন এবং আপনার প্রকল্পের ফোল্ডারে যোগ করুন। আমি যে সম্পদগুলি তৈরি করেছি তা assets/images যখনই আপনি নতুন সম্পদ যোগ করবেন, আপনাকে সেগুলিকে ফাইলে এইভাবে নিবন্ধন করতে হবে: pubspec.yaml flutter: assets: - assets/images/ এবং ভবিষ্যতের জন্য টিপ: আপনি যদি ইতিমধ্যে নিবন্ধিত সম্পদগুলি আপডেট করেন তবে পরিবর্তনগুলি দেখতে আপনাকে গেমটি পুনরায় চালু করতে হবে৷ এখন এর প্রকৃতপক্ষে খেলার মধ্যে সম্পদ লোড করা যাক. আমি সমস্ত সম্পদের নাম এক জায়গায় রাখতে চাই, যা একটি ছোট গেমের জন্য দুর্দান্ত কাজ করে, কারণ সবকিছুর ট্র্যাক রাখা এবং প্রয়োজনে পরিবর্তন করা সহজ। সুতরাং, আসুন ডিরেক্টরিতে একটি নতুন ফাইল তৈরি করি: lib assets.dart const String THE_BOY = "theboy.png"; const String GROUND = "ground.png"; const String PLATFORM = "platform.png"; const String MIST = "mist.png"; const String CLOUDS = "clouds.png"; const String HILLS = "hills.png"; const String COIN = "coin.png"; const String HUD = "hud.png"; const List<String> SPRITES = [THE_BOY, GROUND, PLATFORM, MIST, CLOUDS, HILLS, COIN, HUD]; এবং তারপরে অন্য একটি ফাইল তৈরি করুন, যাতে ভবিষ্যতে সমস্ত গেম লজিক থাকবে: game.dart import 'package:flame/game.dart'; import 'assets.dart' as Assets; class PlatformerGame extends FlameGame { @override Future<void> onLoad() async { await images.loadAll(Assets.SPRITES); } } হল প্রধান শ্রেণী যা আমাদের গেমের প্রতিনিধিত্ব করে, এটি প্রসারিত করে, ফ্লেম ইঞ্জিনে ব্যবহৃত বেস গেম ক্লাস। যা ঘুরে প্রসারিত করে - শিখার মৌলিক বিল্ডিং ব্লক। ছবি, ইন্টারফেস বা প্রভাব সহ আপনার গেমের সবকিছুই উপাদান। প্রতিটি একটি async পদ্ধতি আছে , যাকে কম্পোনেন্ট ইনিশিয়ালাইজেশন বলা হয়। সাধারণত, সমস্ত উপাদান সেটআপ যুক্তি সেখানে যায়। PlatformerGame FlameGame Component Component onLoad অবশেষে, আমরা আমাদের ফাইলটি আমদানি করেছি যা আমরা আগে তৈরি করেছি এবং আমাদের সম্পদের ধ্রুবকগুলি কোথা থেকে আসছে তা স্পষ্টভাবে ঘোষণা করার জন্য যোগ করেছি। এবং তালিকায় তালিকাভুক্ত সমস্ত সম্পদ গেম ইমেজ ক্যাশে লোড করতে ব্যবহার করা হয়েছে। assets.dart as Assets SPRITES images.loadAll তারপর, আমাদের থেকে আমাদের নতুন তৈরি করতে হবে। ফাইলটি নিম্নরূপ পরিবর্তন করুন: main.dart PlatformerGame import 'package:flame/game.dart'; import 'package:flutter/widgets.dart'; import 'game.dart'; void main() { runApp( const GameWidget<PlatformerGame>.controlled( gameFactory: PlatformerGame.new, ), ); } সমস্ত প্রস্তুতি সম্পন্ন হয়, এবং মজার অংশ শুরু হয়। প্লেয়ার চরিত্র যোগ করা হচ্ছে একটি নতুন ফোল্ডার এবং এর ভিতরে একটি নতুন ফাইল তৈরি করুন। এটি এমন একটি উপাদান হতে চলেছে যা খেলোয়াড়ের চরিত্রের প্রতিনিধিত্ব করে: দ্য বয়। lib/actors/ theboy.dart import '../game.dart'; import '../assets.dart' as Assets; import 'package:flame/components.dart'; class TheBoy extends SpriteAnimationComponent with HasGameRef<PlatformerGame> { TheBoy({ required super.position, // Position on the screen }) : super( size: Vector2.all(48), // Size of the component anchor: Anchor.bottomCenter // ); @override Future<void> onLoad() async { animation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, // For now we only need idle animation, so we load only 1 frame textureSize: Vector2.all(20), // Size of a single sprite in the sprite sheet stepTime: 0.12, // Time between frames, since it's a single frame not that important ), ); } } ক্লাসটি প্রসারিত করে যা অ্যানিমেটেড স্প্রাইটগুলির জন্য ব্যবহৃত একটি উপাদান এবং এতে একটি মিক্সিন রয়েছে যা গেমের ক্যাশে থেকে ছবি লোড করতে বা পরে গ্লোবাল ভেরিয়েবল পেতে গেম অবজেক্টের রেফারেন্স করতে দেয়। SpriteAnimationComponent HasGameRef আমাদের পদ্ধতিতে আমরা ফাইলে ঘোষিত স্প্রাইট শীট থেকে একটি নতুন তৈরি করি। onLoad assets.dart THE_BOY SpriteAnimation এখন খেলায় আমাদের খেলোয়াড় যোগ করা যাক! ফাইলে ফিরে যান এবং পদ্ধতির নীচে নিম্নলিখিত যোগ করুন: game.dart onLoad final theBoy = TheBoy(position: Vector2(size.x / 2, size.y / 2)); add(theBoy); আপনি যদি এখন গেমটি চালান, তাহলে আমরা ছেলেটির সাথে দেখা করতে সক্ষম হব! প্লেয়ার আন্দোলন প্রথমত, আমাদের কীবোর্ড থেকে দ্য বয়কে নিয়ন্ত্রণ করার ক্ষমতা যোগ করতে হবে। ফাইলে mixin যোগ করা যাক। game.dart HasKeyboardHandlerComponents class PlatformerGame extends FlameGame with HasKeyboardHandlerComponents এর পরে, আসুন এবং মিশ্রণে ফিরে যাই: theboy.dart KeyboardHandler class TheBoy extends SpriteAnimationComponent with KeyboardHandler, HasGameRef<PlatformerGame> তারপরে, কম্পোনেন্টে কিছু নতুন ক্লাস ভেরিয়েবল যোগ করুন: TheBoy final double _moveSpeed = 300; // Max player's move speed int _horizontalDirection = 0; // Current direction the player is facing final Vector2 _velocity = Vector2.zero(); // Current player's speed সবশেষে, আসুন পদ্ধতিটিকে ওভাররাইড করি যা কীবোর্ড ইনপুট শোনার অনুমতি দেয়: onKeyEvent @override bool onKeyEvent(RawKeyEvent event, Set<LogicalKeyboardKey> keysPressed) { _horizontalDirection = 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyA) || keysPressed.contains(LogicalKeyboardKey.arrowLeft)) ? -1 : 0; _horizontalDirection += (keysPressed.contains(LogicalKeyboardKey.keyD) || keysPressed.contains(LogicalKeyboardKey.arrowRight)) ? 1 : 0; return true; } এখন সমান 1 যদি প্লেয়ার ডানে চলে যায়, -1 যদি প্লেয়ার বাম দিকে চলে যায়, এবং 0 যদি প্লেয়ার সরে না যায়। যাইহোক, আমরা এখনও এটি স্ক্রিনে দেখতে পাচ্ছি না, কারণ প্লেয়ারের অবস্থান এখনও পরিবর্তিত হয়নি। পদ্ধতি যোগ করে এটি ঠিক করা যাক। _horizontalDirection update এখন আমি গেম লুপ কি ব্যাখ্যা করতে হবে. মূলত, এর মানে হল যে গেমটি একটি অন্তহীন লুপে চালানো হচ্ছে। প্রতিটি পুনরাবৃত্তিতে, পদ্ধতি বর্তমান অবস্থা রেন্ডার করা হয় এবং তারপর পদ্ধতি একটি নতুন অবস্থা গণনা করা হয়। পদ্ধতির স্বাক্ষরে প্যারামিটারটি শেষ স্টেট আপডেটের পর থেকে মিলিসেকেন্ডে সময়। এটি মাথায় রেখে, এ নিম্নলিখিতটি যুক্ত করুন: Component's render update dt theboy.dart @override void update(double dt) { super.update(dt); _velocity.x = _horizontalDirection * _moveSpeed; position += _velocity * dt; } প্রতিটি গেম লুপ চক্রের জন্য, আমরা বর্তমান দিক এবং সর্বোচ্চ গতি ব্যবহার করে অনুভূমিক বেগ আপডেট করি। তারপরে আমরা দ্বারা গুণিত আপডেট করা মান দিয়ে স্প্রাইট অবস্থান পরিবর্তন করি। dt কেন আমরা শেষ অংশ প্রয়োজন? ঠিক আছে, আপনি যদি ঠিক বেগের সাথে অবস্থানটি আপডেট করেন তবে স্প্রাইটটি মহাকাশে উড়ে যাবে। কিন্তু আমরা কি শুধু ছোট গতির মান ব্যবহার করতে পারি, আপনি জিজ্ঞাসা করতে পারেন? আমরা পারি, কিন্তু প্লেয়ারের চলাফেরা করার পদ্ধতি ভিন্ন ভিন্ন ফ্রেম প্রতি সেকেন্ডে (FPS) হারে ভিন্ন হবে। প্রতি সেকেন্ডে ফ্রেমের সংখ্যা (বা গেম লুপ) গেমের পারফরম্যান্স এবং এটি চালানো হার্ডওয়্যারের উপর নির্ভর করে। ডিভাইসের পারফরম্যান্স যত ভাল হবে, FPS তত বেশি হবে এবং প্লেয়ার তত দ্রুত চলে যাবে। এটি এড়ানোর জন্য, আমরা গতিকে শেষ ফ্রেম থেকে পাস করা সময়ের উপর নির্ভর করি। এইভাবে স্প্রাইট যেকোন FPS-এ একইভাবে চলে যাবে। ঠিক আছে, যদি আমরা এখন গেমটি চালাই, আমাদের এটি দেখতে হবে: আশ্চর্যজনক, এখন বাম দিকে গেলে ছেলেটিকে ঘুরিয়ে দেই। পদ্ধতির নীচে এটি যোগ করুন: update if ((_horizontalDirection < 0 && scale.x > 0) || (_horizontalDirection > 0 && scale.x < 0)) { flipHorizontally(); } মোটামুটি সহজ যুক্তি: আমরা পরীক্ষা করি যে বর্তমান দিকটি (ব্যবহারকারী যে তীরটি টিপেছে) স্প্রাইটের দিক থেকে ভিন্ন কিনা, তারপর আমরা অনুভূমিক অক্ষ বরাবর স্প্রাইটটিকে উল্টিয়ে দেই। এখন চলমান অ্যানিমেশন যোগ করা যাক। প্রথমে দুটি নতুন ক্লাস ভেরিয়েবল সংজ্ঞায়িত করুন: late final SpriteAnimation _runAnimation; late final SpriteAnimation _idleAnimation; তারপর এই মত আপডেট করুন: onLoad @override Future<void> onLoad() async { _idleAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 1, textureSize: Vector2.all(20), stepTime: 0.12, ), ); _runAnimation = SpriteAnimation.fromFrameData( game.images.fromCache(Assets.THE_BOY), SpriteAnimationData.sequenced( amount: 4, textureSize: Vector2.all(20), stepTime: 0.12, ), ); animation = _idleAnimation; } এখানে আমরা ক্লাস ভেরিয়েবলে পূর্বে যোগ করা নিষ্ক্রিয় অ্যানিমেশন বের করেছি এবং একটি নতুন রান অ্যানিমেশন ভেরিয়েবল সংজ্ঞায়িত করেছি। এর পরে, আসুন একটি নতুন পদ্ধতি যুক্ত করি: updateAnimation void updateAnimation() { if (_horizontalDirection == 0) { animation = _idleAnimation; } else { animation = _runAnimation; } } এবং অবশেষে, পদ্ধতির নীচে এই পদ্ধতিটি চালু করুন এবং গেমটি চালান। update উপসংহার প্রথম অংশের জন্য এটাই। আমরা শিখেছি কিভাবে একটি ফ্লেম গেম সেট আপ করতে হয়, কোথায় সম্পদ খুঁজে বের করতে হয়, কীভাবে সেগুলি আপনার গেমে লোড করতে হয় এবং কীভাবে একটি দুর্দান্ত অ্যানিমেটেড চরিত্র তৈরি করতে হয় এবং কীবোর্ড ইনপুটগুলির উপর ভিত্তি করে এটিকে সরানো যায়। এই অংশের জন্য কোডটি পাওয়া যেতে পারে। আমার গিথুবে পরের প্রবন্ধে, আমি কভার করব কীভাবে টাইলড ব্যবহার করে একটি গেম লেভেল তৈরি করা যায়, কীভাবে ফ্লেম ক্যামেরা নিয়ন্ত্রণ করা যায় এবং একটি প্যারালাক্স ব্যাকগ্রাউন্ড যোগ করা যায়। সাথে থাকুন! সম্পদ প্রতিটি অংশের শেষে, আমি অসাধারণ সৃষ্টিকর্তা এবং সম্পদের একটি তালিকা যোগ করব যা থেকে আমি শিখেছি। GrafxKid এর আর্কেড প্ল্যাটফর্মার সম্পদ https://opengameart.org/content/arcade-platformer-assets DevKage Flame Game Development Series: https://youtu.be/mSPalRqZQS8 ক্রেগ ওডা চ্যানেল https://youtu.be/hwQpBuZoV9s এমবার কোয়েস্ট গেম টিউটোরিয়াল https://github.com/flame-engine/flame/blob/main/doc/tutorials/platformer/platformer.md ফ্লেম ইঞ্জিন ডকুমেন্টেশন https://docs.flame-engine.org/1.6.0/flame/flame.html