Adding Enemies¶
Now that the starship is able to shoot, we need something for the player to shoot at! So for this step we will work on adding enemies to the game.
First, let’s create an Enemy class that will represent the enemies in game. Right-click the
image below, choose “Save as…”, and store it as enemy.png in your assets/images/ folder:

class Enemy extends SpriteAnimationComponent with HasGameRef<SpaceShooterGame> {
Enemy({
super.position,
}) : super(
size: Vector2.all(enemySize),
anchor: Anchor.center,
);
static const enemySize = 50.0;
@override
Future<void> onLoad() async {
await super.onLoad();
animation = await gameRef.loadSpriteAnimation(
'assets/images/enemy.png',
SpriteAnimationData.sequenced(
amount: 4,
stepTime: .2,
textureSize: Vector2.all(16),
),
);
}
@override
void update(double dt) {
super.update(dt);
position.y += dt * 250;
if (position.y > gameRef.size.y) {
removeFromParent();
}
}
}
Note that for now, the Enemy class is super similar to the Bullet one, the only differences are
their sizes, animation information and that bullets travel from bottom to top, while enemies travel from
top to bottom, so nothing new here.
Next we need to make the enemies spawn in the game, the logic here will be simple:
we will make enemies spawn from the top of the screen at a random position on the x axis.
Once again, we could manually add all the time based events in the game’s update() method, maintain
a random instance to get the enemy x position and so on and so forth, but Flame provides us with a
way to avoid having to write all that by ourselves: we can use the SpawnComponent! So in the
SpaceShooterGame.onLoad() method let’s add the following code:
add(
SpawnComponent(
factory: (index) {
return Enemy();
},
period: 1,
area: Rectangle.fromLTWH(0, 0, size.x, -Enemy.enemySize),
),
);
The SpawnComponent will take a couple of arguments, let’s review them as they appear in the code:
factoryreceives a function which has the index of the component that should be created. We don’t use the index in our code, but it is useful to create more advanced spawn routines. This function should return the created component, in our case a new instance ofEnemy.periodsimply define the interval in which a new component will be spawned.areadefines the possible area where the components can be placed once created. In our case they should be placed in the area above the screen top, so they can be seen as they are arriving into the playable area.
And this concludes this short step!
1import 'package:flame/components.dart';
2import 'package:flame/events.dart';
3import 'package:flame/experimental.dart';
4import 'package:flame/game.dart';
5import 'package:flame/parallax.dart';
6import 'package:flutter/material.dart';
7
8void main() {
9 runApp(GameWidget(game: SpaceShooterGame()));
10}
11
12class SpaceShooterGame extends FlameGame with DragCallbacks {
13 late Player player;
14
15 @override
16 Future<void> onLoad() async {
17 final parallax = await loadParallaxComponent(
18 [
19 ParallaxImageData('assets/images/stars_0.png'),
20 ParallaxImageData('assets/images/stars_1.png'),
21 ParallaxImageData('assets/images/stars_2.png'),
22 ],
23 baseVelocity: Vector2(0, -5),
24 repeat: ImageRepeat.repeat,
25 velocityMultiplierDelta: Vector2(0, 5),
26 );
27 add(parallax);
28
29 player = Player();
30 add(player);
31
32 add(
33 SpawnComponent(
34 factory: (index) {
35 return Enemy();
36 },
37 period: 1,
38 area: Rectangle.fromLTWH(0, 0, size.x, -Enemy.enemySize),
39 ),
40 );
41 }
42
43 @override
44 void onDragUpdate(DragUpdateEvent event) {
45 player.move(event.localDelta);
46 }
47
48 @override
49 void onDragStart(DragStartEvent event) {
50 super.onDragStart(event);
51 player.startShooting();
52 }
53
54 @override
55 void onDragEnd(DragEndEvent event) {
56 super.onDragEnd(event);
57 player.stopShooting();
58 }
59}
60
61class Player extends SpriteAnimationComponent
62 with HasGameRef<SpaceShooterGame> {
63 Player()
64 : super(
65 size: Vector2(100, 150),
66 anchor: Anchor.center,
67 );
68
69 late final SpawnComponent _bulletSpawner;
70
71 @override
72 Future<void> onLoad() async {
73 await super.onLoad();
74
75 animation = await gameRef.loadSpriteAnimation(
76 'assets/images/player.png',
77 SpriteAnimationData.sequenced(
78 amount: 4,
79 stepTime: 0.2,
80 textureSize: Vector2(32, 48),
81 ),
82 );
83
84 position = gameRef.size / 2;
85
86 _bulletSpawner = SpawnComponent(
87 period: 0.2,
88 selfPositioning: true,
89 factory: (index) {
90 return Bullet(
91 position:
92 position +
93 Vector2(
94 0,
95 -height / 2,
96 ),
97 );
98 },
99 autoStart: false,
100 );
101
102 gameRef.add(_bulletSpawner);
103 }
104
105 void move(Vector2 delta) {
106 position.add(delta);
107 }
108
109 void startShooting() {
110 _bulletSpawner.timer.start();
111 }
112
113 void stopShooting() {
114 _bulletSpawner.timer.stop();
115 }
116}
117
118class Bullet extends SpriteAnimationComponent
119 with HasGameRef<SpaceShooterGame> {
120 Bullet({
121 super.position,
122 }) : super(
123 size: Vector2(25, 50),
124 anchor: Anchor.center,
125 );
126
127 @override
128 Future<void> onLoad() async {
129 await super.onLoad();
130
131 animation = await gameRef.loadSpriteAnimation(
132 'assets/images/bullet.png',
133 SpriteAnimationData.sequenced(
134 amount: 4,
135 stepTime: 0.2,
136 textureSize: Vector2(8, 16),
137 ),
138 );
139 }
140
141 @override
142 void update(double dt) {
143 super.update(dt);
144
145 position.y += dt * -500;
146
147 if (position.y < -height) {
148 removeFromParent();
149 }
150 }
151}
152
153class Enemy extends SpriteAnimationComponent with HasGameRef<SpaceShooterGame> {
154 Enemy({
155 super.position,
156 }) : super(
157 size: Vector2.all(enemySize),
158 anchor: Anchor.center,
159 );
160
161 static const enemySize = 50.0;
162
163 @override
164 Future<void> onLoad() async {
165 await super.onLoad();
166
167 animation = await gameRef.loadSpriteAnimation(
168 'assets/images/enemy.png',
169 SpriteAnimationData.sequenced(
170 amount: 4,
171 stepTime: 0.2,
172 textureSize: Vector2.all(16),
173 ),
174 );
175 }
176
177 @override
178 void update(double dt) {
179 super.update(dt);
180
181 position.y += dt * 250;
182
183 if (position.y > gameRef.size.y) {
184 removeFromParent();
185 }
186 }
187}