mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 03:15:43 +08:00
chore: Remove 1.8.0 deprecations (#2538)
Removes all the deprecated methods before 1.8.0 release.
This commit is contained in:
@ -166,12 +166,6 @@ KlondikeGame
|
||||
└─ CameraComponent
|
||||
```
|
||||
|
||||
```{note}
|
||||
The **Camera** system described in this tutorial is different from the
|
||||
"official" camera available as a property of the `FlameGame` class. The latter
|
||||
may become deprecated in the future.
|
||||
```
|
||||
|
||||
For this game I've been drawing my image assets having in mind the dimension of
|
||||
a single card at 1000×1400 pixels. So, this will serve as the reference size for
|
||||
determining the overall layout. Another important measurement that affects the
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@ -23,9 +24,12 @@ class EmberQuestGame extends FlameGame
|
||||
double cloudSpeed = 0.0;
|
||||
double objectSpeed = 0.0;
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
//debugMode = true; //Uncomment to see the bounding boxes
|
||||
//debugMode = true; // Uncomment to see the bounding boxes
|
||||
await images.loadAll([
|
||||
'block.png',
|
||||
'ember.png',
|
||||
@ -35,6 +39,10 @@ class EmberQuestGame extends FlameGame
|
||||
'star.png',
|
||||
'water_enemy.png',
|
||||
]);
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
initializeGame(loadHud: true);
|
||||
}
|
||||
|
||||
@ -55,7 +63,7 @@ class EmberQuestGame extends FlameGame
|
||||
for (final block in segments[segmentIndex]) {
|
||||
switch (block.blockType) {
|
||||
case GroundBlock:
|
||||
add(
|
||||
world.add(
|
||||
GroundBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
@ -63,7 +71,7 @@ class EmberQuestGame extends FlameGame
|
||||
);
|
||||
break;
|
||||
case PlatformBlock:
|
||||
add(
|
||||
world.add(
|
||||
PlatformBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
@ -71,7 +79,7 @@ class EmberQuestGame extends FlameGame
|
||||
);
|
||||
break;
|
||||
case Star:
|
||||
add(
|
||||
world.add(
|
||||
Star(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
@ -79,7 +87,7 @@ class EmberQuestGame extends FlameGame
|
||||
);
|
||||
break;
|
||||
case WaterEnemy:
|
||||
add(
|
||||
world.add(
|
||||
WaterEnemy(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
@ -102,9 +110,9 @@ class EmberQuestGame extends FlameGame
|
||||
_ember = EmberPlayer(
|
||||
position: Vector2(128, canvasSize.y - 128),
|
||||
);
|
||||
add(_ember);
|
||||
world.add(_ember);
|
||||
if (loadHud) {
|
||||
add(Hud());
|
||||
cameraComponent.viewport.add(Hud());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -13,9 +13,7 @@ class Hud extends PositionComponent with HasGameRef<EmberQuestGame> {
|
||||
super.anchor,
|
||||
super.children,
|
||||
super.priority = 5,
|
||||
}) {
|
||||
positionType = PositionType.viewport;
|
||||
}
|
||||
});
|
||||
|
||||
late TextComponent _scoreTextComponent;
|
||||
|
||||
|
||||
@ -58,8 +58,9 @@ class MainMenu extends StatelessWidget {
|
||||
),
|
||||
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!''',
|
||||
'''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,
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
## 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
|
||||
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.
|
||||
@ -17,7 +17,7 @@ what the game should do:
|
||||
- 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.
|
||||
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?
|
||||
@ -25,16 +25,16 @@ 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
|
||||
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
|
||||
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
|
||||
@ -62,14 +62,14 @@ class EmberQuestGame extends FlameGame {
|
||||
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`
|
||||
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:
|
||||
`FlameGame`. Change your entire `main.dart` file to the following:
|
||||
|
||||
```dart
|
||||
import 'package:flame/game.dart';
|
||||
@ -86,14 +86,53 @@ void main() {
|
||||
}
|
||||
```
|
||||
|
||||
You can run this file and you should just have a blank screen now. Let's get Ember loaded!
|
||||
You can run this file and you should just have a blank screen now. Let's get Ember loaded!
|
||||
|
||||
|
||||
## 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).
|
||||
|
||||
```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([
|
||||
'block.png',
|
||||
'ember.png',
|
||||
'ground.png',
|
||||
'heart_half.png',
|
||||
'heart.png',
|
||||
'star.png',
|
||||
'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]);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## 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:
|
||||
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';
|
||||
@ -121,19 +160,19 @@ class EmberPlayer extends SpriteAnimationComponent
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
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
|
||||
`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
|
||||
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
|
||||
@ -145,6 +184,9 @@ class EmberQuestGame extends FlameGame {
|
||||
EmberQuestGame();
|
||||
|
||||
late EmberPlayer _ember;
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
@ -157,10 +199,15 @@ class EmberQuestGame extends FlameGame {
|
||||
'star.png',
|
||||
'water_enemy.png',
|
||||
]);
|
||||
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
_ember = EmberPlayer(
|
||||
position: Vector2(128, canvasSize.y - 70),
|
||||
);
|
||||
add(_ember);
|
||||
world.add(_ember);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -171,4 +218,4 @@ Run your game now and you should now see Ember flickering in the lower left-hand
|
||||
## 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)!
|
||||
it's time to create a world for Embers Quest! Proceed on to [](step_3.md)!
|
||||
|
||||
@ -4,16 +4,16 @@
|
||||
## Creating Segments
|
||||
|
||||
For this world to be infinite, the best way to approach this is to create segments that can be
|
||||
reloaded over and over. To do this, we need a rough sketch of what our level segments will look
|
||||
reloaded over and over. To do this, we need a rough sketch of what our level segments will look
|
||||
like. I have created the following sketch to show what the segments would look like and how they can
|
||||
be repeated:
|
||||
|
||||

|
||||
|
||||
Each segment is a 10x10 grid and each block is 64 pixels x 64 pixels. This means Ember Quest has a
|
||||
height of 640 with an infinite width. In my design, there must always be a ground
|
||||
block at the beginning and the end. Additionally, there must be at least 3 ground blocks that come
|
||||
before an enemy, including if the segment wraps to another segment. This is because the plan is to
|
||||
Each segment is a 10x10 grid and each block is 64 pixels x 64 pixels. This means Ember Quest has a
|
||||
height of 640 with an infinite width. In my design, there must always be a ground
|
||||
block at the beginning and the end. Additionally, there must be at least 3 ground blocks that come
|
||||
before an enemy, including if the segment wraps to another segment. This is because the plan is to
|
||||
have the enemies traverse back and forth for 3 blocks. Now that we have a plan for the segments,
|
||||
let's create a segment manager class.
|
||||
|
||||
@ -21,8 +21,8 @@ let's create a segment manager class.
|
||||
### Segment Manager
|
||||
|
||||
To get started, we have to understand that we will be referencing our blocks in the segment manager,
|
||||
so first create a new folder called `lib/objects`. In that folder, create 3 files called
|
||||
`ground_block.dart`, `platform_block.dart`, and `star.dart`. Those files just need basic
|
||||
so first create a new folder called `lib/objects`. In that folder, create 3 files called
|
||||
`ground_block.dart`, `platform_block.dart`, and `star.dart`. Those files just need basic
|
||||
boilerplate code for the class, so create the following in their respective files:
|
||||
|
||||
```dart
|
||||
@ -40,8 +40,8 @@ class WaterEnemy{}
|
||||
```
|
||||
|
||||
Now we can create a file called `segment_manager.dart` which will be placed in a new folder called
|
||||
`lib/managers`. The segment manager is the heart and soul, if you will, of Ember Quest. This is
|
||||
where you can get as creative as you want. You do not have to follow my design, just remember that
|
||||
`lib/managers`. The segment manager is the heart and soul, if you will, of Ember Quest. This is
|
||||
where you can get as creative as you want. You do not have to follow my design, just remember that
|
||||
whatever you design, the segment must follow the rules outlined above. Add the following code to
|
||||
`segment_manager.dart`:
|
||||
|
||||
@ -65,14 +65,14 @@ final segment0 = [
|
||||
```
|
||||
|
||||
So what this does, is allows us to create segments (segment0, segment1, etc) in a list format that
|
||||
gets added to the `segments` list. The individual segments will be made up of multiple entries of the
|
||||
`Block` class. This information will allow us to translate the block position from a 10x10 grid to
|
||||
the actual pixel position in the game world. To create a segment, you need to create
|
||||
gets added to the `segments` list. The individual segments will be made up of multiple entries of the
|
||||
`Block` class. This information will allow us to translate the block position from a 10x10 grid to
|
||||
the actual pixel position in the game world. To create a segment, you need to create
|
||||
entries for each block that you wish to be rendered from the sketch.
|
||||
|
||||
To understand each segment, if we start in the bottom left corner of the grid in the sketch, we see
|
||||
that we should place a `Block()` in the `segment0` list with a first parameter `gridPosition` of a
|
||||
`Vector2(0,0)` and a `blockType` of the `GroundBlock` class that we created earlier. Remember, the
|
||||
`Vector2(0,0)` and a `blockType` of the `GroundBlock` class that we created earlier. Remember, the
|
||||
very bottom left cell is x=0 and y=0 thus the `Vector2(x,y)` is `Vector2(0,0)`.
|
||||
|
||||

|
||||
@ -99,7 +99,7 @@ final segment0 = [
|
||||
];
|
||||
```
|
||||
|
||||
Proceed to build the remaining segments. The full segment manager should look like this:
|
||||
Proceed to build the remaining segments. The full segment manager should look like this:
|
||||
|
||||
```dart
|
||||
import 'package:flame/components.dart';
|
||||
@ -225,10 +225,10 @@ final segment4 = [
|
||||
|
||||
### Loading the Segments into the World
|
||||
|
||||
Now that our segments are defined, we need to create a way to load these blocks into our world. To
|
||||
do that, we are going to start work in the `ember_quest.dart` file. We will create a `loadSegments`
|
||||
Now that our segments are defined, we need to create a way to load these blocks into our world. To
|
||||
do that, we are going to start work in the `ember_quest.dart` file. We will create a `loadSegments`
|
||||
method that when given an index for the segments list, will then loop through that segment from
|
||||
our `segment_manager` and we will add the appropriate blocks later. It should look like this:
|
||||
our `segment_manager` and we will add the appropriate blocks later. It should look like this:
|
||||
|
||||
```dart
|
||||
void loadGameSegments(int segmentIndex, double xPositionOffset) {
|
||||
@ -273,18 +273,18 @@ Now we can refactor our game a bit and create an `initializeGame()` method which
|
||||
_ember = EmberPlayer(
|
||||
position: Vector2(128, canvasSize.y - 70),
|
||||
);
|
||||
add(_ember);
|
||||
world.add(_ember);
|
||||
}
|
||||
```
|
||||
|
||||
We simply are taking the width of the game screen, divide that by 640 (10 blocks in a segment times
|
||||
64 pixels wide for each block), and round that up. As we only defined 5 segments total, we need to
|
||||
64 pixels wide for each block), and round that up. As we only defined 5 segments total, we need to
|
||||
restrict that integer from 0 to the length of the segments list in case the user has a really wide
|
||||
screen. Then we simply loop through the number of `segmentsToLoad` and call `loadGameSegments` with
|
||||
screen. Then we simply loop through the number of `segmentsToLoad` and call `loadGameSegments` with
|
||||
the integer to load and then calculate the offset.
|
||||
|
||||
Additionally, I have moved the Ember-related code from the `onLoad` method to our new
|
||||
`initializeGame` method. This means I can now make the call in `onLoad` to `initializeGame` such
|
||||
`initializeGame` method. This means I can now make the call in `onLoad` to `initializeGame` such
|
||||
as:
|
||||
|
||||
```dart
|
||||
@ -299,6 +299,11 @@ as:
|
||||
'star.png',
|
||||
'water_enemy.png',
|
||||
]);
|
||||
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
initializeGame();
|
||||
}
|
||||
```
|
||||
@ -309,10 +314,10 @@ worry, we will solve those right now.
|
||||
|
||||
### The Platform Block
|
||||
|
||||
One of the easiest blocks to start with is the Platform Block. There are two things that we need to
|
||||
One of the easiest blocks to start with is the Platform Block. There are two things that we need to
|
||||
develop beyond getting the sprite to be displayed; that is, we need to place it in the correct
|
||||
position and as Ember moves across the screen, we need to remove the blocks once they are off the
|
||||
screen. In Ember Quest, the player can only move forward, so this will keep the game lightweight as
|
||||
screen. In Ember Quest, the player can only move forward, so this will keep the game lightweight as
|
||||
it's an infinite level.
|
||||
|
||||
Open the `lib/objects/platform_block.dart` file and add the following code:
|
||||
@ -345,22 +350,22 @@ class PlatformBlock extends SpriteComponent
|
||||
```
|
||||
|
||||
We are going to extend the Flame `SpriteComponent` and we will need the `HasGameRef` mixin to access
|
||||
our game class just like we did before. We are starting with the empty `onLoad` and `update`
|
||||
our game class just like we did before. We are starting with the empty `onLoad` and `update`
|
||||
methods and we will begin adding code to create the functionality that is necessary for the game.
|
||||
|
||||
The secret to any gaming engine is the game loop. This is an infinite loop that calls all the
|
||||
objects in your game so you can provide updates. The `update` method is the hook into this and it
|
||||
The secret to any gaming engine is the game loop. This is an infinite loop that calls all the
|
||||
objects in your game so you can provide updates. The `update` method is the hook into this and it
|
||||
uses a `double dt` to pass to your method the amount of time in seconds since it was last
|
||||
called. This `dt` variable then allows you to calculate how far your component needs to move
|
||||
on-screen.
|
||||
called. This `dt` variable then allows you to calculate how far your component needs to move
|
||||
on-screen.
|
||||
|
||||
All components in our game will need to move at the same speed, so to do this, open
|
||||
`lib/ember_quest.dart`, and let's define a global variable called `objectSpeed`. At the top of the
|
||||
`lib/ember_quest.dart`, and let's define a global variable called `objectSpeed`. At the top of the
|
||||
`EmberQuestGame` class, add:
|
||||
|
||||
```dart
|
||||
late EmberPlayer _ember;
|
||||
double objectSpeed = 0.0;
|
||||
double objectSpeed = 0.0;
|
||||
```
|
||||
|
||||
So to implement that movement, declare a variable at the top of the `PlatformBlock` class and make
|
||||
@ -381,7 +386,7 @@ final Vector2 velocity = Vector2.zero();
|
||||
```
|
||||
|
||||
All that is happening is we define a base `velocity` that is instantiated at 0 on both axes and then
|
||||
we update `velocity` using the global `objectSpeed` variable for the x-axis. As this is our
|
||||
we update `velocity` using the global `objectSpeed` variable for the x-axis. As this is our
|
||||
platform block, it will only scroll left and right, so our y-axis in the `velocity` will always be 0
|
||||
as do not want our blocks jumping.
|
||||
|
||||
@ -391,7 +396,7 @@ By multiplying the `velocity` vector by the `dt` we can move our component to th
|
||||
Finally, if `x` value of position is `-size.x` (this means off the left side of the screen by the
|
||||
width of the image) then remove this platform block from the game entirely.
|
||||
|
||||
Now we just need to finish the `onLoad` method. So make your `onLoad` method look like this:
|
||||
Now we just need to finish the `onLoad` method. So make your `onLoad` method look like this:
|
||||
|
||||
```dart
|
||||
@override
|
||||
@ -406,25 +411,25 @@ Now we just need to finish the `onLoad` method. So make your `onLoad` method lo
|
||||
```
|
||||
|
||||
First, we retrieve the image from cache as we did before, and because this is a `SpriteComponent`
|
||||
we can use the built-in `sprite` variable to assign the image to the component. Next, we need to
|
||||
calculate its starting position. This is where all the magic happens, so let's break this down.
|
||||
we can use the built-in `sprite` variable to assign the image to the component. Next, we need to
|
||||
calculate its starting position. This is where all the magic happens, so let's break this down.
|
||||
|
||||
Just like in the `update` method we will be setting the `position` variable to a `Vector2`. To
|
||||
determine where it needs to be, we need to calculate the x and y positions. Focusing on the x
|
||||
Just like in the `update` method we will be setting the `position` variable to a `Vector2`. To
|
||||
determine where it needs to be, we need to calculate the x and y positions. Focusing on the x
|
||||
first, we can see that we are taking `gridPosition.x` times the width of the image and then we will
|
||||
add that to the `xOffset` that we pass in. With the y-axis, we will take the height of the
|
||||
add that to the `xOffset` that we pass in. With the y-axis, we will take the height of the
|
||||
game and we will subtract the `gridPosition.y` times the height of the image.
|
||||
|
||||
Lastly, as we want Ember to be able to interact with the platform, we will add a `RectangleHitbox`
|
||||
with a `passive` `CollisionType`. Collisions will be explained more in a later chapter.
|
||||
with a `passive` `CollisionType`. Collisions will be explained more in a later chapter.
|
||||
|
||||
|
||||
#### Display the Platform
|
||||
|
||||
In our `loadGameSegments` method from earlier, we will need to add the call to add our block. We
|
||||
will need to define `gridPosition` and `xOffset` to be passed in. `gridPosition` will be a
|
||||
In our `loadGameSegments` method from earlier, we will need to add the call to add our block. We
|
||||
will need to define `gridPosition` and `xOffset` to be passed in. `gridPosition` will be a
|
||||
`Vector2` and `xOffset` is a double as that will be used to calculate the x-axis offset for
|
||||
the block in a `Vector2`. So add the following to your `loadGameSegments` method:
|
||||
the block in a `Vector2`. So add the following to your `loadGameSegments` method:
|
||||
|
||||
```dart
|
||||
case PlatformBlock:
|
||||
@ -439,8 +444,8 @@ If you run your code, you should now see:
|
||||
|
||||

|
||||
|
||||
While this does run, the black just makes it look like Ember is in a dungeon. Let's change that
|
||||
background real quick so there is a nice blue sky. Just add the following code to
|
||||
While this does run, the black just makes it look like Ember is in a dungeon. Let's change that
|
||||
background real quick so there is a nice blue sky. Just add the following code to
|
||||
`lib/ember_quest.dart`:
|
||||
|
||||
```dart
|
||||
@ -452,7 +457,7 @@ Color backgroundColor() {
|
||||
}
|
||||
```
|
||||
|
||||
Excellent! Ember is now in front of a blue sky.
|
||||
Excellent! Ember is now in front of a blue sky.
|
||||
|
||||
On to [](step_4.md), where we will add the rest of the components now that we have a basic
|
||||
understanding of what we are going to accomplish.
|
||||
|
||||
@ -3,9 +3,9 @@
|
||||
|
||||
## Star
|
||||
|
||||
The star is pretty simple. It is just like the Platform block except we are going to add an effect
|
||||
to make it pulse in size. For the effect to look correct, we need to change the object's `Anchor`
|
||||
to `center`. This means we will need to adjust the position by half of the image size. For brevity,
|
||||
The star is pretty simple. It is just like the Platform block except we are going to add an effect
|
||||
to make it pulse in size. For the effect to look correct, we need to change the object's `Anchor`
|
||||
to `center`. This means we will need to adjust the position by half of the image size. For brevity,
|
||||
I am going to add the whole class and explain the additional changes after.
|
||||
|
||||
```dart
|
||||
@ -33,8 +33,8 @@ class Star extends SpriteComponent
|
||||
final starImage = game.images.fromCache('star.png');
|
||||
sprite = Sprite(starImage);
|
||||
position = Vector2(
|
||||
(gridPosition.x * size.x) + xOffset + (size.x / 2),
|
||||
game.size.y - (gridPosition.y * size.y) - (size.y / 2),
|
||||
(gridPosition.x * size.x) + xOffset + (size.x / 2),
|
||||
game.size.y - (gridPosition.y * size.y) - (size.y / 2),
|
||||
);
|
||||
add(RectangleHitbox(collisionType: CollisionType.passive));
|
||||
add(
|
||||
@ -64,30 +64,32 @@ So the only change between the Star and the Platform beyond the anchor is simply
|
||||
|
||||
```dart
|
||||
add(
|
||||
SizeEffect.by(
|
||||
Vector2(-24, -24),
|
||||
SizeEffect.by(
|
||||
Vector2(-24, -24),
|
||||
EffectController(
|
||||
duration: .75,
|
||||
reverseDuration: .5,
|
||||
infinite: true,
|
||||
curve: Curves.easeOut,
|
||||
),
|
||||
duration: .75,
|
||||
reverseDuration: .5,
|
||||
infinite: true,
|
||||
curve: Curves.easeOut,
|
||||
),
|
||||
),
|
||||
);
|
||||
```
|
||||
|
||||
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
|
||||
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(
|
||||
add(
|
||||
Star(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
));
|
||||
),
|
||||
);
|
||||
break;
|
||||
```
|
||||
|
||||
@ -129,8 +131,8 @@ class WaterEnemy extends SpriteAnimationComponent
|
||||
),
|
||||
);
|
||||
position = Vector2(
|
||||
(gridPosition.x * size.x) + xOffset + (size.x / 2),
|
||||
game.size.y - (gridPosition.y * size.y) - (size.y / 2),
|
||||
(gridPosition.x * size.x) + xOffset + (size.x / 2),
|
||||
game.size.y - (gridPosition.y * size.y) - (size.y / 2),
|
||||
);
|
||||
add(RectangleHitbox(collisionType: CollisionType.passive));
|
||||
add(
|
||||
@ -158,23 +160,25 @@ class WaterEnemy extends SpriteAnimationComponent
|
||||
|
||||
The water drop enemy is an animation just like Ember, so this class is extending the
|
||||
`SpriteAnimationComponent` class but it uses all of the previous code we have used for the Star and
|
||||
the Platform. The only difference will be instead of the `SizeEffect`, we are going to use the
|
||||
`MoveEffect`. The best resource for information will be their [help
|
||||
docs](../../flame/effects.md#sizeeffectby).
|
||||
the Platform. The only difference will be instead of the `SizeEffect`, we are going to use the
|
||||
`MoveEffect`. The best resource for information will be their [help
|
||||
docs](../../flame/effects.md#sizeeffectby).
|
||||
|
||||
In short, the `MoveEffect` will last for 3 seconds, alternate directions, and run infinitely. It
|
||||
will move our enemy to the left, 128 pixels (-2 x image width). You may have noticed that in the
|
||||
constructor, I set `Anchor` to `center`. This was done just for the sake of making the calculations
|
||||
In short, the `MoveEffect` will last for 3 seconds, alternate directions, and run infinitely. It
|
||||
will move our enemy to the left, 128 pixels (-2 x image width). You may have noticed that in the
|
||||
constructor, I set `Anchor` to `center`. This was done just for the sake of making the calculations
|
||||
easier but could have been left as `bottomLeft`.
|
||||
|
||||
Don't forget to add the water enemy to your `lib/ember_quest.dart` file by doing:
|
||||
|
||||
```dart
|
||||
case WaterEnemy:
|
||||
add(WaterEnemy(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
));
|
||||
add(
|
||||
WaterEnemy(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
),
|
||||
);
|
||||
break;
|
||||
```
|
||||
|
||||
@ -185,7 +189,7 @@ If you run the game now, the Water Enemy should be displayed and moving!
|
||||
|
||||
## Ground Blocks
|
||||
|
||||
Finally, the last component that needs to be displayed is the Ground Block! This component is more
|
||||
Finally, the last component that needs to be displayed is the Ground Block! This component is more
|
||||
complex than the others as we need to identify two times during a block's life cycle.
|
||||
|
||||
- When the block is added, if it is the last block in the segment, we need to update a global value
|
||||
@ -217,8 +221,9 @@ class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
void onLoad() {
|
||||
final groundImage = game.images.fromCache('ground.png');
|
||||
sprite = Sprite(groundImage);
|
||||
position = Vector2((gridPosition.x * size.x) + xOffset,
|
||||
game.size.y - (gridPosition.y * size.y),
|
||||
position = Vector2(
|
||||
gridPosition.x * size.x + xOffset,
|
||||
game.size.y - gridPosition.y * size.y,
|
||||
);
|
||||
add(RectangleHitbox(collisionType: CollisionType.passive));
|
||||
}
|
||||
@ -233,7 +238,7 @@ class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
```
|
||||
|
||||
The first thing we will tackle is registering the block globally if it is the absolute last block to
|
||||
be loaded. To do this, add two new global variables in `lib/ember_quest.dart` called:
|
||||
be loaded. To do this, add two new global variables in `lib/ember_quest.dart` called:
|
||||
|
||||
```dart
|
||||
late double lastBlockXPosition = 0.0;
|
||||
@ -279,14 +284,14 @@ Now we can address updating this information, so in the `update` method, add the
|
||||
```
|
||||
|
||||
`game.lastBlockXPosition` is being updated by the block's current x-axis position plus its width -
|
||||
10 pixels. This will cause a little overlap, but due to the potential variance in `dt` this
|
||||
10 pixels. This will cause a little overlap, but due to the potential variance in `dt` this
|
||||
prevents gaps in the map as it loads while a player is moving.
|
||||
|
||||
|
||||
### Loading the Next Random Segment
|
||||
|
||||
To load the next random segment, we will use the `Random()` function that is built-in to
|
||||
`dart:math`. The following line of code gets a random integer from 0 (inclusive) to the max number
|
||||
`dart:math`. The following line of code gets a random integer from 0 (inclusive) to the max number
|
||||
in the passed parameter (exclusive).
|
||||
|
||||
```dart
|
||||
@ -301,7 +306,9 @@ if (position.x < -size.x) {
|
||||
removeFromParent();
|
||||
if (gridPosition.x == 0) {
|
||||
game.loadGameSegments(
|
||||
Random().nextInt(segments.length), game.lastBlockXPosition);
|
||||
Random().nextInt(segments.length),
|
||||
game.lastBlockXPosition,
|
||||
);
|
||||
}
|
||||
}
|
||||
```
|
||||
@ -309,7 +316,7 @@ if (position.x < -size.x) {
|
||||
This simply extends the code that we have in our other objects, where once the block is off the
|
||||
screen and if the block is the first block of the segment, we will call the `loadGameSegments`
|
||||
method in our game class, get a random number between 0 and the number of segments and pass in the
|
||||
offset. If `Random()` or `segments.length` does not auto-import, you will need:
|
||||
offset. If `Random()` or `segments.length` does not auto-import, you will need:
|
||||
|
||||
```dart
|
||||
import 'dart:math';
|
||||
@ -345,8 +352,9 @@ class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
void onLoad() {
|
||||
final groundImage = game.images.fromCache('ground.png');
|
||||
sprite = Sprite(groundImage);
|
||||
position = Vector2((gridPosition.x * size.x) + xOffset,
|
||||
game.size.y - (gridPosition.y * size.y),
|
||||
position = Vector2(
|
||||
gridPosition.x * size.x + xOffset,
|
||||
game.size.y - gridPosition.y * size.y,
|
||||
);
|
||||
add(RectangleHitbox(collisionType: CollisionType.passive));
|
||||
if (gridPosition.x == 9 && position.x > game.lastBlockXPosition) {
|
||||
@ -364,8 +372,9 @@ class GroundBlock extends SpriteComponent with HasGameRef<EmberQuestGame> {
|
||||
removeFromParent();
|
||||
if (gridPosition.x == 0) {
|
||||
game.loadGameSegments(
|
||||
Random().nextInt(segments.length),
|
||||
game.lastBlockXPosition);
|
||||
Random().nextInt(segments.length),
|
||||
game.lastBlockXPosition,
|
||||
);
|
||||
}
|
||||
}
|
||||
if (gridPosition.x == 9) {
|
||||
@ -384,10 +393,12 @@ Finally, don't forget to add your Ground Block to `lib/ember_quest.dart` by addi
|
||||
|
||||
```dart
|
||||
case GroundBlock:
|
||||
add(GroundBlock(
|
||||
add(
|
||||
GroundBlock(
|
||||
gridPosition: block.gridPosition,
|
||||
xOffset: xPositionOffset,
|
||||
));
|
||||
),
|
||||
);
|
||||
break;
|
||||
```
|
||||
|
||||
@ -395,6 +406,6 @@ If you run your code, your game should now look like this:
|
||||
|
||||

|
||||
|
||||
You might say, but wait! Ember is in the middle of the ground and that is correct because Ember's
|
||||
`Anchor` is set to center. This is ok and we will be addressing this in [](step_5.md) where we will
|
||||
You might say, but wait! Ember is in the middle of the ground and that is correct because Ember's
|
||||
`Anchor` is set to center. This is ok and we will be addressing this in [](step_5.md) where we will
|
||||
be adding movement and collisions to Ember!
|
||||
|
||||
@ -1,11 +1,11 @@
|
||||
# 5. Controlling Movement
|
||||
|
||||
If you were waiting for some serious coding, this chapter is it. Prepare yourself as we dive in!
|
||||
If you were waiting for some serious coding, this chapter is it. Prepare yourself as we dive in!
|
||||
|
||||
|
||||
## Keyboard Controls
|
||||
|
||||
The first step will be to allow control of Ember via the keyboard. We need to start by adding the
|
||||
The first step will be to allow control of Ember via the keyboard. We need to start by adding the
|
||||
appropriate mixins to the game class and Ember. Add the following:
|
||||
|
||||
`lib/ember_quest.dart'
|
||||
@ -39,7 +39,7 @@ import 'package:flutter/services.dart';
|
||||
```
|
||||
|
||||
To control Ember's movement, it is easiest to set a variable where we think of the direction of
|
||||
movement like a normalized vector, meaning the value will be restricted to -1, 0, or 1. So let's
|
||||
movement like a normalized vector, meaning the value will be restricted to -1, 0, or 1. So let's
|
||||
set a variable at the top of the class:
|
||||
|
||||
```dart
|
||||
@ -65,8 +65,8 @@ Now in our `onKeyEvent` method, we can register the key pressed by adding:
|
||||
}
|
||||
```
|
||||
|
||||
Let's make Ember move by adding a few lines of code and creating our `update` method. First, we
|
||||
need to define a velocity variable for Ember. Add the following at the top of the `EmberPlayer`
|
||||
Let's make Ember move by adding a few lines of code and creating our `update` method. First, we
|
||||
need to define a velocity variable for Ember. Add the following at the top of the `EmberPlayer`
|
||||
class:
|
||||
|
||||
```dart
|
||||
@ -104,10 +104,10 @@ Now Ember looks in the direction they are traveling.
|
||||
|
||||
## Collisions
|
||||
|
||||
It is time to get into the thick of it with collisions. I highly suggest reading the
|
||||
[documentation](../../flame/collision_detection.md) to understand how collisions work in Flame. The
|
||||
It is time to get into the thick of it with collisions. I highly suggest reading the
|
||||
[documentation](../../flame/collision_detection.md) to understand how collisions work in Flame. The
|
||||
first thing we need to do is make the game aware that collisions are going to occur using the
|
||||
`HasCollisionDetection` mixin. Add that to `lib/ember_quest.dart` like:
|
||||
`HasCollisionDetection` mixin. Add that to `lib/ember_quest.dart` like:
|
||||
|
||||
```dart
|
||||
class EmberQuestGame extends FlameGame
|
||||
@ -182,7 +182,7 @@ add(
|
||||
```
|
||||
|
||||
Now that we have the basic collisions created, we can add gravity so Ember exists in a game world
|
||||
with very basic physics. To do that, we need to create some more variables:
|
||||
with very basic physics. To do that, we need to create some more variables:
|
||||
|
||||
```dart
|
||||
final double gravity = 15;
|
||||
@ -219,7 +219,7 @@ velocity.y = velocity.y.clamp(-jumpSpeed, terminalVelocity);
|
||||
```
|
||||
|
||||
Earlier I mentioned that Ember was in the center of the grass, to solve this and show how collisions
|
||||
and gravity work with Ember, I like to add a little drop-in when you start the game. So in
|
||||
and gravity work with Ember, I like to add a little drop-in when you start the game. So in
|
||||
`lib/ember_quest.dart` in the `initializeGame` method, change the following:
|
||||
|
||||
```dart
|
||||
@ -293,8 +293,8 @@ collide with an enemy, Ember should blink.
|
||||
|
||||
## Adding the Scrolling
|
||||
|
||||
This is our last task with Ember. We need to restrict Ember's movement because as of now, Ember can
|
||||
go off-screen and we never move the map. So to implement this feature, we simply need to add the
|
||||
This is our last task with Ember. We need to restrict Ember's movement because as of now, Ember can
|
||||
go off-screen and we never move the map. So to implement this feature, we simply need to add the
|
||||
following to our `update` method:
|
||||
|
||||
```dart
|
||||
@ -311,8 +311,8 @@ if (position.x + 64 >= game.size.x / 2 && horizontalDirection > 0) {
|
||||
```
|
||||
|
||||
If you run the game now, Ember can't move off-screen to the left, and as Ember moves to the right,
|
||||
once they get to the middle of the screen, the rest of the objects scroll by. This is because we
|
||||
are now updating `game.objectSpeed` which we established early on in the series. Additionally,
|
||||
once they get to the middle of the screen, the rest of the objects scroll by. This is because we
|
||||
are now updating `game.objectSpeed` which we established early on in the series. Additionally,
|
||||
you will see the next random segment be generated and added to the level based on the work we did in
|
||||
Ground Block.
|
||||
|
||||
@ -325,5 +325,5 @@ object, we could reload the level and start all over maintaining the stars
|
||||
collected and health.
|
||||
```
|
||||
|
||||
We are almost done! In [](step_6.md), we will add the health system, keep track of
|
||||
We are almost done! In [](step_6.md), we will add the health system, keep track of
|
||||
the score, and provide a HUD to relay that information to the player.
|
||||
|
||||
@ -3,8 +3,8 @@
|
||||
|
||||
## Setting up the HUD
|
||||
|
||||
Now that the game is up and running, the rest of the code should come fairly easily. To prepare for
|
||||
the hud, we need to add some variables in `lib/ember_quest.dart`. Add the following to the top of
|
||||
Now that the game is up and running, the rest of the code should come fairly easily. To prepare for
|
||||
the hud, we need to add some variables in `lib/ember_quest.dart`. Add the following to the top of
|
||||
the class:
|
||||
|
||||
```dart
|
||||
@ -13,8 +13,8 @@ int health = 3;
|
||||
```
|
||||
|
||||
Start by creating a folder called `lib/overlays`, and in that folder, create a component called
|
||||
`heart.dart`. This is going to be the health monitoring component in the upper left-hand corner of
|
||||
the game. Add the following code:
|
||||
`heart.dart`. This is going to be the health monitoring component in the upper left-hand corner of
|
||||
the game. Add the following code:
|
||||
|
||||
```dart
|
||||
import 'package:ember_quest/ember_quest.dart';
|
||||
@ -74,7 +74,7 @@ class HeartHealthComponent extends SpriteGroupComponent<HeartState>
|
||||
```
|
||||
|
||||
The `HeartHealthComponent` is just a [SpriteGroupComponent](../../flame/components.md#spritegroup)
|
||||
that uses the heart images that were created early on. The unique thing that is being done, is when
|
||||
that uses the heart images that were created early on. The unique thing that is being done, is when
|
||||
the component is created, it requires a `heartNumber`, so in the `update` method, we check to see if
|
||||
the `game.health` is less than the `heartNumber` and if so, change the state of the component to
|
||||
unavailable.
|
||||
@ -97,14 +97,12 @@ class Hud extends PositionComponent with HasGameRef<EmberQuestGame> {
|
||||
super.anchor,
|
||||
super.children,
|
||||
super.priority = 5,
|
||||
}) {
|
||||
positionType = PositionType.viewport;
|
||||
}
|
||||
});
|
||||
|
||||
late TextComponent _scoreTextComponent;
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
Future<void> onLoad() async {
|
||||
_scoreTextComponent = TextComponent(
|
||||
text: '${game.starsCollected}',
|
||||
textRenderer: TextPaint(
|
||||
@ -138,26 +136,23 @@ class Hud extends PositionComponent with HasGameRef<EmberQuestGame> {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
_scoreTextComponent.text = '${game.starsCollected}';
|
||||
super.update(dt);
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In the `onLoad` method, you can see where we loop from 1 to the `game.health` amount, to create
|
||||
the number of hearts necessary. The last step is to add the hud to the game.
|
||||
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
|
||||
add(Hud());
|
||||
cameraComponent.viewport.add(Hud());
|
||||
```
|
||||
|
||||
If the auto-import did not occur, you will need to add:
|
||||
@ -173,38 +168,38 @@ If you run the game now, you should see:
|
||||
|
||||
## Updating the HUD Data
|
||||
|
||||
The last thing we need to do before closing out the HUD is to update the data. To do this, we need
|
||||
The last thing we need to do before closing out the HUD is to update the data. To do this, we need
|
||||
to open `lib/actors/ember.dart` and add the following code:
|
||||
|
||||
`onCollision`
|
||||
|
||||
```dart
|
||||
if (other is Star) {
|
||||
other.removeFromParent();
|
||||
game.starsCollected++;
|
||||
other.removeFromParent();
|
||||
game.starsCollected++;
|
||||
}
|
||||
```
|
||||
|
||||
```dart
|
||||
void hit() {
|
||||
if (!hitByEnemy) {
|
||||
if (!hitByEnemy) {
|
||||
game.health--;
|
||||
hitByEnemy = true;
|
||||
}
|
||||
add(
|
||||
}
|
||||
add(
|
||||
OpacityEffect.fadeOut(
|
||||
EffectController(
|
||||
alternate: true,
|
||||
duration: 0.1,
|
||||
repeatCount: 5,
|
||||
),
|
||||
EffectController(
|
||||
alternate: true,
|
||||
duration: 0.1,
|
||||
repeatCount: 5,
|
||||
),
|
||||
)..onComplete = () {
|
||||
hitByEnemy = false;
|
||||
},
|
||||
);
|
||||
);
|
||||
}
|
||||
```
|
||||
|
||||
If you run the game now, you will see that your health is updated and the stars are incremented as
|
||||
appropriate. Finally, in [](step_7), we will finish the game by adding the main menu and the
|
||||
appropriate. Finally, in [](step_7), we will finish the game by adding the main menu and the
|
||||
game-over menu.
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
# 7. Adding Menus
|
||||
|
||||
To add menus to the game, we will leverage Flame's built-in
|
||||
[overlay](../../flame/overlays.md) system.
|
||||
[overlay](../../flame/overlays.md) system.
|
||||
|
||||
|
||||
## Main Menu
|
||||
@ -69,8 +69,9 @@ class MainMenu extends StatelessWidget {
|
||||
),
|
||||
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!',
|
||||
'''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,
|
||||
@ -88,9 +89,9 @@ class MainMenu extends StatelessWidget {
|
||||
```
|
||||
|
||||
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
|
||||
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
|
||||
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.
|
||||
|
||||
@ -168,52 +169,56 @@ class GameOver extends StatelessWidget {
|
||||
```
|
||||
|
||||
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.
|
||||
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);
|
||||
await images.loadAll([
|
||||
'block.png',
|
||||
'ember.png',
|
||||
'ground.png',
|
||||
'heart_half.png',
|
||||
'heart.png',
|
||||
'star.png',
|
||||
'water_enemy.png',
|
||||
]);
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
initializeGame(true);
|
||||
}
|
||||
|
||||
void initializeGame(bool loadHud) {
|
||||
// Assume that size.x < 3200
|
||||
final segmentsToLoad = (size.x / 640).ceil();
|
||||
segmentsToLoad.clamp(0, segments.length);
|
||||
// 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());
|
||||
}
|
||||
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);
|
||||
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
|
||||
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()`.
|
||||
|
||||
@ -244,25 +249,25 @@ 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
|
||||
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!
|
||||
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;
|
||||
game.health = 0;
|
||||
}
|
||||
|
||||
if (game.health <= 0) {
|
||||
removeFromParent();
|
||||
removeFromParent();
|
||||
}
|
||||
```
|
||||
|
||||
@ -270,7 +275,7 @@ In `lib/actors/water_enemy.dart`, in the `update` method update the following co
|
||||
|
||||
```dart
|
||||
if (position.x < -size.x || game.health <= 0) {
|
||||
removeFromParent();
|
||||
removeFromParent();
|
||||
}
|
||||
```
|
||||
|
||||
@ -278,7 +283,7 @@ In `lib/objects/ground_block.dart`, in the `update` method update the following
|
||||
|
||||
```dart
|
||||
if (game.health <= 0) {
|
||||
removeFromParent();
|
||||
removeFromParent();
|
||||
}
|
||||
```
|
||||
|
||||
@ -286,7 +291,7 @@ In `lib/objects/platform_block.dart`, in the `update` method update the followin
|
||||
|
||||
```dart
|
||||
if (position.x < -size.x || game.health <= 0) {
|
||||
removeFromParent();
|
||||
removeFromParent();
|
||||
}
|
||||
```
|
||||
|
||||
@ -294,7 +299,7 @@ In `lib/objects/star.dart`, in the `update` method update the following code:
|
||||
|
||||
```dart
|
||||
if (position.x < -size.x || game.health <= 0) {
|
||||
removeFromParent();
|
||||
removeFromParent();
|
||||
}
|
||||
```
|
||||
|
||||
@ -303,17 +308,16 @@ 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);
|
||||
if (health <= 0) {
|
||||
overlays.add('GameOver');
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
## Congratulations
|
||||
|
||||
You made it! You have a working Ember Quest. Press the button below to see what the resulting code
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user