mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-29 07:56:53 +08:00
This adds a platformer tutorial called Ember Quest. I hope I have done a service, because I am tired, lol. I am sure there will be comments. I just want to say, I did my best. I approached this as someone new to Flame, just like I was about 10 months ago. Are there concepts that can be improved, sure. We can always optimize code, but I didn't want any concepts to be super abstract. I had never coded a game before when I began my journey with Flame this year, so things might be a bit simple for experienced game developers, but for myself, I had never even thought about a game loop or animations, etc.
323 lines
8.3 KiB
Markdown
323 lines
8.3 KiB
Markdown
# 7. Adding Menus
|
|
|
|
To add menus to the game, we will leverage Flame's built-in
|
|
[overlay](../../flame/game.md#flutter-widgets-and-game-instances) system.
|
|
|
|
|
|
## Main Menu
|
|
|
|
In the `lib/overlays` folder, create `main_menu.dart` and add the following code:
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../ember_quest.dart';
|
|
|
|
class MainMenu extends StatelessWidget {
|
|
// Reference to parent game.
|
|
final EmberQuestGame game;
|
|
|
|
const MainMenu({super.key, required this.game});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const blackTextColor = Color.fromRGBO(0, 0, 0, 1.0);
|
|
const whiteTextColor = Color.fromRGBO(255, 255, 255, 1.0);
|
|
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Center(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(10.0),
|
|
height: 250,
|
|
width: 300,
|
|
decoration: const BoxDecoration(
|
|
color: blackTextColor,
|
|
borderRadius: const BorderRadius.all(
|
|
Radius.circular(20),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Ember Quest',
|
|
style: TextStyle(
|
|
color: whiteTextColor,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 40),
|
|
SizedBox(
|
|
width: 200,
|
|
height: 75,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
game.overlays.remove('MainMenu');
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: whiteTextColor,
|
|
),
|
|
child: const Text(
|
|
'Play',
|
|
style: TextStyle(
|
|
fontSize: 40.0,
|
|
color: blackTextColor,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
const SizedBox(height: 20),
|
|
const Text(
|
|
'Use WASD or Arrow Keys for movement. Space bar to jump.
|
|
Collect as many stars as you can and avoid enemies!',
|
|
textAlign: TextAlign.center,
|
|
style: TextStyle(
|
|
color: whiteTextColor,
|
|
fontSize: 14,
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
```
|
|
|
|
This is a pretty self-explanatory file that just uses standard Flutter widgets to display
|
|
information and provide a `Play` button. The only Flame-related line is
|
|
`game.overlays.remove('MainMenu');` which simply removes the overlay so the user can play the
|
|
game. It should be noted that the user can technically move Ember while this is displayed, but
|
|
trapping the input is outside the scope of this tutorial as there are multiple ways this can be
|
|
accomplished.
|
|
|
|
|
|
## Game Over Menu
|
|
|
|
Next, create a file called `lib/overlays/game_over.dart` and add the following code:
|
|
|
|
```dart
|
|
import 'package:flutter/material.dart';
|
|
|
|
import '../ember_quest.dart';
|
|
|
|
class GameOver extends StatelessWidget {
|
|
// Reference to parent game.
|
|
final EmberQuestGame game;
|
|
const GameOver({super.key, required this.game});
|
|
|
|
@override
|
|
Widget build(BuildContext context) {
|
|
const blackTextColor = Color.fromRGBO(0, 0, 0, 1.0);
|
|
const whiteTextColor = Color.fromRGBO(255, 255, 255, 1.0);
|
|
|
|
return Material(
|
|
color: Colors.transparent,
|
|
child: Center(
|
|
child: Container(
|
|
padding: const EdgeInsets.all(10.0),
|
|
height: 200,
|
|
width: 300,
|
|
decoration: const BoxDecoration(
|
|
color: blackTextColor,
|
|
borderRadius: const BorderRadius.all(
|
|
Radius.circular(20),
|
|
),
|
|
),
|
|
child: Column(
|
|
mainAxisAlignment: MainAxisAlignment.center,
|
|
children: [
|
|
const Text(
|
|
'Game Over',
|
|
style: TextStyle(
|
|
color: whiteTextColor,
|
|
fontSize: 24,
|
|
),
|
|
),
|
|
const SizedBox(height: 40),
|
|
SizedBox(
|
|
width: 200,
|
|
height: 75,
|
|
child: ElevatedButton(
|
|
onPressed: () {
|
|
game.reset();
|
|
game.overlays.remove('GameOver');
|
|
},
|
|
style: ElevatedButton.styleFrom(
|
|
backgroundColor: whiteTextColor,
|
|
),
|
|
child: const Text(
|
|
'Play Again',
|
|
style: TextStyle(
|
|
fontSize: 28.0,
|
|
color: blackTextColor,
|
|
),
|
|
),
|
|
),
|
|
),
|
|
],
|
|
),
|
|
),
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
As with the Main Menu, this is all standard Flutter widgets except for the call to remove the
|
|
overlay and also the call to `game.reset()` which we will create now.
|
|
|
|
Open `lib/ember_quest.dart` and add / update the following code:
|
|
|
|
```dart
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await images.loadAll([
|
|
'block.png',
|
|
'ember.png',
|
|
'ground.png',
|
|
'heart_half.png',
|
|
'heart.png',
|
|
'star.png',
|
|
'water_enemy.png',
|
|
]);
|
|
initializeGame(true);
|
|
}
|
|
|
|
void initializeGame(bool loadHud) {
|
|
// Assume that size.x < 3200
|
|
final segmentsToLoad = (size.x / 640).ceil();
|
|
segmentsToLoad.clamp(0, segments.length);
|
|
|
|
for (var i = 0; i <= segmentsToLoad; i++) {
|
|
loadGameSegments(i, (640 * i).toDouble());
|
|
}
|
|
|
|
_ember = EmberPlayer(
|
|
position: Vector2(128, canvasSize.y - 128),
|
|
);
|
|
add(_ember);
|
|
if (loadHud) {
|
|
add(Hud());
|
|
}
|
|
}
|
|
|
|
void reset() {
|
|
starsCollected = 0;
|
|
health = 3;
|
|
initializeGame(false);
|
|
}
|
|
```
|
|
|
|
You may notice that we have added a parameter to the `initializeGame` method which allows us to
|
|
bypass adding the HUD to the game. This is because in the coming section, when Ember's health drops
|
|
to 0, we will wipe the game, but we do not need to remove the HUD, as we just simply need to reset
|
|
the values using `reset()`.
|
|
|
|
|
|
## Displaying the Menus
|
|
|
|
To display the menus, add the following code to `lib/main.dart`:
|
|
|
|
```dart
|
|
void main() {
|
|
runApp(
|
|
GameWidget<EmberQuestGame>.controlled(
|
|
gameFactory: EmberQuestGame.new,
|
|
overlayBuilderMap: {
|
|
'MainMenu': (_, game) => MainMenu(game: game),
|
|
'GameOver': (_, game) => GameOver(game: game),
|
|
},
|
|
initialActiveOverlays: const ['MainMenu'],
|
|
),
|
|
);
|
|
}
|
|
```
|
|
|
|
If the menus did not auto-import, add the following:
|
|
|
|
```dart
|
|
import 'overlays/game_over.dart';
|
|
import 'overlays/main_menu.dart';
|
|
```
|
|
|
|
If you run the game now, you should be greeted with the Main Menu overlay. Pressing play will
|
|
remove it and allow you to start playing the game.
|
|
|
|
|
|
### Health Check for Game Over
|
|
|
|
Our last step to finish Ember Quest is to add a game-over mechanism. This is fairly simple but
|
|
requires us to place similar code in all of our components. So let's get started!
|
|
|
|
In `lib/actors/ember.dart`, in the `update` method, add the following:
|
|
|
|
```dart
|
|
// If ember fell in pit, then game over.
|
|
if (position.y > game.size.y + size.y) {
|
|
game.health = 0;
|
|
}
|
|
|
|
if (game.health <= 0) {
|
|
removeFromParent();
|
|
}
|
|
```
|
|
|
|
In `lib/actors/water_enemy.dart`, in the `update` method update the following code:
|
|
|
|
```dart
|
|
if (position.x < -size.x || game.health <= 0) {
|
|
removeFromParent();
|
|
}
|
|
```
|
|
|
|
In `lib/objects/ground_block.dart`, in the `update` method update the following code:
|
|
|
|
```dart
|
|
if (game.health <= 0) {
|
|
removeFromParent();
|
|
}
|
|
```
|
|
|
|
In `lib/objects/platform_block.dart`, in the `update` method update the following code:
|
|
|
|
```dart
|
|
if (position.x < -size.x || game.health <= 0) {
|
|
removeFromParent();
|
|
}
|
|
```
|
|
|
|
In `lib/objects/star.dart`, in the `update` method update the following code:
|
|
|
|
```dart
|
|
if (position.x < -size.x || game.health <= 0) {
|
|
removeFromParent();
|
|
}
|
|
```
|
|
|
|
Finally, in `lib/ember_quest.dart`, add the following `update` method:
|
|
|
|
```dart
|
|
@override
|
|
void update(double dt) {
|
|
if (health <= 0) {
|
|
overlays.add('GameOver');
|
|
}
|
|
super.update(dt);
|
|
}
|
|
```
|
|
|
|
|
|
## Congratulations
|
|
|
|
You made it! You have a working Ember Quest. Press the button below to see what the resulting code
|
|
looks like or to play it live.
|
|
|
|
```{flutter-app}
|
|
:sources: ../tutorials/platformer/app
|
|
:show: popup code
|
|
```
|