visit
This tutorial will provide you with an introduction to the Flutter Flame game engine. You’ll learn how to set up and build a Flutter Flame game, load sprites, and add animations.
This tutorial assumes that you have a working knowledge of Dart and Flutter.
To get started with Flame, you need to . In your pubspec.yaml
file, add the dependency as shown below:
dependencies:
flame: ^1.1.1
To render a game, you use the GameWidget
. Adding the code snippet below in the main.dart
file renders a Flame game, which is currently a black screen.
void main() {
final game = FlameGame();
runApp(
GameWidget(
game: game,
),
);
}
You are now ready to add some graphics to your game.
To render static images, you’ll need to make use of the SpriteComponent
class. Add your game graphics in the assets/images
folder, and update your pubspec.yaml
file to load the assets. This tutorial contains player and background images that will be loaded.
You’ll create and update the three files below 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),
);
}
First, create a helpers
folder with the files below, and update them as shown:
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;
}
Finally, update the dino_player.dart
file to update the player’s position by including the code snippet below:
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;
}
}
The sprite sheet contains different player frames that can be animated to illustrate actions like walking right or left. The sprite sheet is added to the assets/images
folder.
To animate your player, do the following in the dino_player.dart
file:
SpriteAnimationComponent
instead of SpriteComponent
. late final SpriteAnimation _walkingRightAnimation;
late final SpriteAnimation _walkingLeftAnimation;
late final SpriteAnimation _idleAnimation;
final double _animationSpeed = .15;
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
function selects a sequence of sprites defined by the row
, from
, and to
properties and animates them.
First, override the onLoad
function to load the _idleAnimation
.
@override
Future<void> onLoad() async {
super.onLoad();
await _loadAnimations().then((_) => {animation = _idleAnimation});
}
Then update the updatePosition
function to load different animations based on the direction the player is facing. The sprites for the idle state, right movement, and left movement are provided in this tutorial.
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;
}
}
Your app is ready, and you can now add some settings to determine how your app is built.
To build your app, you need to customize the build settings to suit your app:
Congrats! You just made your first build with Codemagic and can now download and share your app artifacts!