mirror of
https://github.com/flame-engine/flame.git
synced 2025-10-31 00:48:47 +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.
175 lines
5.8 KiB
Markdown
175 lines
5.8 KiB
Markdown
# 2. Start Coding
|
|
|
|
|
|
## The Plan
|
|
|
|
Now that we have the assets loaded and a very rough idea of what classes we will need, we need to
|
|
start thinking about how we will implement this game and our goals. To do this, let's break down
|
|
what the game should do:
|
|
|
|
- Ember should be able to be controlled to move left, right, and jump.
|
|
- The level will be infinite, so we need a way to randomly load sections of the level.
|
|
- The objective is to collect stars while avoiding enemies.
|
|
- Enemies cannot be killed as you need to use the platforms to avoid them.
|
|
- If Ember is hit by an enemy, it should reduce Ember's health by 1.
|
|
- Ember should have 3 lives to lose.
|
|
- There should be pits that if Ember falls into, it is automatically game over.
|
|
- There should be a main menu and a game-over screen that lets the player start over.
|
|
|
|
Now that this is planned out, I know you are probably as excited as I am to begin and I just want to
|
|
see Ember on the screen. So let's do that first.
|
|
|
|
```{note}
|
|
Why did I choose to make this game an infinite side scrolling platformer?
|
|
|
|
Well, I wanted to be able to showcase random level loading. No two game plays
|
|
will be the same. This exact setup can easily be adapted to be a traditional
|
|
level game. As you make your way through this tutorial, you will see how we
|
|
could modify the level code to have an end. I will add a note in that section
|
|
to explain the appropriate mechanics.
|
|
```
|
|
|
|
|
|
## Loading Assets
|
|
|
|
For Ember to be displayed, we will need to load the assets. This can be done in `main.dart`, but by
|
|
so doing, we will quickly clutter the file. To keep our game organized, we should create files that
|
|
have a single focus. So let's create a file in the `lib` folder called `ember_quest.dart`. In that
|
|
file, we will add:
|
|
|
|
```dart
|
|
import 'package:flame/game.dart';
|
|
|
|
class EmberQuestGame extends FlameGame {
|
|
EmberQuestGame();
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await images.loadAll([
|
|
'block.png',
|
|
'ember.png',
|
|
'ground.png',
|
|
'heart_half.png',
|
|
'heart.png',
|
|
'star.png',
|
|
'water_enemy.png',
|
|
]);
|
|
|
|
}
|
|
}
|
|
```
|
|
|
|
As I mentioned in the [assets](step_1.md#assets) section, we are using multiple individual image
|
|
files and for performance reasons, we should leverage Flame's built-in caching system which will
|
|
only load the files once, but allow us to access them as many times as needed without an impact to
|
|
the game. `await images.loadAll()` takes a list of the file names that are found in `assets\images`
|
|
and loads them to cache.
|
|
|
|
|
|
## Scaffolding
|
|
|
|
So now that we have our game file, let's prepare the `main.dart` file to receive our newly created
|
|
`FlameGame`. Change your entire `main.dart` file to the following:
|
|
|
|
```dart
|
|
import 'package:flame/game.dart';
|
|
import 'package:flutter/material.dart';
|
|
|
|
import 'ember_quest.dart';
|
|
|
|
void main() {
|
|
runApp(
|
|
const GameWidget<EmberQuestGame>.controlled(
|
|
gameFactory: EmberQuestGame.new,
|
|
),
|
|
);
|
|
}
|
|
```
|
|
|
|
You can run this file and you should just have a blank screen now. Let's get Ember loaded!
|
|
|
|
|
|
## Ember Time
|
|
|
|
Keeping your game files organized can always be a challenge. I like to keep things logically
|
|
organized by how they will be involved in my game. So for Ember, let's create the following folder,
|
|
`lib/actors` and in that folder, create `ember.dart`. In that file, add the following code:
|
|
|
|
```dart
|
|
import 'package:flame/components.dart';
|
|
|
|
import '../ember_quest.dart';
|
|
|
|
class EmberPlayer extends SpriteAnimationComponent
|
|
with HasGameRef<EmberQuestGame> {
|
|
EmberPlayer({
|
|
required super.position,
|
|
}) : super(size: Vector2.all(64), anchor: Anchor.center);
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
animation = SpriteAnimation.fromFrameData(
|
|
game.images.fromCache('ember.png'),
|
|
SpriteAnimationData.sequenced(
|
|
amount: 4,
|
|
textureSize: Vector2.all(16),
|
|
stepTime: 0.12,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
```
|
|
|
|
This file uses the `HasGameRef` mixin which allows us to reach back to `ember_quest.dart` and
|
|
leverage any of the variables or methods that are defined in the game class. You can see this in
|
|
use with the line `game.images.fromCache('ember.png')`. Earlier, we loaded all the files into
|
|
cache, so to use that file now, we call `fromCache` so it can be leveraged by the `SpriteAnimation`.
|
|
The `EmberPlayer` class is extending a `SpriteAnimationComponent` which allows us to define
|
|
animation as well as position it accordingly in our game world. When we construct this class, the
|
|
default size of `Vector2.all(64)` is defined as the size of Ember in our game world should be 64x64.
|
|
You may notice that in the animation `SpriteAnimationData`, the `textureSize` is defined as
|
|
`Vector2.all(16)` or 16x16. This is because the individual frame in our `ember.png` is 16x16 and
|
|
there are 4 frames in total. To define the speed of the animation, `stepTime` is used and set at
|
|
`0.12` seconds per frame. You can change the `stepTime` to any length that makes the animation seem
|
|
correct for your game vision.
|
|
|
|
Now before you rush to run the game again, we have to add Ember to the game world. To do this, go
|
|
back to `ember_quest.dart` and add the following:
|
|
|
|
```dart
|
|
import 'package:flame/game.dart';
|
|
|
|
import 'actors/ember.dart';
|
|
|
|
class EmberQuestGame extends FlameGame {
|
|
EmberQuestGame();
|
|
|
|
late EmberPlayer _ember;
|
|
|
|
@override
|
|
Future<void> onLoad() async {
|
|
await images.loadAll([
|
|
'block.png',
|
|
'ember.png',
|
|
'ground.png',
|
|
'heart_half.png',
|
|
'heart.png',
|
|
'star.png',
|
|
'water_enemy.png',
|
|
]);
|
|
_ember = EmberPlayer(
|
|
position: Vector2(128, canvasSize.y - 70),
|
|
);
|
|
add(_ember);
|
|
}
|
|
}
|
|
```
|
|
|
|
Run your game now and you should now see Ember flickering in the lower left-hand corner.
|
|
|
|
|
|
## Building Blocks
|
|
|
|
Now that we have Ember showing on screen and we know our basic environment is all working correctly,
|
|
it's time to create a world for Embers Quest! Proceed on to [](step_3.md)!
|