Create 2D games with Flutter Flame Simply and Quickly
Using Google’s open-source Flutter UI framework, a single codebase may be used to construct natively built mobile, browser, and desktop apps. It is an effective tool that has completely changed how programmers make cross-platform apps.
But owing to Flutter Flame’s framework, Flutter may also be employed to make games; it is not simply confined to making user interfaces. You should think about app developers for hire from www.flutteragency.com creating 2D games with Flutter Flame. Keep on reading to discover the fundamentals of this article.
Introduction to Flutter Flame
On the upper end of Flutter, Flutter Flame is a 2D development kit that offers a simple collection of resources for game production. It has several pre-made elements, such as sprites, animations, particle systems, physics, and collision detection. Developers may easily create games on mobile, tablet, and online websites with Flutter Flame.
Flutter Flame is an attractive option for game creation for several reasons. First, because it is developed on the upper edge of Flutter, developers may take advantage of the UI tools, including widgets already present in Flutter. This makes designing aesthetically appealing panels and interfaces for games simple.
Second, even for creators who are novices in game production, Flutter Flame is made to be straightforward to use. Last but not least, Flutter Flame is quick and effective, enabling games developed using it to function without a hitch on various platforms.
Flutter Flame is a great option if you are interested in game creation and want to discover how to make 2D games fast. With just several lines of code, you can build games for various platforms using its robust collection of tools and simple interface. Why then wait? Let us get started with Flutter Flame to start making some incredible games!
Configuring Flutter Flame
You must install Flutter and Flutter Flame on your machine before we begin.
Download the package in your (pubspec.yaml) file and add the dependence described below to get Flame up and running.
dependencies:
flame: ^1.1.1
The GameWidget is used to render games. adding the following line of code to the main. Flame game is rendered by the dart file, which has a black screen for now.
void main() {
final game = FlameGame();
runApp(
GameWidget(
game: game,
),
);
}
You are now prepared to give your game some visuals.
Sprite Loading
The SpriteComponent class must be used to render static pictures. Update your pubspec.yaml file to retrieve the assets after adding your game’s visuals to the assets/images folder. The backdrop and player graphics in this lesson will be loaded.
The three files listed below will be created and updated in the lib folder:
dino_player.dart, which will load and position our player:
import ‘package:flame/components.dart’;
class DinoPlayer extends SpriteComponent with HasGameRef {
DinoPlayer() : super(size: Vector2.all(100.0));
@override
Future<void> onLoad() async {
super.onLoad();
sprite = await gameRef.loadSprite(‘idle.png’);
position = gameRef.size / 2;
}
}
dino_world.dart, which will load our game background:
import ‘package:flame/components.dart’;
class DinoWorld extends SpriteComponent with HasGameRef {
@override
Future<void> onLoad() async {
super.onLoad();
sprite = await gameRef.loadSprite(‘background.png’);
size = sprite!.originalSize;
}
}
dino_game.dart, which will manage all our game components. It adds our game player and background and positions them:
import ‘dart:ui’;
import ‘package:flame/game.dart’;
import ‘dino_player.dart’;
import ‘dino_world.dart’;
class DinoGame extends FlameGame{
DinoPlayer _dinoPlayer = DinoPlayer();
DinoWorld _dinoWorld = DinoWorld();
@override
Future<void> onLoad() async {
super.onLoad();
await add(_dinoWorld);
await add(_dinoPlayer);
_dinoPlayer.position = _dinoWorld.size / 1.5;
camera.followComponent(_dinoPlayer,
worldBounds: Rect.fromLTRB(0, 0, _dinoWorld.size.x, _dinoWorld.size.y));
}
}
The camera.followComponent function sets the game viewport to follow the player. This function is necessary, as we’ll be adding motion to our player.
Update your main.dart file to load the DinoGame as shown below:
import ‘package:flame/game.dart’;
import ‘package:flutter/material.dart’;
import ‘dino_game.dart’;
void main() {
final game = DinoGame();
runApp(
GameWidget(game: game),
);
}
Running your application should display your player and a background.
Dynamic movement
You must be able to recognize and react to the route you choose to move your player. The pointer keys in the game will be used in this tutorial to give your player movement.
Create a folder called “helpers” and add the following files to it, updating them as needed:
directions.dart contains the directions enum:
enum Direction { up, down, left, right, none }
navigation_keys.dart contains the UI and logic of the navigation keys:
import ‘package:flutter/gestures.dart’;
import ‘package:flutter/material.dart’;
import ‘directions.dart’;
class NavigationKeys extends StatefulWidget {
final ValueChanged<Direction>? onDirectionChanged;
const NavigationKeys({Key? key, required this.onDirectionChanged})
: super(key: key);
@override
State<NavigationKeys> createState() => _NavigationKeysState();
}
class _NavigationKeysState extends State<NavigationKeys> {
Direction direction = Direction.none;
@override
Widget build(BuildContext context) {
return SizedBox(
height: 200,
width: 120,
child: Column(
children: [
ArrowKey(
icons: Icons.keyboard_arrow_up,
onTapDown: (det) {
updateDirection(Direction.up);
},
onTapUp: (dets) {
updateDirection(Direction.none);
},
onLongPressDown: () {
updateDirection(Direction.up);
},
onLongPressEnd: (dets) {
updateDirection(Direction.none);
},
),
Row(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ArrowKey(
icons: Icons.keyboard_arrow_left,
onTapDown: (det) {
updateDirection(Direction.left);
},
onTapUp: (dets) {
updateDirection(Direction.none);
},
onLongPressDown: () {
updateDirection(Direction.left);
},
onLongPressEnd: (dets) {
updateDirection(Direction.none);
},
),
ArrowKey(
icons: Icons.keyboard_arrow_right,
onTapDown: (det) {
updateDirection(Direction.right);
},
onTapUp: (dets) {
updateDirection(Direction.none);
},
onLongPressDown: () {
updateDirection(Direction.right);
},
onLongPressEnd: (dets) {
updateDirection(Direction.none);
},
),
],
),
ArrowKey(
icons: Icons.keyboard_arrow_down,
onTapDown: (det) {
updateDirection(Direction.down);
},
onTapUp: (dets) {
updateDirection(Direction.none);
},
onLongPressDown: () {
updateDirection(Direction.down);
},
onLongPressEnd: (dets) {
updateDirection(Direction.none);
},
),
],
),
);
}
void updateDirection(Direction newDirection) {
direction = newDirection;
widget.onDirectionChanged!(direction);
}
}
class ArrowKey extends StatelessWidget {
const ArrowKey({
Key? key,
required this.icons,
required this.onTapDown,
required this.onTapUp,
required this.onLongPressDown,
required this.onLongPressEnd,
}) : super(key: key);
final IconData icons;
final Function(TapDownDetails) onTapDown;
final Function(TapUpDetails) onTapUp;
final Function() onLongPressDown;
final Function(LongPressEndDetails) onLongPressEnd;
@override
Widget build(BuildContext context) {
return GestureDetector(
onTapDown: onTapDown,
onTapUp: onTapUp,
onLongPress: onLongPressDown,
onLongPressEnd: onLongPressEnd,
child: Container(
margin: const EdgeInsets.all(8),
decoration: BoxDecoration(
color: const Color(0x88ffffff),
borderRadius: BorderRadius.circular(60),
),
child: Icon(
icons,
size: 42,
),
),
);
}
}
Then, update the main.dart file to display your game and keys as shown below:
void main() {
final game = DinoGame();
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(
body: Stack(
children: [
GameWidget(
game: game,
),
Align(
alignment: Alignment.bottomRight,
child: NavigationKeys(onDirectionChanged: game.onArrowKeyChanged,),
),
],
),
),
),
);
}
Add the function below into the dino_game.dart file to execute the player’s movement:
onArrowKeyChanged(Direction direction){
_dinoPlayer.direction = direction;
}
Lastly, add the following code snippet to the (dino_player.dart file) to update the player’s position:
Direction direction = Direction.none;
@override
void update(double dt) {
super.update(dt);
updatePosition(dt);
}
updatePosition(double dt) {
switch (direction) {
case Direction.up:
position.y –;
break;
case Direction.down:
position.y ++;
break;
case Direction.left:
position.x –;
break;
case Direction.right:
position.x ++;
break;
case Direction.none:
break;
}
}
Running your program and using any of the arrow keys will change the location of your player.
Animated sprites
Your player is now moving as planned, but it has not yet been animated to appear natural. You will need to utilize a sprite sheet to animate your player.
A group of sprites organized in column and row order make up a sprite sheet. Compared to loading individual sprites, it is rapid. Just a portion of the sprite sheet may be loaded and rendered by the Flame engine. A sprite file of the dino player is seen in the image below.
Sprite Sheet
Several player frames can be animated on the sprite sheet to show movements like turning right or left. The assets/images folder receives the addition of the sprite sheet.
In the (dino player.dart file), do the following actions to animate your player:
Extend SpriteAnimationComponent instead of SpriteComponent.
We will concentrate on the movements for moving left and right in this lesson.
late final SpriteAnimation _walkingRightAnimation;
late final SpriteAnimation _walkingLeftAnimation;
late final SpriteAnimation _idleAnimation;
final double _animationSpeed = .15;
From of the sprite sheet, upload your sprites. Based on where they are located on the sheet, the sprites are loaded. The width and row of every sprite may be specified, or you can choose each sprite depending on where it is in the row and column.
Future<void> _loadAnimations() async {
final spriteSheet = SpriteSheet.fromColumnsAndRows(
image: await gameRef.images.load(‘spritesheet.png’),
columns: 30,
rows: 1);
_idleAnimation = spriteSheet.createAnimation(
row: 0, stepTime: _animationSpeed, from: 0, to: 9);
_walkingRightAnimation = spriteSheet.createAnimation(
row: 0, stepTime: _animationSpeed, from: 10, to: 19);
_walkingLeftAnimation = spriteSheet.createAnimation(row: 0, stepTime: _animationSpeed, from: 20, to: 29);
}
The (spriteSheet.createAnimation) method chooses and animates a series of sprites according to the row, from, and to properties.
The player will be updated to load the chosen animation.
First, override the onLoad function to load the _idleAnimation.
@override
Future<void> onLoad() async {
super.onLoad();
await _loadAnimations().then((_) => {animation = _idleAnimation});
}
Then modify the updatePosition method to load various animations according to the player’s looking orientation. This lesson includes sprites for standing still, moving to the right, and moving to the left.
updatePosition(double dt) {
switch (direction) {
case Direction.up:
position.y –;
break;
case Direction.down:
position.y ++;
break;
case Direction.left:
animation = _walkingLeftAnimation;
position.x –;
break;
case Direction.right:
animation = _walkingRightAnimation;
position.x ++;
break;
case Direction.none:
animation = _idleAnimation;
break;
}
}
Starting your program and navigating left or right changes the player’s movement, making it appear more natural.
Congratulations, you have just created your first easy game with Flame!
High-Tech Methods
To create games that differentiate themselves from others and are interesting and immersive, one must master sophisticated approaches. Creating unique game objects and actions, integrating Firebase for cloud services and networking, implementing AI and pathfinding methods, and testing and debugging Flutter Flame games are all topics covered in this series installment.
We may add distinctive gameplay dynamics to our games by designing bespoke objects in the scene and behaviors. We can build unique game objects by modifying the SpriteComponent class in Flutter Flame and adding our custom update and render methods. Using mixins also enables us to include functionality in your graphical interfaces without changing any code, allowing us to build new behaviors.
We can add cloud services and multiplayer functionality to our game by integrating with Firebase. We may use Firebase to develop real-time multiplayer by leveraging its real-time databases and verification capabilities and storing game data like high scores and accolades, including player logs and accounts in the cloud.
Our games may get more dynamic and difficult by including AI and pathfinding systems. The Al algorithm and Steering Characteristics package are the only tools and frameworks Flutter Flame offers that we may utilize to construct AI and pathfinding. With these tools, we can design opponents and other game elements that behave logically and react to player input.
An essential step in the design process is testing and debugging Flutter Flame games. Flutter Flame offers many testing and debugging tools, such as the DevTools debugger and the Flutter Driver testing framework. These tools enable us to debug, test, and ensure that the game code functions correctly across various platforms and devices. You can check here top mobile app development trends this year.
Conclusion
This series has addressed the rapid and simple creation of 2D games with Flutter Flame. The fundamentals of building a Flutter Flame project, developing the game environment, and applying more sophisticated strategies like connecting with Firebase and implementing AI and pathfinding ai techniques have all been addressed.
Flutter Flame provides numerous benefits for game creation, such as its cross-platform interoperability, efficiency, and ease of use. Using Flutter Flame, there are countless opportunities to develop original games that players will like. We strongly advise you about app developers for hire from Flutter Agency when using Flutter Flame if you are trying to create a 2D game.