mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
docs: Update platformer tutorial to latest Flame (#2904)
Updates the platformer tutorial to Flame v1.11.0
This commit is contained in:
1
.github/.cspell/gamedev_dictionary.txt
vendored
1
.github/.cspell/gamedev_dictionary.txt
vendored
@ -45,7 +45,6 @@ raycasts # plural of raycast
|
||||
raytrace # act of raytracing
|
||||
raytracing # rendering techniques that calculates light rays as straight lines
|
||||
rects # plural of rect
|
||||
refactorings # plural of refactoring
|
||||
respawned # past tense of respawn
|
||||
respawn # when the player character dies and is brought back after some time and penalties
|
||||
RGBA # red green blue alpha
|
||||
|
||||
2
.github/.cspell/words_dictionary.txt
vendored
2
.github/.cspell/words_dictionary.txt
vendored
@ -2,8 +2,6 @@
|
||||
bloodlust
|
||||
collidable
|
||||
collidables
|
||||
composability
|
||||
discoverability
|
||||
draggables
|
||||
focusable
|
||||
gamepad
|
||||
|
||||
@ -23,7 +23,7 @@ void main() {
|
||||
|
||||
Like in the [klondike](../klondike/klondike.md) tutorial, starting a new game can feel overwhelming.
|
||||
I like to first decide what platform I am trying to target. Will this be a mobile game, a desktop
|
||||
game, or maybe a web game, with Flutter and Flame, these are all possible. For this game though, I
|
||||
game, or maybe a web game, with Flutter and Flame, these are all possible. For this game though, I
|
||||
am going to focus on a web game. This means my users will interact with the game using their
|
||||
keyboards.
|
||||
|
||||
@ -45,16 +45,16 @@ All of these will be brought together in `EmberQuestGame` derived from `FlameGam
|
||||
|
||||
## Assets
|
||||
|
||||
Every game needs assets. Assets are images, sprites, animations, sounds, etc. Now, I am not an
|
||||
Every game needs assets. Assets are images, sprites, animations, sounds, etc. Now, I am not an
|
||||
artist, but because I am basing this game on Ember, the flame mascot, and Ember is already designed,
|
||||
it sets the tone that this will be a pixel art game. There are numerous sites available that
|
||||
it sets the tone that this will be a pixel art game. There are numerous sites available that
|
||||
provide free pixel art that can be used in games, but please check and comply with the licensing and
|
||||
always provide valid creator attribution. For this game though, I am going to take a chance and
|
||||
make my artwork using an online pixel art tool. If you decide to use this tool, multiple online
|
||||
tutorials will assist you with the basic operations as well as exporting the assets. Now normally,
|
||||
most games will utilize sprite sheets. These combine many images into one larger image that can be
|
||||
sectioned and used as individual images. For this tutorial though, I specifically will save the
|
||||
images individually as I want to demonstrate the Flame engine's caching abilities. Ember and the
|
||||
always provide valid creator attribution. For this game though, I am going to take a chance and
|
||||
make my artwork using an online pixel art tool. If you decide to use this tool, multiple online
|
||||
tutorials will assist you with the basic operations as well as exporting the assets. Now normally,
|
||||
most games will utilize sprite sheets. These combine many images into one larger image that can be
|
||||
sectioned and used as individual images. For this tutorial though, I specifically will save the
|
||||
images individually as I want to demonstrate the Flame engine's caching abilities. Ember and the
|
||||
water enemy are sprite sheets though as they contain multiple images to create animations.
|
||||
|
||||
Right-click the images below, choose "Save as...", and store them in the `assets/images` folder of the
|
||||
@ -88,8 +88,8 @@ emberquest/
|
||||
You may ask, why are the images different sizes?
|
||||
|
||||
As I was using the online tool to make the assets, I had trouble getting the
|
||||
detail I desired for the game in a 16x16 block. The heart worked out in 32x32
|
||||
and the ground as well as the star were 64x64. Regardless, the asset size does
|
||||
detail I desired for the game in a 16x16 block. The heart worked out in 32x32
|
||||
and the ground as well as the star were 64x64. Regardless, the asset size does
|
||||
not matter for the game as we will resize as needed.
|
||||
```
|
||||
|
||||
|
||||
@ -91,20 +91,15 @@ You can run this file and you should just have a blank screen now. Let's get Emb
|
||||
|
||||
## CameraComponent and World
|
||||
|
||||
Since `FlameGame.camera` is deprecated, we want to add a `CameraComponent` that we can move around,
|
||||
and a world that we can add all our components to and move around our player in.
|
||||
(The `CameraComponent` will be built-in in Flame v2).
|
||||
To move around in the world we are going the use the built-in `CameraComponent` and `World` that
|
||||
exists on the `FlameGame` class.
|
||||
We are going to add all our components to the `world` and follow the player with the `camera`.
|
||||
|
||||
```dart
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
class EmberQuestGame extends FlameGame {
|
||||
EmberQuestGame();
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await images.loadAll([
|
||||
@ -117,12 +112,10 @@ class EmberQuestGame extends FlameGame {
|
||||
'water_enemy.png',
|
||||
]);
|
||||
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
// Everything in this tutorial assumes that the position
|
||||
// of the `CameraComponent`s viewfinder (where the camera is looking)
|
||||
// is in the top left corner, that's why we set the anchor here.
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
camera.viewfinder.anchor = Anchor.topLeft;
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -140,7 +133,7 @@ import 'package:flame/components.dart';
|
||||
import '../ember_quest.dart';
|
||||
|
||||
class EmberPlayer extends SpriteAnimationComponent
|
||||
with HasGameRef<EmberQuestGame> {
|
||||
with HasGameReference<EmberQuestGame> {
|
||||
EmberPlayer({
|
||||
required super.position,
|
||||
}) : super(size: Vector2.all(64), anchor: Anchor.center);
|
||||
@ -181,13 +174,8 @@ import 'package:flame/game.dart';
|
||||
import 'actors/ember.dart';
|
||||
|
||||
class EmberQuestGame extends FlameGame {
|
||||
EmberQuestGame();
|
||||
|
||||
late EmberPlayer _ember;
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await images.loadAll([
|
||||
@ -200,9 +188,7 @@ class EmberQuestGame extends FlameGame {
|
||||
'water_enemy.png',
|
||||
]);
|
||||
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
camera.viewfinder.anchor = Anchor.topLeft;
|
||||
|
||||
_ember = EmberPlayer(
|
||||
position: Vector2(128, canvasSize.y - 70),
|
||||
|
||||
@ -26,17 +26,17 @@ so first create a new folder called `lib/objects`. In that folder, create 3 file
|
||||
boilerplate code for the class, so create the following in their respective files:
|
||||
|
||||
```dart
|
||||
class GroundBlock{}
|
||||
class GroundBlock {}
|
||||
|
||||
class PlatformBlock{}
|
||||
class PlatformBlock {}
|
||||
|
||||
class Star{}
|
||||
class Star {}
|
||||
```
|
||||
|
||||
Also, create `water_enemy.dart` in the `lib/actors` folder using this boilerplate code:
|
||||
|
||||
```dart
|
||||
class WaterEnemy{}
|
||||
class WaterEnemy {}
|
||||
```
|
||||
|
||||
Now we can create a file called `segment_manager.dart` which will be placed in a new folder called
|
||||
@ -300,10 +300,7 @@ as:
|
||||
'water_enemy.png',
|
||||
]);
|
||||
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
camera.viewfinder.anchor = Anchor.topLeft;
|
||||
initializeGame();
|
||||
}
|
||||
```
|
||||
@ -329,7 +326,7 @@ import 'package:flame/components.dart';
|
||||
import '../ember_quest.dart';
|
||||
|
||||
class PlatformBlock extends SpriteComponent
|
||||
with HasGameRef<EmberQuestGame> {
|
||||
with HasGameReference<EmberQuestGame> {
|
||||
final Vector2 gridPosition;
|
||||
double xOffset;
|
||||
|
||||
@ -433,11 +430,11 @@ the block in a `Vector2`. So add the following to your `loadGameSegments` method
|
||||
|
||||
```dart
|
||||
case PlatformBlock:
|
||||
add(PlatformBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
));
|
||||
break;
|
||||
add(PlatformBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
));
|
||||
break;
|
||||
```
|
||||
|
||||
If you run your code, you should now see:
|
||||
@ -453,7 +450,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
@override
|
||||
Color backgroundColor() {
|
||||
return const Color.fromARGB(255, 173, 223, 247);
|
||||
return const Color.fromARGB(255, 173, 223, 247);
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
@ -17,7 +17,7 @@ import 'package:flutter/material.dart';
|
||||
import '../ember_quest.dart';
|
||||
|
||||
class Star extends SpriteComponent
|
||||
with HasGameRef<EmberQuestGame> {
|
||||
with HasGameReference<EmberQuestGame> {
|
||||
final Vector2 gridPosition;
|
||||
double xOffset;
|
||||
|
||||
@ -65,7 +65,7 @@ So the only change between the Star and the Platform beyond the anchor is simply
|
||||
```dart
|
||||
add(
|
||||
SizeEffect.by(
|
||||
Vector2(-24, -24),
|
||||
Vector2(-24, -24),
|
||||
EffectController(
|
||||
duration: .75,
|
||||
reverseDuration: .5,
|
||||
@ -76,24 +76,24 @@ add(
|
||||
);
|
||||
```
|
||||
|
||||
The `SizeEffect` is best explained by going to their [help
|
||||
docs](../../flame/effects.md#sizeeffectby). In short, we simply reduce the size of the star
|
||||
The `SizeEffect` is best explained by going to their
|
||||
[docs](../../flame/effects.md#sizeeffectby). In short, we simply reduce the size of the star
|
||||
by -24 pixels in both directions and we make it pulse infinitely using the `EffectController`.
|
||||
|
||||
Don't forget to add the star to your `lib/ember_quest.dart` file by doing:
|
||||
|
||||
```dart
|
||||
case Star:
|
||||
add(
|
||||
Star(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
),
|
||||
);
|
||||
break;
|
||||
world.add(
|
||||
Star(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
),
|
||||
);
|
||||
break;
|
||||
```
|
||||
|
||||
If you run your game, you should now see pulsing stars!
|
||||
If you run your game, you should now see pulsating stars!
|
||||
|
||||
|
||||
## Water Enemy
|
||||
@ -109,7 +109,7 @@ import 'package:flame/effects.dart';
|
||||
import '../ember_quest.dart';
|
||||
|
||||
class WaterEnemy extends SpriteAnimationComponent
|
||||
with HasGameRef<EmberQuestGame> {
|
||||
with HasGameReference<EmberQuestGame> {
|
||||
final Vector2 gridPosition;
|
||||
double xOffset;
|
||||
|
||||
@ -171,7 +171,7 @@ Don't forget to add the water enemy to your `lib/ember_quest.dart` file by doing
|
||||
|
||||
```dart
|
||||
case WaterEnemy:
|
||||
add(
|
||||
world.add(
|
||||
WaterEnemy(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
@ -204,7 +204,7 @@ import 'package:flutter/material.dart';
|
||||
|
||||
import '../ember_quest.dart';
|
||||
|
||||
class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
class GroundBlock extends SpriteComponent with HasGameReference<EmberQuestGame> {
|
||||
final Vector2 gridPosition;
|
||||
double xOffset;
|
||||
|
||||
@ -253,8 +253,8 @@ Now in your Ground Block's `onLoad` method, add the following at the end of the
|
||||
|
||||
```dart
|
||||
if (gridPosition.x == 9 && position.x > game.lastBlockXPosition) {
|
||||
game.lastBlockKey = _blockKey;
|
||||
game.lastBlockXPosition = position.x + size.x;
|
||||
game.lastBlockKey = _blockKey;
|
||||
game.lastBlockXPosition = position.x + size.x;
|
||||
}
|
||||
```
|
||||
|
||||
@ -334,7 +334,7 @@ import 'package:flutter/material.dart';
|
||||
import '../ember_quest.dart';
|
||||
import '../managers/segment_manager.dart';
|
||||
|
||||
class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
class GroundBlock extends SpriteComponent with HasGameReference<EmberQuestGame> {
|
||||
final Vector2 gridPosition;
|
||||
double xOffset;
|
||||
|
||||
@ -391,13 +391,13 @@ Finally, don't forget to add your Ground Block to `lib/ember_quest.dart` by addi
|
||||
|
||||
```dart
|
||||
case GroundBlock:
|
||||
add(
|
||||
GroundBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
),
|
||||
);
|
||||
break;
|
||||
world.add(
|
||||
GroundBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
),
|
||||
);
|
||||
break;
|
||||
```
|
||||
|
||||
If you run your code, your game should now look like this:
|
||||
|
||||
@ -20,7 +20,7 @@ class EmberQuestGame extends FlameGame with HasKeyboardHandlerComponents {
|
||||
|
||||
```dart
|
||||
class EmberPlayer extends SpriteAnimationComponent
|
||||
with KeyboardHandler, HasGameRef<EmberQuestGame> {
|
||||
with KeyboardHandler, HasGameReference<EmberQuestGame> {
|
||||
```
|
||||
|
||||
Now we can add a new method:
|
||||
@ -118,7 +118,7 @@ Next, add the `CollisionCallbacks` mixin to `lib/actors/ember.dart` like:
|
||||
|
||||
```dart
|
||||
class EmberPlayer extends SpriteAnimationComponent
|
||||
with KeyboardHandler, CollisionCallbacks, HasGameRef<EmberQuestGame> {
|
||||
with KeyboardHandler, CollisionCallbacks, HasGameReference<EmberQuestGame> {
|
||||
```
|
||||
|
||||
If it did not auto-import, you will need the following:
|
||||
@ -176,9 +176,7 @@ For the collisions to be activated for Ember, we need to add a `CircleHitbox`, s
|
||||
method, add the following:
|
||||
|
||||
```dart
|
||||
add(
|
||||
CircleHitbox(),
|
||||
);
|
||||
add(CircleHitbox());
|
||||
```
|
||||
|
||||
Now that we have the basic collisions created, we can add gravity so Ember exists in a game world
|
||||
|
||||
@ -26,7 +26,7 @@ enum HeartState {
|
||||
}
|
||||
|
||||
class HeartHealthComponent extends SpriteGroupComponent<HeartState>
|
||||
with HasGameRef<EmberQuestGame> {
|
||||
with HasGameReference<EmberQuestGame> {
|
||||
final int heartNumber;
|
||||
|
||||
HeartHealthComponent({
|
||||
@ -88,7 +88,7 @@ import 'package:flutter/material.dart';
|
||||
import '../ember_quest.dart';
|
||||
import 'heart.dart';
|
||||
|
||||
class Hud extends PositionComponent with HasGameRef<EmberQuestGame> {
|
||||
class Hud extends PositionComponent with HasGameReference<EmberQuestGame> {
|
||||
Hud({
|
||||
super.position,
|
||||
super.size,
|
||||
@ -152,7 +152,7 @@ the number of hearts necessary. The last step is to add the hud to the game.
|
||||
Go to `lib/ember_quest.dart` and add the following code in the `initializeGame` method:
|
||||
|
||||
```dart
|
||||
cameraComponent.viewport.add(Hud());
|
||||
camera.viewport.add(Hud());
|
||||
```
|
||||
|
||||
If the auto-import did not occur, you will need to add:
|
||||
|
||||
@ -185,10 +185,8 @@ Future<void> onLoad() async {
|
||||
'star.png',
|
||||
'water_enemy.png',
|
||||
]);
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
camera.viewfinder.anchor = Anchor.topLeft;
|
||||
initializeGame(true);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user