-
-
Notifications
You must be signed in to change notification settings - Fork 944
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
docs: SpaceShooter tutorial step 5 (#2924)
5th step of the space shooter tutorial. --------- Co-authored-by: Lukas Klingsbo <[email protected]>
- Loading branch information
1 parent
8cee80c
commit 7f563d7
Showing
3 changed files
with
271 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
import 'package:flame/components.dart'; | ||
import 'package:flame/events.dart'; | ||
import 'package:flame/experimental.dart'; | ||
import 'package:flame/game.dart'; | ||
import 'package:flame/input.dart'; | ||
import 'package:flame/parallax.dart'; | ||
import 'package:flutter/material.dart'; | ||
|
||
void main() { | ||
runApp(GameWidget(game: SpaceShooterGame())); | ||
} | ||
|
||
class SpaceShooterGame extends FlameGame with PanDetector { | ||
late Player player; | ||
|
||
@override | ||
Future<void> onLoad() async { | ||
final parallax = await loadParallaxComponent( | ||
[ | ||
ParallaxImageData('stars_0.png'), | ||
ParallaxImageData('stars_1.png'), | ||
ParallaxImageData('stars_2.png'), | ||
], | ||
baseVelocity: Vector2(0, -5), | ||
repeat: ImageRepeat.repeat, | ||
velocityMultiplierDelta: Vector2(0, 5), | ||
); | ||
add(parallax); | ||
|
||
player = Player(); | ||
add(player); | ||
|
||
add( | ||
SpawnComponent( | ||
factory: (index) { | ||
return Enemy(); | ||
}, | ||
period: 1, | ||
area: Rectangle.fromLTWH(0, 0, size.x, -Enemy.enemySize), | ||
), | ||
); | ||
} | ||
|
||
@override | ||
void onPanUpdate(DragUpdateInfo info) { | ||
player.move(info.delta.global); | ||
} | ||
|
||
@override | ||
void onPanStart(DragStartInfo info) { | ||
player.startShooting(); | ||
} | ||
|
||
@override | ||
void onPanEnd(DragEndInfo info) { | ||
player.stopShooting(); | ||
} | ||
} | ||
|
||
class Player extends SpriteAnimationComponent | ||
with HasGameReference<SpaceShooterGame> { | ||
late final TimerComponent _bulletSpawner; | ||
|
||
@override | ||
Future<void> onLoad() async { | ||
await super.onLoad(); | ||
|
||
animation = await game.loadSpriteAnimation( | ||
'player.png', | ||
SpriteAnimationData.sequenced( | ||
amount: 4, | ||
stepTime: .2, | ||
textureSize: Vector2(32, 48), | ||
), | ||
); | ||
|
||
position = game.size / 2; | ||
width = 100; | ||
height = 150; | ||
anchor = Anchor.center; | ||
|
||
_bulletSpawner = TimerComponent( | ||
period: .2, | ||
onTick: () { | ||
final bullet = Bullet( | ||
position: position + | ||
Vector2( | ||
0, | ||
-height / 2, | ||
), | ||
); | ||
game.add(bullet); | ||
}, | ||
repeat: true, | ||
autoStart: false, | ||
); | ||
|
||
add(_bulletSpawner); | ||
} | ||
|
||
void move(Vector2 delta) { | ||
position.add(delta); | ||
} | ||
|
||
void startShooting() { | ||
_bulletSpawner.timer.start(); | ||
} | ||
|
||
void stopShooting() { | ||
_bulletSpawner.timer.stop(); | ||
} | ||
} | ||
|
||
class Bullet extends SpriteAnimationComponent | ||
with HasGameReference<SpaceShooterGame> { | ||
Bullet({ | ||
super.position, | ||
}) : super(size: Vector2(25, 50)); | ||
|
||
@override | ||
Future<void> onLoad() async { | ||
await super.onLoad(); | ||
|
||
animation = await game.loadSpriteAnimation( | ||
'bullet.png', | ||
SpriteAnimationData.sequenced( | ||
amount: 4, | ||
stepTime: .2, | ||
textureSize: Vector2(8, 16), | ||
), | ||
); | ||
|
||
width = 25; | ||
height = 50; | ||
anchor = Anchor.center; | ||
} | ||
|
||
@override | ||
void update(double dt) { | ||
super.update(dt); | ||
|
||
position.y += dt * -500; | ||
|
||
if (position.y < -height) { | ||
removeFromParent(); | ||
} | ||
} | ||
} | ||
|
||
class Enemy extends SpriteAnimationComponent | ||
with HasGameReference<SpaceShooterGame> { | ||
Enemy({ | ||
super.position, | ||
}) : super(size: Vector2.all(enemySize)); | ||
|
||
static const enemySize = 50.0; | ||
|
||
@override | ||
Future<void> onLoad() async { | ||
await super.onLoad(); | ||
|
||
animation = await game.loadSpriteAnimation( | ||
'enemy.png', | ||
SpriteAnimationData.sequenced( | ||
amount: 4, | ||
stepTime: .2, | ||
textureSize: Vector2.all(16), | ||
), | ||
); | ||
|
||
width = 50; | ||
height = 50; | ||
anchor = Anchor.center; | ||
} | ||
|
||
@override | ||
void update(double dt) { | ||
super.update(dt); | ||
|
||
position.y += dt * 250; | ||
|
||
if (position.y > game.size.y) { | ||
removeFromParent(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# 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. | ||
|
||
So first things first, let's create an `Enemy` class that will represent the enemies in game: | ||
|
||
```dart | ||
class Enemy extends SpriteAnimationComponent | ||
with HasGameReference<SpaceShooterGame> { | ||
Enemy({ | ||
super.position, | ||
}) : super(size: Vector2.all(enemySize)); | ||
static const enemySize = 50.0; | ||
@override | ||
Future<void> onLoad() async { | ||
await super.onLoad(); | ||
animation = await game.loadSpriteAnimation( | ||
'enemy.png', | ||
SpriteAnimationData.sequenced( | ||
amount: 4, | ||
stepTime: .2, | ||
textureSize: Vector2.all(16), | ||
), | ||
); | ||
width = 50; | ||
height = 50; | ||
anchor = Anchor.center; | ||
} | ||
@override | ||
void update(double dt) { | ||
super.update(dt); | ||
position.y += dt * 250; | ||
if (position.y > game.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 that we will do here will be simple, | ||
we will simply make enemies spawn from the top of the screen at a random position on the `x` axis. | ||
|
||
Once again, we could manually make all the time based event 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 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: | ||
|
||
```dart | ||
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: | ||
|
||
- `factory` receives 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 of `Enemy`. | ||
- `period` simply define the interval in which a new component will be spawned. | ||
- `area` defines 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! |