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:
1
.github/.cspell/gamedev_dictionary.txt
vendored
1
.github/.cspell/gamedev_dictionary.txt
vendored
@ -66,6 +66,7 @@ hitbox
|
||||
hitboxes
|
||||
hoverable
|
||||
hoverables
|
||||
HUD
|
||||
ints
|
||||
jank
|
||||
janky
|
||||
|
||||
@ -29,7 +29,7 @@ class RayTraceExample extends FlameGame
|
||||
Future<void> onLoad() async {
|
||||
add(
|
||||
CircleComponent(
|
||||
radius: min(camera.canvasSize.x, camera.canvasSize.y) / 2,
|
||||
radius: min(size.x, size.y) / 2,
|
||||
paint: boxPaint,
|
||||
children: [CircleHitbox()],
|
||||
),
|
||||
|
||||
@ -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
|
||||
@ -146,6 +185,9 @@ class EmberQuestGame extends FlameGame {
|
||||
|
||||
late EmberPlayer _ember;
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await images.loadAll([
|
||||
@ -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
|
||||
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
|
||||
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.
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -175,45 +176,49 @@ 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}
|
||||
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 21 KiB After Width: | Height: | Size: 73 KiB |
@ -68,7 +68,7 @@ class TRexGame extends FlameGame
|
||||
scoreText = TextComponent(
|
||||
position: Vector2(20, 20),
|
||||
textRenderer: renderer,
|
||||
)..positionType = PositionType.viewport,
|
||||
),
|
||||
);
|
||||
score = 0;
|
||||
}
|
||||
|
||||
@ -17,9 +17,14 @@ starts to drop in FPS, this is without any sprite batching and such.
|
||||
final counterPrefix = 'Animations: ';
|
||||
final Random random = Random();
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
await addAll([
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([cameraComponent, world]);
|
||||
await cameraComponent.viewport.addAll([
|
||||
FpsTextComponent(
|
||||
position: size - Vector2(0, 50),
|
||||
anchor: Anchor.bottomRight,
|
||||
@ -29,29 +34,28 @@ starts to drop in FPS, this is without any sprite batching and such.
|
||||
anchor: Anchor.bottomRight,
|
||||
priority: 1,
|
||||
),
|
||||
Ember(size: emberSize, position: size / 2),
|
||||
]);
|
||||
world.add(Ember(size: emberSize, position: size / 2));
|
||||
children.register<Ember>();
|
||||
}
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
emberCounter.text = '$counterPrefix ${children.query<Ember>().length}';
|
||||
emberCounter.text =
|
||||
'$counterPrefix ${world.children.query<Ember>().length}';
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownInfo info) {
|
||||
final halfWidth = emberSize.x / 2;
|
||||
final halfHeight = emberSize.y / 2;
|
||||
addAll(
|
||||
world.addAll(
|
||||
List.generate(
|
||||
100,
|
||||
(_) => Ember(
|
||||
size: emberSize,
|
||||
position: Vector2(
|
||||
halfWidth + (size.x - halfWidth) * random.nextDouble(),
|
||||
halfHeight + (size.y - halfHeight) * random.nextDouble(),
|
||||
(size.x / 2) * random.nextDouble() * (random.nextBool() ? 1 : -1),
|
||||
(size.y / 2) * random.nextDouble() * (random.nextBool() ? 1 : -1),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
||||
@ -23,22 +23,22 @@ class SimpleIsolateExample extends FlameGame {
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
|
||||
|
||||
const rect = Rect.fromLTRB(80, 230, 320, 470);
|
||||
|
||||
add(
|
||||
CalculatePrimeNumber(
|
||||
position: rect.center.toVector2(),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
final world = World();
|
||||
final cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
const rect = Rect.fromLTRB(-120, -120, 120, 120);
|
||||
final circle = Path()..addOval(rect);
|
||||
final teal = Paint()..color = Colors.tealAccent;
|
||||
|
||||
for (var i = 0; i < 20; i++) {
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(size: 10)
|
||||
..paint = (Paint()..color = Colors.tealAccent)
|
||||
..paint = teal
|
||||
..add(
|
||||
MoveAlongPathEffect(
|
||||
circle,
|
||||
@ -52,7 +52,8 @@ class SimpleIsolateExample extends FlameGame {
|
||||
),
|
||||
);
|
||||
}
|
||||
return super.onMount();
|
||||
|
||||
world.add(CalculatePrimeNumber());
|
||||
}
|
||||
}
|
||||
|
||||
@ -67,10 +68,7 @@ enum ComputeType {
|
||||
|
||||
class CalculatePrimeNumber extends PositionComponent
|
||||
with TapCallbacks, FlameIsolate {
|
||||
CalculatePrimeNumber({
|
||||
required super.position,
|
||||
required super.anchor,
|
||||
});
|
||||
CalculatePrimeNumber() : super(anchor: Anchor.center);
|
||||
|
||||
ComputeType computeType = ComputeType.isolate;
|
||||
late Timer _interval;
|
||||
@ -140,7 +138,7 @@ class CalculatePrimeNumber extends PositionComponent
|
||||
ComputeType.values[(computeType.index + 1) % ComputeType.values.length];
|
||||
}
|
||||
|
||||
final _paint = Paint()..color = const Color(0xa98d560d);
|
||||
final _paint = Paint()..color = Colors.green;
|
||||
|
||||
final _textPaint = TextPaint(
|
||||
style: const TextStyle(
|
||||
@ -149,7 +147,7 @@ class CalculatePrimeNumber extends PositionComponent
|
||||
);
|
||||
|
||||
late final rect = Rect.fromLTWH(0, 0, width, height);
|
||||
late final topLeftVector = rect.topLeft.toVector2();
|
||||
late final topLeftVector = rect.topLeft.toVector2() + Vector2.all(4);
|
||||
late final centerVector = rect.center.toVector2();
|
||||
|
||||
@override
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/domino_example.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/sprite_body_example.dart';
|
||||
import 'package:flame/input.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/sprite_body_example.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:flame/events.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class GearJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||
static const description = '''
|
||||
This example shows how to use a `GearJoint`.
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:flame/events.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class MotorJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||
static const description = '''
|
||||
This example shows how to use a `MotorJoint`. The ball spins around the
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/revolute_joint_with_motor_example.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boxes.dart';
|
||||
|
||||
@ -6,6 +6,7 @@ import 'package:flame/events.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class PulleyJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||
static const description = '''
|
||||
This example shows how to use a `PulleyJoint`. Drag one of the boxes and see
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:flame/input.dart';
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class RopeJointExample extends Forge2DGame with TapDetector, HasDraggables {
|
||||
static const description = '''
|
||||
This example shows how to use a `RopeJoint`.
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/balls.dart';
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
import 'package:flame/events.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:flame_forge2d/flame_forge2d.dart';
|
||||
|
||||
List<Wall> createBoundaries(Forge2DGame game) {
|
||||
|
||||
@ -43,6 +43,7 @@ class Box extends BodyComponent {
|
||||
}
|
||||
}
|
||||
|
||||
// ignore: deprecated_member_use
|
||||
class DraggableBox extends Box with Draggable {
|
||||
MouseJoint? mouseJoint;
|
||||
late final groundBody = world.createBody(BodyDef());
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:examples/stories/bridge_libraries/forge2d/utils/boundaries.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
|
||||
@ -5,7 +5,7 @@ import 'package:flame/components.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/game.dart' hide Viewport;
|
||||
|
||||
class CameraComponentPropertiesExample extends FlameGame with TapCallbacks {
|
||||
class CameraComponentPropertiesExample extends FlameGame {
|
||||
static const description = '''
|
||||
This example uses FixedSizeViewport which is dynamically sized and
|
||||
positioned based on the size of the game widget.
|
||||
|
||||
@ -31,12 +31,32 @@ class CoordinateSystemsExample extends FlameGame
|
||||
);
|
||||
|
||||
String? lastEventDescription;
|
||||
Vector2 cameraPosition = Vector2.zero();
|
||||
Vector2 cameraVelocity = Vector2.zero();
|
||||
final cameraPosition = Vector2.zero();
|
||||
final cameraVelocity = Vector2.zero();
|
||||
late final CameraComponent cameraComponent;
|
||||
final world = World();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.followVector2(cameraPosition, relativeOffset: Anchor.topLeft);
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([world, cameraComponent]);
|
||||
final rectanglePosition = canvasSize / 4;
|
||||
final rectangleSize = Vector2.all(20);
|
||||
final positions = [
|
||||
Vector2(rectanglePosition.x, rectanglePosition.y),
|
||||
Vector2(rectanglePosition.x, -rectanglePosition.y),
|
||||
Vector2(-rectanglePosition.x, rectanglePosition.y),
|
||||
Vector2(-rectanglePosition.x, -rectanglePosition.y),
|
||||
];
|
||||
world.addAll(
|
||||
[
|
||||
for (final position in positions)
|
||||
RectangleComponent(
|
||||
position: position,
|
||||
size: rectangleSize,
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -49,7 +69,8 @@ class CoordinateSystemsExample extends FlameGame
|
||||
);
|
||||
_text.render(
|
||||
canvas,
|
||||
'Camera: ${camera.position}, zoom: ${camera.zoom}',
|
||||
'Camera: ${cameraComponent.viewfinder.position}, '
|
||||
'zoom: ${cameraComponent.viewfinder.zoom}',
|
||||
Vector2(canvasSize.x - 5, 5.0),
|
||||
anchor: Anchor.topRight,
|
||||
);
|
||||
@ -98,12 +119,13 @@ class CoordinateSystemsExample extends FlameGame
|
||||
|
||||
/// Describes generic event information + some event specific details for
|
||||
/// some events.
|
||||
static String _describe(String name, PositionInfo info) {
|
||||
String _describe(String name, PositionInfo info) {
|
||||
return [
|
||||
name,
|
||||
'Global: ${info.eventPosition.global}',
|
||||
'Widget: ${info.eventPosition.widget}',
|
||||
'Game: ${info.eventPosition.game}',
|
||||
'Camera: ${cameraComponent.viewfinder.position}',
|
||||
if (info is DragUpdateInfo) ...[
|
||||
'Delta',
|
||||
'Global: ${info.delta.global}',
|
||||
@ -120,10 +142,11 @@ class CoordinateSystemsExample extends FlameGame
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
cameraPosition.add(cameraVelocity * dt * 10);
|
||||
cameraPosition.add(cameraVelocity * dt * 30);
|
||||
// just make it look pretty
|
||||
cameraPosition.x = _roundDouble(cameraPosition.x, 5);
|
||||
cameraPosition.y = _roundDouble(cameraPosition.y, 5);
|
||||
cameraComponent.viewfinder.position = cameraPosition;
|
||||
}
|
||||
|
||||
/// Round [val] up to [places] decimal places.
|
||||
@ -150,9 +173,9 @@ class CoordinateSystemsExample extends FlameGame
|
||||
cameraVelocity.y = isKeyDown ? 1 : 0;
|
||||
} else if (isKeyDown) {
|
||||
if (event.logicalKey == LogicalKeyboardKey.keyQ) {
|
||||
camera.zoom *= 2;
|
||||
cameraComponent.viewfinder.zoom *= 2;
|
||||
} else if (event.logicalKey == LogicalKeyboardKey.keyE) {
|
||||
camera.zoom /= 2;
|
||||
cameraComponent.viewfinder.zoom /= 2;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -23,15 +23,17 @@ class FixedResolutionExample extends FlameGame
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
final flameSprite = await loadSprite('layers/player.png');
|
||||
final world = World();
|
||||
final cameraComponent = CameraComponent.withFixedResolution(
|
||||
width: viewportResolution.x,
|
||||
height: viewportResolution.y,
|
||||
world: world,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
camera.viewport = FixedResolutionViewport(viewportResolution);
|
||||
camera.setRelativeOffset(Anchor.topLeft);
|
||||
camera.speed = 1;
|
||||
|
||||
add(Background());
|
||||
add(
|
||||
world.add(Background());
|
||||
world.add(
|
||||
SpriteComponent(
|
||||
position: camera.viewport.effectiveSize / 2,
|
||||
sprite: flameSprite,
|
||||
size: Vector2(149, 211),
|
||||
)..anchor = Anchor.center,
|
||||
@ -46,7 +48,7 @@ class Background extends PositionComponent with HasGameRef {
|
||||
late Paint white;
|
||||
late final Rect hugeRect;
|
||||
|
||||
Background() : super(size: Vector2.all(100000));
|
||||
Background() : super(size: Vector2.all(100000), anchor: Anchor.center);
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:flame/collisions.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/experimental.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
@ -22,25 +23,30 @@ class FollowComponentExample extends FlameGame
|
||||
respects the camera transformation.
|
||||
''';
|
||||
|
||||
FollowComponentExample({required this.viewportResolution});
|
||||
|
||||
late MovableEmber ember;
|
||||
final Vector2 viewportResolution;
|
||||
|
||||
FollowComponentExample({
|
||||
required this.viewportResolution,
|
||||
});
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.viewport = FixedResolutionViewport(viewportResolution);
|
||||
add(Map());
|
||||
final world = World();
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
width: viewportResolution.x,
|
||||
height: viewportResolution.y,
|
||||
world: world,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
add(ember = MovableEmber());
|
||||
camera.speed = 1;
|
||||
camera.followComponent(ember, worldBounds: Map.bounds);
|
||||
world.add(Map());
|
||||
world.add(ember = MovableEmber());
|
||||
cameraComponent.setBounds(Map.bounds);
|
||||
cameraComponent.follow(ember, maxSpeed: 250);
|
||||
|
||||
for (var i = 0; i < 30; i++) {
|
||||
add(Rock(Vector2(Map.genCoord(), Map.genCoord())));
|
||||
}
|
||||
world.addAll(
|
||||
List.generate(30, (_) => Rock(Map.generateCoordinates())),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -78,18 +84,18 @@ class MovableEmber extends Ember<FollowComponentExample>
|
||||
}
|
||||
|
||||
@override
|
||||
void onCollision(Set<Vector2> intersectionPoints, PositionComponent other) {
|
||||
super.onCollision(intersectionPoints, other);
|
||||
void onCollisionStart(
|
||||
Set<Vector2> intersectionPoints,
|
||||
PositionComponent other,
|
||||
) {
|
||||
super.onCollisionStart(intersectionPoints, other);
|
||||
if (other is Rock) {
|
||||
gameRef.camera.setRelativeOffset(Anchor.topCenter);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void onCollisionEnd(PositionComponent other) {
|
||||
super.onCollisionEnd(other);
|
||||
if (other is Rock) {
|
||||
gameRef.camera.setRelativeOffset(Anchor.center);
|
||||
other.add(
|
||||
ScaleEffect.to(
|
||||
Vector2.all(1.5),
|
||||
EffectController(duration: 0.2, alternate: true),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -125,7 +131,8 @@ class MovableEmber extends Ember<FollowComponentExample>
|
||||
|
||||
class Map extends Component {
|
||||
static const double size = 1500;
|
||||
static const Rect bounds = Rect.fromLTWH(-size, -size, 2 * size, 2 * size);
|
||||
static const Rect _bounds = Rect.fromLTRB(-size, -size, size, size);
|
||||
static Rectangle bounds = Rectangle.fromLTRB(-size, -size, size, size);
|
||||
|
||||
static final Paint _paintBorder = Paint()
|
||||
..color = Colors.white12
|
||||
@ -155,16 +162,18 @@ class Map extends Component {
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
canvas.drawRect(bounds, _paintBg);
|
||||
canvas.drawRect(bounds, _paintBorder);
|
||||
canvas.drawRect(_bounds, _paintBg);
|
||||
canvas.drawRect(_bounds, _paintBorder);
|
||||
for (var i = 0; i < (size / 50).ceil(); i++) {
|
||||
canvas.drawCircle(Offset.zero, size - i * 50, _paintPool[i]);
|
||||
canvas.drawRect(_rectPool[i], _paintBorder);
|
||||
}
|
||||
}
|
||||
|
||||
static double genCoord() {
|
||||
return -size + _rng.nextDouble() * (2 * size);
|
||||
static Vector2 generateCoordinates() {
|
||||
return Vector2.random()
|
||||
..scale(2 * size)
|
||||
..sub(Vector2.all(size));
|
||||
}
|
||||
}
|
||||
|
||||
@ -174,6 +183,7 @@ class Rock extends SpriteComponent with HasGameRef, TapCallbacks {
|
||||
position: position,
|
||||
size: Vector2.all(50),
|
||||
priority: 1,
|
||||
anchor: Anchor.center,
|
||||
);
|
||||
|
||||
@override
|
||||
@ -186,8 +196,8 @@ class Rock extends SpriteComponent with HasGameRef, TapCallbacks {
|
||||
@override
|
||||
void onTapDown(_) {
|
||||
add(
|
||||
ScaleEffect.by(
|
||||
Vector2.all(10),
|
||||
ScaleEffect.to(
|
||||
Vector2.all(scale.x >= 2.0 ? 1 : 2),
|
||||
EffectController(duration: 0.3),
|
||||
),
|
||||
);
|
||||
|
||||
@ -8,39 +8,45 @@ class ZoomExample extends FlameGame with ScrollDetector, ScaleDetector {
|
||||
On mobile: use scale gesture to zoom in and out.
|
||||
''';
|
||||
|
||||
final Vector2 viewportResolution;
|
||||
late SpriteComponent flame;
|
||||
|
||||
ZoomExample({
|
||||
required this.viewportResolution,
|
||||
});
|
||||
|
||||
final Vector2 viewportResolution;
|
||||
late SpriteComponent flame;
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
final flameSprite = await loadSprite('flame.png');
|
||||
|
||||
camera.viewport = FixedResolutionViewport(viewportResolution);
|
||||
camera.setRelativeOffset(Anchor.center);
|
||||
camera.speed = 100;
|
||||
final world = World();
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: viewportResolution.x,
|
||||
height: viewportResolution.y,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
final flameSize = Vector2(149, 211);
|
||||
add(
|
||||
world.add(
|
||||
flame = SpriteComponent(
|
||||
sprite: flameSprite,
|
||||
size: flameSize,
|
||||
size: Vector2(149, 211),
|
||||
)..anchor = Anchor.center,
|
||||
);
|
||||
}
|
||||
|
||||
void clampZoom() {
|
||||
camera.zoom = camera.zoom.clamp(0.05, 3.0);
|
||||
cameraComponent.viewfinder.zoom =
|
||||
cameraComponent.viewfinder.zoom.clamp(0.05, 3.0);
|
||||
}
|
||||
|
||||
static const zoomPerScrollUnit = 0.02;
|
||||
|
||||
@override
|
||||
void onScroll(PointerScrollInfo info) {
|
||||
camera.zoom += info.scrollDelta.game.y.sign * zoomPerScrollUnit;
|
||||
cameraComponent.viewfinder.zoom +=
|
||||
info.scrollDelta.game.y.sign * zoomPerScrollUnit;
|
||||
clampZoom();
|
||||
}
|
||||
|
||||
@ -48,18 +54,18 @@ class ZoomExample extends FlameGame with ScrollDetector, ScaleDetector {
|
||||
|
||||
@override
|
||||
void onScaleStart(_) {
|
||||
startZoom = camera.zoom;
|
||||
startZoom = cameraComponent.viewfinder.zoom;
|
||||
}
|
||||
|
||||
@override
|
||||
void onScaleUpdate(ScaleUpdateInfo info) {
|
||||
final currentScale = info.scale.global;
|
||||
if (!currentScale.isIdentity()) {
|
||||
camera.zoom = startZoom * currentScale.y;
|
||||
cameraComponent.viewfinder.zoom = startZoom * currentScale.y;
|
||||
clampZoom();
|
||||
} else {
|
||||
camera.translateBy(-info.delta.game);
|
||||
camera.snap();
|
||||
final delta = info.delta.game;
|
||||
cameraComponent.viewfinder.position.translate(-delta.x, -delta.y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -11,6 +11,7 @@ import 'package:examples/stories/collision_detection/raycast_light_example.dart'
|
||||
import 'package:examples/stories/collision_detection/raycast_max_distance_example.dart';
|
||||
import 'package:examples/stories/collision_detection/raytrace_example.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
void addCollisionDetectionStories(Dashbook dashbook) {
|
||||
dashbook.storiesOf('Collision Detection')
|
||||
@ -35,7 +36,7 @@ void addCollisionDetectionStories(Dashbook dashbook) {
|
||||
)
|
||||
..add(
|
||||
'Multiple shapes',
|
||||
(_) => GameWidget(game: MultipleShapesExample()),
|
||||
(_) => ClipRect(child: GameWidget(game: MultipleShapesExample())),
|
||||
codeLink: baseLink('collision_detection/multiple_shapes_example.dart'),
|
||||
info: MultipleShapesExample.description,
|
||||
)
|
||||
|
||||
@ -39,10 +39,14 @@ Press T button to toggle player to collide with other objects.
|
||||
|
||||
static const mapSize = 300;
|
||||
static const bricksCount = 8000;
|
||||
late final CameraComponent cameraComponent;
|
||||
late final Player player;
|
||||
final staticLayer = StaticLayer();
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
super.onLoad();
|
||||
final world = World();
|
||||
|
||||
const mapWidth = mapSize * tileSize;
|
||||
const mapHeight = mapSize * tileSize;
|
||||
@ -63,75 +67,79 @@ Press T button to toggle player to collide with other objects.
|
||||
srcPosition: Vector2(0, tileSize),
|
||||
srcSize: Vector2.all(tileSize),
|
||||
);
|
||||
|
||||
for (var i = 0; i < bricksCount; i++) {
|
||||
final x = random.nextInt(mapSize);
|
||||
final y = random.nextInt(mapSize);
|
||||
final brick = Brick(
|
||||
position: Vector2(x.toDouble() * tileSize, y.toDouble() * tileSize),
|
||||
position: Vector2(x * tileSize, y * tileSize),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 0,
|
||||
sprite: spriteBrick,
|
||||
);
|
||||
add(brick);
|
||||
world.add(brick);
|
||||
staticLayer.components.add(brick);
|
||||
}
|
||||
|
||||
staticLayer.reRender();
|
||||
camera.viewport = FixedResolutionViewport(Vector2(500, 250));
|
||||
final playerPoint = Vector2.all(mapSize * tileSize / 2);
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 500,
|
||||
height: 250,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
final player =
|
||||
Player(position: playerPoint, size: Vector2.all(tileSize), priority: 2);
|
||||
add(player);
|
||||
this.player = player;
|
||||
camera.followComponent(player);
|
||||
player = Player(
|
||||
position: Vector2.all(mapSize * tileSize / 2),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 2,
|
||||
);
|
||||
world.add(player);
|
||||
cameraComponent.follow(player);
|
||||
|
||||
final brick = Brick(
|
||||
position: playerPoint.translate(0, -tileSize * 2),
|
||||
position: player.position.translated(0, -tileSize * 2),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 0,
|
||||
sprite: spriteBrick,
|
||||
);
|
||||
add(brick);
|
||||
world.add(brick);
|
||||
staticLayer.components.add(brick);
|
||||
|
||||
final water1 = Water(
|
||||
position: playerPoint.translate(0, tileSize * 2),
|
||||
position: player.position.translated(0, tileSize * 2),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 0,
|
||||
sprite: spriteWater,
|
||||
);
|
||||
add(water1);
|
||||
world.add(water1);
|
||||
|
||||
final water2 = Water(
|
||||
position: playerPoint.translate(tileSize * 2, 0),
|
||||
position: player.position.translated(tileSize * 2, 0),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 0,
|
||||
sprite: spriteWater,
|
||||
);
|
||||
add(water2);
|
||||
world.add(water2);
|
||||
|
||||
final water3 = Water(
|
||||
position: playerPoint.translate(-tileSize * 2, 0),
|
||||
position: player.position.translated(-tileSize * 2, 0),
|
||||
size: Vector2.all(tileSize),
|
||||
priority: 0,
|
||||
sprite: spriteWater,
|
||||
);
|
||||
add(water3);
|
||||
world.add(water3);
|
||||
|
||||
add(QuadTreeDebugComponent(collisionDetection));
|
||||
add(LayerComponent(staticLayer));
|
||||
add(FpsTextComponent());
|
||||
camera.zoom = 1;
|
||||
world.add(QuadTreeDebugComponent(collisionDetection));
|
||||
world.add(LayerComponent(staticLayer));
|
||||
cameraComponent.viewport.add(FpsTextComponent());
|
||||
}
|
||||
|
||||
final elapsedMicroseconds = <double>[];
|
||||
|
||||
late Player player;
|
||||
final _playerDisplacement = Vector2.zero();
|
||||
var _fireBullet = false;
|
||||
|
||||
final staticLayer = StaticLayer();
|
||||
static const stepSize = 1.0;
|
||||
|
||||
@override
|
||||
@ -142,19 +150,19 @@ Press T button to toggle player to collide with other objects.
|
||||
for (final key in keysPressed) {
|
||||
if (key == LogicalKeyboardKey.keyW && player.canMoveTop) {
|
||||
_playerDisplacement.setValues(0, -stepSize);
|
||||
player.position = player.position.translate(0, -stepSize);
|
||||
player.position.translate(0, -stepSize);
|
||||
}
|
||||
if (key == LogicalKeyboardKey.keyA && player.canMoveLeft) {
|
||||
_playerDisplacement.setValues(-stepSize, 0);
|
||||
player.position = player.position.translate(-stepSize, 0);
|
||||
player.position.translate(-stepSize, 0);
|
||||
}
|
||||
if (key == LogicalKeyboardKey.keyS && player.canMoveBottom) {
|
||||
_playerDisplacement.setValues(0, stepSize);
|
||||
player.position = player.position.translate(0, stepSize);
|
||||
player.position.translate(0, stepSize);
|
||||
}
|
||||
if (key == LogicalKeyboardKey.keyD && player.canMoveRight) {
|
||||
_playerDisplacement.setValues(stepSize, 0);
|
||||
player.position = player.position.translate(stepSize, 0);
|
||||
player.position.translate(stepSize, 0);
|
||||
}
|
||||
if (key == LogicalKeyboardKey.space) {
|
||||
_fireBullet = true;
|
||||
@ -186,8 +194,9 @@ Press T button to toggle player to collide with other objects.
|
||||
|
||||
@override
|
||||
void onScroll(PointerScrollInfo info) {
|
||||
camera.zoom += info.scrollDelta.game.y.sign * 0.08;
|
||||
camera.zoom = camera.zoom.clamp(0.05, 5.0);
|
||||
cameraComponent.viewfinder.zoom += info.scrollDelta.game.y.sign * 0.08;
|
||||
cameraComponent.viewfinder.zoom =
|
||||
cameraComponent.viewfinder.zoom.clamp(0.05, 5.0);
|
||||
}
|
||||
}
|
||||
|
||||
@ -392,12 +401,6 @@ class LayerComponent extends PositionComponent {
|
||||
}
|
||||
}
|
||||
|
||||
extension Vector2Ext on Vector2 {
|
||||
Vector2 translate(double x, double y) {
|
||||
return Vector2(this.x + x, this.y + y);
|
||||
}
|
||||
}
|
||||
|
||||
class QuadTreeDebugComponent extends PositionComponent with HasPaint {
|
||||
QuadTreeDebugComponent(QuadTreeCollisionDetection cd) {
|
||||
dbg = QuadTreeNodeDebugInfo.init(cd);
|
||||
@ -408,22 +411,22 @@ class QuadTreeDebugComponent extends PositionComponent with HasPaint {
|
||||
|
||||
late final QuadTreeNodeDebugInfo dbg;
|
||||
|
||||
final _boxPaint = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
..color = Colors.lightGreenAccent
|
||||
..strokeWidth = 1;
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
final nodes = dbg.nodes;
|
||||
for (final node in nodes) {
|
||||
canvas.drawRect(node.rect, paint);
|
||||
final nodeElements = node.ownElements;
|
||||
Paint? boxPaint;
|
||||
if (!node.noChildren && nodeElements.isNotEmpty) {
|
||||
boxPaint = Paint();
|
||||
boxPaint.style = PaintingStyle.stroke;
|
||||
boxPaint.color = Colors.lightGreenAccent;
|
||||
boxPaint.strokeWidth = 1;
|
||||
}
|
||||
|
||||
final shouldPaint = !node.noChildren && nodeElements.isNotEmpty;
|
||||
for (final box in nodeElements) {
|
||||
if (boxPaint != null) {
|
||||
canvas.drawRect(box.aabb.toRect(), boxPaint);
|
||||
if (shouldPaint) {
|
||||
canvas.drawRect(box.aabb.toRect(), _boxPaint);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/geometry.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flame_noise/flame_noise.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class RaycastMaxDistanceExample extends FlameGame with HasCollisionDetection {
|
||||
@ -16,6 +17,8 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
|
||||
late Ray2 _ray;
|
||||
late _Character _character;
|
||||
late CameraComponent cameraComponent;
|
||||
final world = World();
|
||||
final _result = RaycastResult<ShapeHitbox>();
|
||||
|
||||
final _text = TextComponent(
|
||||
@ -27,18 +30,23 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
color: Colors.amber,
|
||||
),
|
||||
),
|
||||
)..positionType = PositionType.viewport;
|
||||
);
|
||||
|
||||
@override
|
||||
void onLoad() {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(320, 180));
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 320,
|
||||
height: 180,
|
||||
);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
_addMovingWall();
|
||||
|
||||
add(
|
||||
world.add(
|
||||
_character = _Character(
|
||||
maxDistance: _maxDistance,
|
||||
position: size / 2 - Vector2(50, 0),
|
||||
position: Vector2(-50, 0),
|
||||
anchor: Anchor.center,
|
||||
),
|
||||
);
|
||||
@ -52,9 +60,8 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
}
|
||||
|
||||
void _addMovingWall() {
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent(
|
||||
position: size / 2,
|
||||
size: Vector2(20, 40),
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.red.paint(),
|
||||
@ -77,11 +84,16 @@ This examples showcases how raycast APIs can be used to detect hits within certa
|
||||
void update(double dt) {
|
||||
collisionDetection.raycast(_ray, maxDistance: _maxDistance, out: _result);
|
||||
if (_result.isActive) {
|
||||
if (!camera.shaking) {
|
||||
camera.shake(duration: 0.2, intensity: 1);
|
||||
if (cameraComponent.viewfinder.children.query<Effect>().isEmpty) {
|
||||
cameraComponent.viewfinder.add(
|
||||
MoveEffect.by(
|
||||
Vector2(5, 5),
|
||||
PerlinNoiseEffectController(duration: 0.2, frequency: 400),
|
||||
),
|
||||
);
|
||||
}
|
||||
if (!_text.isMounted) {
|
||||
add(_text);
|
||||
world.add(_text);
|
||||
}
|
||||
} else {
|
||||
_text.removeFromParent();
|
||||
@ -101,26 +113,22 @@ class _Character extends PositionComponent {
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
add(
|
||||
addAll([
|
||||
CircleComponent(
|
||||
radius: 20,
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.green.paint(),
|
||||
)..scale = Vector2(0.55, 1),
|
||||
);
|
||||
add(
|
||||
CircleComponent(
|
||||
radius: 10,
|
||||
anchor: Anchor.center,
|
||||
paint: _rayPaint,
|
||||
),
|
||||
);
|
||||
add(
|
||||
RectangleComponent(
|
||||
size: Vector2(10, 3),
|
||||
position: Vector2(12, 5),
|
||||
),
|
||||
);
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -45,7 +45,7 @@ bounce on will appear.
|
||||
addAll([
|
||||
ScreenHitbox(),
|
||||
CircleComponent(
|
||||
radius: min(camera.canvasSize.x, camera.canvasSize.y) / 2,
|
||||
radius: min(canvasSize.x, canvasSize.y) / 2,
|
||||
paint: boxPaint,
|
||||
children: [CircleHitbox()],
|
||||
),
|
||||
|
||||
@ -19,18 +19,9 @@ class _Rectangle extends RectangleComponent {
|
||||
[Colors.orange, Colors.blue],
|
||||
),
|
||||
children: [
|
||||
SequenceEffect(
|
||||
[
|
||||
RotateEffect.by(
|
||||
pi * 2,
|
||||
LinearEffectController(.4),
|
||||
),
|
||||
RotateEffect.by(
|
||||
0,
|
||||
LinearEffectController(.4),
|
||||
),
|
||||
],
|
||||
infinite: true,
|
||||
RotateEffect.by(
|
||||
pi * 2,
|
||||
EffectController(duration: .4, infinite: true),
|
||||
),
|
||||
],
|
||||
);
|
||||
|
||||
@ -5,7 +5,6 @@ import 'package:examples/stories/components/components_notifier_example.dart';
|
||||
import 'package:examples/stories/components/components_notifier_provider_example.dart';
|
||||
import 'package:examples/stories/components/composability_example.dart';
|
||||
import 'package:examples/stories/components/debug_example.dart';
|
||||
import 'package:examples/stories/components/game_in_game_example.dart';
|
||||
import 'package:examples/stories/components/look_at_example.dart';
|
||||
import 'package:examples/stories/components/look_at_smooth_example.dart';
|
||||
import 'package:examples/stories/components/priority_example.dart';
|
||||
@ -32,12 +31,6 @@ void addComponentsStories(Dashbook dashbook) {
|
||||
codeLink: baseLink('components/debug_example.dart'),
|
||||
info: DebugExample.description,
|
||||
)
|
||||
..add(
|
||||
'Game-in-game',
|
||||
(_) => GameWidget(game: GameInGameExample()),
|
||||
codeLink: baseLink('components/game_in_game_example.dart'),
|
||||
info: GameInGameExample.description,
|
||||
)
|
||||
..add(
|
||||
'ClipComponent',
|
||||
(context) => GameWidget(game: ClipComponentExample()),
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import 'package:examples/stories/components/composability_example.dart';
|
||||
import 'package:examples/stories/input/draggables_example.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
class GameInGameExample extends FlameGame {
|
||||
static const String description = '''
|
||||
This example shows two games having another game as a parent.
|
||||
One game contains draggable components and the other is a rotating square
|
||||
with other square children.
|
||||
After 5 seconds, one of the components from the game with draggable squares
|
||||
changes its parent from its original game to the component that is rotating.
|
||||
After another 5 seconds it changes back to its original parent, and so on.
|
||||
''';
|
||||
|
||||
@override
|
||||
bool debugMode = true;
|
||||
late final ComposabilityExample composedGame;
|
||||
late final DraggablesExample draggablesGame;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
composedGame = ComposabilityExample();
|
||||
draggablesGame = DraggablesExample(zoom: 1.0);
|
||||
await add(composedGame);
|
||||
await add(draggablesGame);
|
||||
|
||||
add(GameChangeTimer());
|
||||
}
|
||||
}
|
||||
|
||||
class GameChangeTimer extends TimerComponent
|
||||
with HasGameRef<GameInGameExample> {
|
||||
GameChangeTimer() : super(period: 5, repeat: true);
|
||||
|
||||
@override
|
||||
void onTick() {
|
||||
final child = gameRef.draggablesGame.square;
|
||||
final newParent = child.parent == gameRef.draggablesGame
|
||||
? gameRef.composedGame.parentSquare as Component
|
||||
: gameRef.draggablesGame;
|
||||
child.parent = newParent;
|
||||
}
|
||||
}
|
||||
@ -1,14 +1,14 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LookAtExample extends FlameGame with TapDetector {
|
||||
class LookAtExample extends FlameGame {
|
||||
static const description = 'This example demonstrates how a component can be '
|
||||
'made to look at a specific target using the lookAt method. Tap anywhere '
|
||||
'to change the target point for both the choppers. '
|
||||
@ -16,21 +16,20 @@ class LookAtExample extends FlameGame with TapDetector {
|
||||
'oriented in the desired direction if the image is not facing the '
|
||||
'correct direction.';
|
||||
|
||||
final world = _TapWorld();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
late SpriteAnimationComponent _chopper1;
|
||||
late SpriteAnimationComponent _chopper2;
|
||||
|
||||
final CircleComponent _targetComponent = CircleComponent(
|
||||
radius: 5,
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.black.paint(),
|
||||
);
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color.fromARGB(255, 96, 145, 112);
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(640, 360));
|
||||
Future<void> onLoad() async {
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
final spriteSheet = SpriteSheet(
|
||||
image: await images.load('animations/chopper.png'),
|
||||
srcSize: Vector2.all(48),
|
||||
@ -38,45 +37,29 @@ class LookAtExample extends FlameGame with TapDetector {
|
||||
|
||||
_spawnChoppers(spriteSheet);
|
||||
_spawnInfoText();
|
||||
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownInfo info) {
|
||||
if (!_targetComponent.isMounted) {
|
||||
add(_targetComponent);
|
||||
}
|
||||
_targetComponent.position = info.eventPosition.game;
|
||||
|
||||
_chopper1.lookAt(_targetComponent.absolutePosition);
|
||||
_chopper2.lookAt(_targetComponent.absolutePosition);
|
||||
|
||||
super.onTapDown(info);
|
||||
}
|
||||
|
||||
void _spawnChoppers(SpriteSheet spriteSheet) {
|
||||
// Notice now the nativeAngle is set to pi because the chopper
|
||||
// is facing in down/south direction in the original image.
|
||||
add(
|
||||
world.add(
|
||||
_chopper1 = SpriteAnimationComponent(
|
||||
nativeAngle: pi,
|
||||
size: Vector2.all(64),
|
||||
size: Vector2.all(128),
|
||||
anchor: Anchor.center,
|
||||
animation: spriteSheet.createAnimation(row: 0, stepTime: 0.05),
|
||||
position: Vector2(size.x * 0.3, size.y * 0.5),
|
||||
),
|
||||
);
|
||||
|
||||
// This chopper does not use correct nativeAngle, hence using
|
||||
// lookAt on it results in the sprite pointing in incorrect
|
||||
// direction visually.
|
||||
add(
|
||||
world.add(
|
||||
_chopper2 = SpriteAnimationComponent(
|
||||
size: Vector2.all(64),
|
||||
size: Vector2.all(128),
|
||||
anchor: Anchor.center,
|
||||
animation: spriteSheet.createAnimation(row: 0, stepTime: 0.05),
|
||||
position: Vector2(size.x * 0.6, size.y * 0.5),
|
||||
position: Vector2(0, 160),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -86,29 +69,49 @@ class LookAtExample extends FlameGame with TapDetector {
|
||||
final shaded = TextPaint(
|
||||
style: TextStyle(
|
||||
color: BasicPalette.white.color,
|
||||
fontSize: 20.0,
|
||||
fontSize: 30.0,
|
||||
shadows: const [
|
||||
Shadow(offset: Offset(1, 1), blurRadius: 1),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
add(
|
||||
world.add(
|
||||
TextComponent(
|
||||
text: 'nativeAngle = pi',
|
||||
textRenderer: shaded,
|
||||
anchor: Anchor.center,
|
||||
position: _chopper1.absolutePosition + Vector2(0, -50),
|
||||
position: _chopper1.absolutePosition + Vector2(0, -70),
|
||||
),
|
||||
);
|
||||
|
||||
add(
|
||||
world.add(
|
||||
TextComponent(
|
||||
text: 'nativeAngle = 0',
|
||||
textRenderer: shaded,
|
||||
anchor: Anchor.center,
|
||||
position: _chopper2.absolutePosition + Vector2(0, -50),
|
||||
position: _chopper2.absolutePosition + Vector2(0, -70),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TapWorld extends World with TapCallbacks {
|
||||
final CircleComponent _targetComponent = CircleComponent(
|
||||
radius: 5,
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.black.paint(),
|
||||
);
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownEvent event) {
|
||||
if (!_targetComponent.isMounted) {
|
||||
add(_targetComponent);
|
||||
}
|
||||
_targetComponent.position = event.localPosition;
|
||||
final choppers = children.query<SpriteAnimationComponent>();
|
||||
for (final chopper in choppers) {
|
||||
chopper.lookAt(event.localPosition);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -2,14 +2,14 @@ import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame/palette.dart';
|
||||
import 'package:flame/sprite.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class LookAtSmoothExample extends FlameGame with TapDetector {
|
||||
class LookAtSmoothExample extends FlameGame {
|
||||
static const description = 'This example demonstrates how a component can be '
|
||||
'made to smoothly rotate towards a target using the angleTo method. '
|
||||
'Tap anywhere to change the target point for both the choppers. '
|
||||
@ -17,22 +17,20 @@ class LookAtSmoothExample extends FlameGame with TapDetector {
|
||||
'oriented in the desired direction if the image is not facing the '
|
||||
'correct direction.';
|
||||
|
||||
bool _isRotating = false;
|
||||
final world = _TapWorld();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
late SpriteAnimationComponent _chopper1;
|
||||
late SpriteAnimationComponent _chopper2;
|
||||
|
||||
final CircleComponent _targetComponent = CircleComponent(
|
||||
radius: 5,
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.black.paint(),
|
||||
);
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color.fromARGB(255, 96, 145, 112);
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(640, 360));
|
||||
Future<void> onLoad() async {
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
final spriteSheet = SpriteSheet(
|
||||
image: await images.load('animations/chopper.png'),
|
||||
srcSize: Vector2.all(48),
|
||||
@ -40,63 +38,29 @@ class LookAtSmoothExample extends FlameGame with TapDetector {
|
||||
|
||||
_spawnChoppers(spriteSheet);
|
||||
_spawnInfoText();
|
||||
|
||||
return super.onLoad();
|
||||
}
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownInfo info) {
|
||||
if (!_targetComponent.isMounted) {
|
||||
add(_targetComponent);
|
||||
}
|
||||
|
||||
// Ignore if choppers are already rotating.
|
||||
if (!_isRotating) {
|
||||
_isRotating = true;
|
||||
_targetComponent.position = info.eventPosition.game;
|
||||
|
||||
_chopper1.add(
|
||||
RotateEffect.by(
|
||||
_chopper1.angleTo(_targetComponent.absolutePosition),
|
||||
LinearEffectController(1),
|
||||
onComplete: () => _isRotating = false,
|
||||
),
|
||||
);
|
||||
|
||||
_chopper2.add(
|
||||
RotateEffect.by(
|
||||
_chopper2.angleTo(_targetComponent.absolutePosition),
|
||||
LinearEffectController(1),
|
||||
onComplete: () => _isRotating = false,
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
super.onTapDown(info);
|
||||
}
|
||||
|
||||
void _spawnChoppers(SpriteSheet spriteSheet) {
|
||||
// Notice now the nativeAngle is set to pi because the chopper
|
||||
// is facing in down/south direction in the original image.
|
||||
add(
|
||||
world.add(
|
||||
_chopper1 = SpriteAnimationComponent(
|
||||
nativeAngle: pi,
|
||||
size: Vector2.all(64),
|
||||
size: Vector2.all(128),
|
||||
anchor: Anchor.center,
|
||||
animation: spriteSheet.createAnimation(row: 0, stepTime: 0.05),
|
||||
position: Vector2(size.x * 0.3, size.y * 0.5),
|
||||
),
|
||||
);
|
||||
|
||||
// This chopper does not use correct nativeAngle, hence using
|
||||
// lookAt on it results in the sprite pointing in incorrect
|
||||
// direction visually.
|
||||
add(
|
||||
world.add(
|
||||
_chopper2 = SpriteAnimationComponent(
|
||||
size: Vector2.all(64),
|
||||
size: Vector2.all(128),
|
||||
anchor: Anchor.center,
|
||||
animation: spriteSheet.createAnimation(row: 0, stepTime: 0.05),
|
||||
position: Vector2(size.x * 0.6, size.y * 0.5),
|
||||
position: Vector2(0, 160),
|
||||
),
|
||||
);
|
||||
}
|
||||
@ -106,29 +70,63 @@ class LookAtSmoothExample extends FlameGame with TapDetector {
|
||||
final shaded = TextPaint(
|
||||
style: TextStyle(
|
||||
color: BasicPalette.white.color,
|
||||
fontSize: 20.0,
|
||||
fontSize: 30.0,
|
||||
shadows: const [
|
||||
Shadow(offset: Offset(1, 1), blurRadius: 1),
|
||||
],
|
||||
),
|
||||
);
|
||||
|
||||
add(
|
||||
world.add(
|
||||
TextComponent(
|
||||
text: 'nativeAngle = pi',
|
||||
textRenderer: shaded,
|
||||
anchor: Anchor.center,
|
||||
position: _chopper1.absolutePosition + Vector2(0, -50),
|
||||
position: _chopper1.absolutePosition + Vector2(0, -70),
|
||||
),
|
||||
);
|
||||
|
||||
add(
|
||||
world.add(
|
||||
TextComponent(
|
||||
text: 'nativeAngle = 0',
|
||||
textRenderer: shaded,
|
||||
anchor: Anchor.center,
|
||||
position: _chopper2.absolutePosition + Vector2(0, -50),
|
||||
position: _chopper2.absolutePosition + Vector2(0, -70),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _TapWorld extends World with TapCallbacks {
|
||||
bool _isRotating = false;
|
||||
|
||||
final CircleComponent _targetComponent = CircleComponent(
|
||||
radius: 5,
|
||||
anchor: Anchor.center,
|
||||
paint: BasicPalette.black.paint(),
|
||||
);
|
||||
|
||||
@override
|
||||
void onTapDown(TapDownEvent event) {
|
||||
if (!_targetComponent.isMounted) {
|
||||
add(_targetComponent);
|
||||
}
|
||||
|
||||
// Ignore if choppers are already rotating.
|
||||
if (!_isRotating) {
|
||||
_isRotating = true;
|
||||
_targetComponent.position = event.localPosition;
|
||||
|
||||
final choppers = children.query<SpriteAnimationComponent>();
|
||||
for (final chopper in choppers) {
|
||||
chopper.add(
|
||||
RotateEffect.by(
|
||||
chopper.angleTo(_targetComponent.absolutePosition),
|
||||
LinearEffectController(1),
|
||||
onComplete: () => _isRotating = false,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -31,25 +31,33 @@ class TimeScaleExample extends FlameGame
|
||||
@override
|
||||
Color backgroundColor() => const Color.fromARGB(255, 88, 114, 97);
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(640, 360));
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 640,
|
||||
height: 360,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
final spriteSheet = SpriteSheet(
|
||||
image: await images.load('animations/chopper.png'),
|
||||
srcSize: Vector2.all(48),
|
||||
);
|
||||
gameSpeedText.position = Vector2(size.x * 0.5, size.y * 0.8);
|
||||
|
||||
await addAll([
|
||||
await world.addAll([
|
||||
_Chopper(
|
||||
position: Vector2(size.x * 0.3, size.y * 0.45),
|
||||
position: Vector2(-100, -10),
|
||||
size: Vector2.all(64),
|
||||
anchor: Anchor.center,
|
||||
angle: -pi / 2,
|
||||
animation: spriteSheet.createAnimation(row: 0, stepTime: 0.05),
|
||||
),
|
||||
_Chopper(
|
||||
position: Vector2(size.x * 0.6, size.y * 0.55),
|
||||
position: Vector2(100, 10),
|
||||
size: Vector2.all(64),
|
||||
anchor: Anchor.center,
|
||||
angle: pi / 2,
|
||||
|
||||
@ -1,5 +1,6 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/camera.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/geometry.dart';
|
||||
@ -19,12 +20,21 @@ class EffectControllersExample extends FlameGame {
|
||||
delayed.
|
||||
''';
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
|
||||
add(
|
||||
void onLoad() {
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(20, 50),
|
||||
position: Vector2(-140, 0),
|
||||
size: 20,
|
||||
)..add(
|
||||
MoveEffect.by(
|
||||
@ -33,9 +43,9 @@ class EffectControllersExample extends FlameGame {
|
||||
),
|
||||
),
|
||||
);
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(70, 50),
|
||||
position: Vector2(-50, 0),
|
||||
size: 20,
|
||||
paint: Paint()..color = const Color(0xffffbc63),
|
||||
)..addAll([
|
||||
@ -50,9 +60,9 @@ class EffectControllersExample extends FlameGame {
|
||||
]),
|
||||
);
|
||||
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(140, 50),
|
||||
position: Vector2(50, 0),
|
||||
size: 20,
|
||||
paint: Paint()..color = const Color(0xffbeff63),
|
||||
)..add(
|
||||
@ -62,9 +72,9 @@ class EffectControllersExample extends FlameGame {
|
||||
),
|
||||
),
|
||||
);
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(190, 50),
|
||||
position: Vector2(140, 0),
|
||||
size: 10,
|
||||
paint: Paint()..color = const Color(0xffb663ff),
|
||||
)..addAll([
|
||||
|
||||
@ -1,8 +1,8 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/geometry.dart';
|
||||
import 'package:flame_noise/flame_noise.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
@ -21,10 +21,19 @@ class MoveEffectExample extends FlameGame {
|
||||
an arbitrary path using `MoveEffect.along`.
|
||||
''';
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
void onLoad() {
|
||||
const tau = Transform2D.tau;
|
||||
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([world, cameraComponent]);
|
||||
|
||||
final paint1 = Paint()
|
||||
..style = PaintingStyle.stroke
|
||||
@ -37,7 +46,7 @@ class MoveEffectExample extends FlameGame {
|
||||
final paint3 = Paint()..color = const Color(0xffb372dc);
|
||||
|
||||
// Red square, moving back and forth
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(20, 50),
|
||||
size: 20,
|
||||
@ -56,7 +65,7 @@ class MoveEffectExample extends FlameGame {
|
||||
);
|
||||
|
||||
// Green square, moving and jumping
|
||||
add(
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
position: Vector2(20, 150),
|
||||
size: 20,
|
||||
@ -87,7 +96,8 @@ class MoveEffectExample extends FlameGame {
|
||||
),
|
||||
);
|
||||
|
||||
add(
|
||||
// Purple square, vibrating from two noise controllers.
|
||||
world.add(
|
||||
RectangleComponent.square(
|
||||
size: 15,
|
||||
position: Vector2(40, 240),
|
||||
@ -110,6 +120,29 @@ class MoveEffectExample extends FlameGame {
|
||||
),
|
||||
);
|
||||
|
||||
// A circle of moving rectangles.
|
||||
final path2 = Path()..addOval(const Rect.fromLTRB(80, 230, 320, 470));
|
||||
for (var i = 0; i < 20; i++) {
|
||||
world.add(
|
||||
RectangleComponent.square(size: 10)
|
||||
..position = Vector2(i * 10, 0)
|
||||
..paint = (Paint()..color = Colors.tealAccent)
|
||||
..add(
|
||||
MoveAlongPathEffect(
|
||||
path2,
|
||||
EffectController(
|
||||
duration: 6,
|
||||
startDelay: i * 0.3,
|
||||
infinite: true,
|
||||
),
|
||||
absolute: true,
|
||||
oriented: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
// A star of moving rectangles.
|
||||
final path1 = Path()..moveTo(200, 250);
|
||||
for (var i = 1; i <= 5; i++) {
|
||||
final x = 200 + 100 * sin(i * tau * 2 / 5);
|
||||
@ -117,9 +150,9 @@ class MoveEffectExample extends FlameGame {
|
||||
path1.lineTo(x, y);
|
||||
}
|
||||
for (var i = 0; i < 40; i++) {
|
||||
add(
|
||||
world.add(
|
||||
CircleComponent(radius: 5)
|
||||
..position = Vector2(0, -1000)
|
||||
..position = Vector2(i * 10, 0)
|
||||
..add(
|
||||
MoveAlongPathEffect(
|
||||
path1,
|
||||
@ -133,24 +166,5 @@ class MoveEffectExample extends FlameGame {
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
final path2 = Path()..addOval(const Rect.fromLTRB(80, 230, 320, 470));
|
||||
for (var i = 0; i < 20; i++) {
|
||||
add(
|
||||
RectangleComponent.square(size: 10)
|
||||
..paint = (Paint()..color = Colors.tealAccent)
|
||||
..add(
|
||||
MoveAlongPathEffect(
|
||||
path2,
|
||||
EffectController(
|
||||
duration: 6,
|
||||
startDelay: i * 0.3,
|
||||
infinite: true,
|
||||
),
|
||||
oriented: true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -12,13 +12,22 @@ class RemoveEffectExample extends FlameGame {
|
||||
disappear after a 0.5 second delay.
|
||||
''';
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
void onLoad() {
|
||||
super.onMount();
|
||||
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
cameraComponent.viewfinder.anchor = Anchor.topLeft;
|
||||
addAll([cameraComponent, world]);
|
||||
final rng = Random();
|
||||
for (var i = 0; i < 20; i++) {
|
||||
add(_RandomCircle.random(rng));
|
||||
world.add(_RandomCircle.random(rng));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -16,11 +16,20 @@ class RotateEffectExample extends FlameGame {
|
||||
add small amounts of wobble, creating quasi-chaotic movement.
|
||||
''';
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
void onMount() {
|
||||
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
|
||||
final compass = Compass(200)..position = Vector2(200, 300);
|
||||
add(compass);
|
||||
void onLoad() {
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
final compass = Compass(size: 200);
|
||||
world.add(compass);
|
||||
|
||||
compass.rim.add(
|
||||
RotateEffect.by(
|
||||
@ -67,7 +76,7 @@ class RotateEffectExample extends FlameGame {
|
||||
}
|
||||
|
||||
class Compass extends PositionComponent {
|
||||
Compass(double size)
|
||||
Compass({required double size})
|
||||
: _radius = size / 2,
|
||||
super(
|
||||
size: Vector2.all(size),
|
||||
|
||||
@ -1,25 +1,31 @@
|
||||
import 'package:examples/commons/ember.dart';
|
||||
import 'package:flame/camera.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart' show Colors;
|
||||
|
||||
class DraggablesExample extends FlameGame {
|
||||
static const String description = '''
|
||||
In this example we show you can use the `Draggable` mixin on
|
||||
In this example we show you can use the `DragCallbacks` mixin on
|
||||
`PositionComponent`s. Drag around the Embers and see their position
|
||||
changing.
|
||||
''';
|
||||
|
||||
DraggablesExample({required this.zoom});
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
final double zoom;
|
||||
late final DraggableEmber square;
|
||||
|
||||
DraggablesExample({required this.zoom});
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.zoom = zoom;
|
||||
add(square = DraggableEmber());
|
||||
add(DraggableEmber()..y = 350);
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
cameraComponent.viewfinder.zoom = zoom;
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
world.add(square = DraggableEmber());
|
||||
world.add(DraggableEmber()..y = 350);
|
||||
}
|
||||
}
|
||||
|
||||
@ -29,28 +35,24 @@ class DraggableEmber extends Ember with DragCallbacks {
|
||||
@override
|
||||
bool debugMode = true;
|
||||
|
||||
DraggableEmber({Vector2? position})
|
||||
: super(
|
||||
position: position ?? Vector2.all(100),
|
||||
size: Vector2.all(100),
|
||||
);
|
||||
DraggableEmber({super.position}) : super(size: Vector2.all(100));
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
debugColor = isDragged && parent is DraggablesExample
|
||||
debugColor = isDragged && findGame() is DraggablesExample
|
||||
? Colors.greenAccent
|
||||
: Colors.purple;
|
||||
}
|
||||
|
||||
@override
|
||||
void onDragUpdate(DragUpdateEvent event) {
|
||||
if (parent is! DraggablesExample) {
|
||||
if (findGame() is! DraggablesExample) {
|
||||
event.continuePropagation = true;
|
||||
return;
|
||||
}
|
||||
|
||||
position.add(event.localPosition);
|
||||
position.add(event.delta);
|
||||
event.continuePropagation = false;
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/collisions.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/extensions.dart';
|
||||
import 'package:flame/game.dart';
|
||||
|
||||
@ -25,8 +25,14 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection {
|
||||
late final TextComponent speedText;
|
||||
late final TextComponent directionText;
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
final image = await images.load('joystick.png');
|
||||
final sheet = SpriteSheet.fromColumnsAndRows(
|
||||
image: image,
|
||||
@ -158,11 +164,11 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection {
|
||||
speedText = TextComponent(
|
||||
text: 'Speed: 0',
|
||||
textRenderer: regular,
|
||||
)..positionType = PositionType.viewport;
|
||||
);
|
||||
directionText = TextComponent(
|
||||
text: 'Direction: idle',
|
||||
textRenderer: regular,
|
||||
)..positionType = PositionType.viewport;
|
||||
);
|
||||
|
||||
final speedWithMargin = HudMarginComponent(
|
||||
margin: const EdgeInsets.only(
|
||||
@ -185,8 +191,8 @@ class JoystickAdvancedExample extends FlameGame with HasCollisionDetection {
|
||||
add(buttonComponent);
|
||||
add(spriteButtonComponent);
|
||||
add(shapeButton);
|
||||
add(speedWithMargin);
|
||||
add(directionWithMargin);
|
||||
cameraComponent.viewport.add(speedWithMargin);
|
||||
cameraComponent.viewport.add(directionWithMargin);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -32,7 +32,6 @@ class IsometricTileMapExample extends FlameGame with MouseMovementDetector {
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
debugMode = true;
|
||||
final tilesetImage = await images.load('tile_maps/tiles$suffix.png');
|
||||
final tileset = SpriteSheet(
|
||||
image: tilesetImage,
|
||||
|
||||
@ -16,6 +16,8 @@ class ParticlesInteractiveExample extends FlameGame with PanDetector {
|
||||
final Tween<double> noise = Tween(begin: -1, end: 1);
|
||||
final ColorTween colorTween;
|
||||
final double zoom;
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
ParticlesInteractiveExample({
|
||||
required Color from,
|
||||
@ -25,9 +27,13 @@ class ParticlesInteractiveExample extends FlameGame with PanDetector {
|
||||
|
||||
@override
|
||||
Future<void> onLoad() async {
|
||||
camera.followVector2(Vector2.zero());
|
||||
|
||||
camera.zoom = zoom;
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
addAll([cameraComponent, world]);
|
||||
cameraComponent.viewfinder.zoom = zoom;
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -4,7 +4,8 @@ import 'package:flame/text.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
|
||||
class RichTextExample extends FlameGame {
|
||||
static String description = '';
|
||||
static String description = 'A non-interactive example of how to render rich '
|
||||
'text in Flame.';
|
||||
|
||||
@override
|
||||
Color backgroundColor() => const Color(0xFF888888);
|
||||
@ -25,14 +26,14 @@ class MyTextComponent extends PositionComponent {
|
||||
height: 200,
|
||||
padding: const EdgeInsets.symmetric(vertical: 10, horizontal: 14),
|
||||
background: BackgroundStyle(
|
||||
color: const Color(0xFFFFFFEE),
|
||||
color: const Color(0xFF4E322E),
|
||||
borderColor: const Color(0xFF000000),
|
||||
borderWidth: 2.0,
|
||||
),
|
||||
paragraph: BlockStyle(
|
||||
padding: const EdgeInsets.symmetric(vertical: 2, horizontal: 6),
|
||||
padding: const EdgeInsets.symmetric(vertical: 12, horizontal: 16),
|
||||
background: BackgroundStyle(
|
||||
color: const Color(0xFFFFF0CB),
|
||||
color: const Color(0xFF004D40),
|
||||
borderColor: const Color(0xFFAAAAAA),
|
||||
),
|
||||
),
|
||||
|
||||
@ -50,7 +50,7 @@ class Background extends SvgComponent with HasGameRef<SvgComponentExample> {
|
||||
}
|
||||
|
||||
class Balloons extends SvgComponent with HasGameRef<SvgComponentExample> {
|
||||
Balloons()
|
||||
Balloons({super.position})
|
||||
: super(
|
||||
priority: 2,
|
||||
size: Vector2(75, 125),
|
||||
@ -77,44 +77,29 @@ class SvgComponentExample extends FlameGame
|
||||
''';
|
||||
|
||||
late Player player;
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
@override
|
||||
Future<void>? onLoad() async {
|
||||
await super.onLoad();
|
||||
camera.followVector2(Vector2.zero());
|
||||
|
||||
add(player = Player());
|
||||
add(Background());
|
||||
|
||||
add(
|
||||
Balloons()
|
||||
..x = -10
|
||||
..y = -20,
|
||||
cameraComponent = CameraComponent.withFixedResolution(
|
||||
world: world,
|
||||
width: 400,
|
||||
height: 600,
|
||||
);
|
||||
addAll([cameraComponent, world]);
|
||||
|
||||
add(
|
||||
Balloons()
|
||||
..x = -100
|
||||
..y = -150,
|
||||
);
|
||||
world.add(player = Player());
|
||||
world.add(Background());
|
||||
|
||||
add(
|
||||
Balloons()
|
||||
..x = -200
|
||||
..y = -140,
|
||||
);
|
||||
|
||||
add(
|
||||
Balloons()
|
||||
..x = 100
|
||||
..y = 130,
|
||||
);
|
||||
|
||||
add(
|
||||
Balloons()
|
||||
..x = 50
|
||||
..y = -130,
|
||||
);
|
||||
world.addAll([
|
||||
Balloons(position: Vector2(-10, -20)),
|
||||
Balloons(position: Vector2(-100, -150)),
|
||||
Balloons(position: Vector2(-200, -140)),
|
||||
Balloons(position: Vector2(100, 130)),
|
||||
Balloons(position: Vector2(50, -130)),
|
||||
]);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
@ -8,7 +8,7 @@ class NoFlameGameExample extends Game with KeyboardEvents {
|
||||
static const String description = '''
|
||||
This example showcases how to create a game without the FlameGame.
|
||||
It also briefly showcases how to act on keyboard events.
|
||||
Usage: Use A W S D to steer the rectangle.
|
||||
Usage: Use W A S D to steer the rectangle.
|
||||
''';
|
||||
|
||||
static final Paint white = BasicPalette.white.paint();
|
||||
|
||||
@ -1,8 +0,0 @@
|
||||
#!/bin/bash -e
|
||||
|
||||
# run this first time:
|
||||
# flutter update-packages
|
||||
|
||||
flutter analyze --flutter-repo
|
||||
flutter format .
|
||||
flutter test
|
||||
@ -1,24 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
FORMAT_ISSUES=$(flutter format --set-exit-if-changed -n .)
|
||||
if [ $? -eq 1 ]; then
|
||||
echo "flutter format issue"
|
||||
echo $FORMAT_ISSUES
|
||||
exit 1
|
||||
fi
|
||||
|
||||
flutter pub get
|
||||
result=$(flutter pub run dart_code_metrics:metrics .)
|
||||
if [ "$result" != "" ]; then
|
||||
echo "flutter dart code metrics issues: $1"
|
||||
echo "$result"
|
||||
exit 1
|
||||
fi
|
||||
result=$(flutter analyze .)
|
||||
if ! echo "$result" | grep -q "No issues found!"; then
|
||||
echo "$result"
|
||||
echo "flutter analyze issue: $1"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "success"
|
||||
exit 0
|
||||
@ -9,7 +9,6 @@ export 'src/effects/controllers/duration_effect_controller.dart';
|
||||
export 'src/effects/controllers/effect_controller.dart';
|
||||
export 'src/effects/controllers/infinite_effect_controller.dart';
|
||||
export 'src/effects/controllers/linear_effect_controller.dart';
|
||||
export 'src/effects/controllers/noise_effect_controller.dart';
|
||||
export 'src/effects/controllers/pause_effect_controller.dart';
|
||||
export 'src/effects/controllers/random_effect_controller.dart';
|
||||
export 'src/effects/controllers/repeated_effect_controller.dart';
|
||||
|
||||
@ -2,16 +2,10 @@ export 'src/events/component_mixins/double_tap_callbacks.dart'
|
||||
show DoubleTapCallbacks;
|
||||
export 'src/events/component_mixins/drag_callbacks.dart' show DragCallbacks;
|
||||
export 'src/events/component_mixins/tap_callbacks.dart' show TapCallbacks;
|
||||
export 'src/events/flame_game_mixins/has_draggable_components.dart'
|
||||
show
|
||||
HasDraggableComponents; // ignore: deprecated_member_use_from_same_package
|
||||
export 'src/events/flame_game_mixins/has_draggables_bridge.dart'
|
||||
show HasDraggablesBridge; // ignore: deprecated_member_use_from_same_package
|
||||
export 'src/events/flame_game_mixins/has_tappable_components.dart'
|
||||
show
|
||||
HasTappableComponents; // ignore: deprecated_member_use_from_same_package
|
||||
show HasDraggablesBridge;
|
||||
export 'src/events/flame_game_mixins/has_tappables_bridge.dart'
|
||||
show HasTappablesBridge; // ignore: deprecated_member_use_from_same_package
|
||||
show HasTappablesBridge;
|
||||
export 'src/events/game_mixins/multi_touch_drag_detector.dart'
|
||||
show MultiTouchDragDetector;
|
||||
export 'src/events/game_mixins/multi_touch_tap_detector.dart'
|
||||
@ -31,8 +25,11 @@ export 'src/events/messages/drag_update_event.dart' show DragUpdateEvent;
|
||||
export 'src/events/messages/tap_cancel_event.dart' show TapCancelEvent;
|
||||
export 'src/events/messages/tap_down_event.dart' show TapDownEvent;
|
||||
export 'src/events/messages/tap_up_event.dart' show TapUpEvent;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
export 'src/game/mixins/has_draggables.dart' show HasDraggables;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
export 'src/game/mixins/has_hoverables.dart' show HasHoverables;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
export 'src/game/mixins/has_tappables.dart' show HasTappables;
|
||||
export 'src/game/mixins/keyboard.dart'
|
||||
show HasKeyboardHandlerComponents, KeyboardEvents;
|
||||
|
||||
@ -54,6 +54,7 @@ class Anchor {
|
||||
if (this == otherAnchor) {
|
||||
return position;
|
||||
} else {
|
||||
// TODO(Any): This should not be creating new Vector2 objects.
|
||||
return position +
|
||||
((otherAnchor.toVector2() - toVector2())..multiply(size));
|
||||
}
|
||||
|
||||
@ -79,7 +79,7 @@ class QuadTree<T extends Hitbox<T>> {
|
||||
childSize.dy,
|
||||
);
|
||||
default:
|
||||
assert(false, 'Invalid child index');
|
||||
assert(false, 'Invalid child index $box $zone');
|
||||
return Rect.zero;
|
||||
}
|
||||
}
|
||||
@ -316,9 +316,8 @@ class QuadTreeNodeDebugInfo {
|
||||
|
||||
List<QuadTreeNodeDebugInfo> get nodes {
|
||||
final list = <QuadTreeNodeDebugInfo>[this];
|
||||
var i = 0;
|
||||
for (final node in this.node.children) {
|
||||
i++;
|
||||
for (var i = 0; i < node.children.length; i++) {
|
||||
final node = this.node.children[i];
|
||||
if (node == null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -18,6 +18,9 @@ class ScreenHitbox<T extends FlameGame> extends PositionComponent
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
// TODO(Lukas): Pass in a CameraComponent and use the position of the
|
||||
// viewfinder, or only allow this to be attached to a viewport.
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
position = gameRef.camera.unprojectVector(_zeroVector);
|
||||
}
|
||||
|
||||
|
||||
@ -745,20 +745,6 @@ class Component {
|
||||
}
|
||||
}
|
||||
|
||||
/// Usually this is not something that the user would want to call since the
|
||||
/// component list isn't re-ordered when it is called.
|
||||
/// See FlameGame.changePriority instead.
|
||||
@Deprecated('Will be removed in 1.8.0. Use priority setter instead.')
|
||||
// ignore: use_setters_to_change_properties
|
||||
void changePriorityWithoutResorting(int priority) => _priority = priority;
|
||||
|
||||
/// Call this if any of this component's children priorities have changed
|
||||
/// at runtime.
|
||||
///
|
||||
/// This will call [ComponentSet.rebalanceAll] on the [children] ordered set.
|
||||
@Deprecated('Will be removed in 1.8.0, it is now done automatically.')
|
||||
void reorderChildren() => _children?.rebalanceAll();
|
||||
|
||||
//#endregion
|
||||
|
||||
//#region Internal lifecycle management
|
||||
@ -973,8 +959,14 @@ class Component {
|
||||
///
|
||||
/// Do note that this currently only works if the component is added directly
|
||||
/// to the root `FlameGame`.
|
||||
@Deprecated('''
|
||||
Use the CameraComponent and add your component to the viewport with
|
||||
cameraComponent.viewport.add(yourHudComponent) instead.
|
||||
This will be removed in Flame v2.
|
||||
''')
|
||||
PositionType positionType = PositionType.game;
|
||||
|
||||
@Deprecated('To be removed in Flame v2')
|
||||
@protected
|
||||
Vector2 eventPosition(PositionInfo info) {
|
||||
switch (positionType) {
|
||||
|
||||
@ -77,40 +77,4 @@ class ComponentSet extends QueryableOrderedSet<Component> {
|
||||
super.clear();
|
||||
elements.forEach(super.add);
|
||||
}
|
||||
|
||||
/// Call this on your update method.
|
||||
///
|
||||
/// This method effectuates any pending operations of insertion or removal,
|
||||
/// and thus actually modifies the components set.
|
||||
/// Note: do not call this while iterating the set.
|
||||
@Deprecated('Will be removed in 1.8.0.')
|
||||
void updateComponentList() {}
|
||||
|
||||
@Deprecated('Will be removed in 1.8.0.')
|
||||
@override
|
||||
void rebalanceAll() => reorder();
|
||||
|
||||
@Deprecated('Will be removed in 1.8.0.')
|
||||
@override
|
||||
void rebalanceWhere(bool Function(Component element) test) {
|
||||
// bypass the wrapper because the components are already added
|
||||
final elements = super.removeWhere(test).toList();
|
||||
elements.forEach(super.add);
|
||||
}
|
||||
|
||||
/// Changes the priority of [component] and reorders the games component list.
|
||||
///
|
||||
/// Returns true if changing the component's priority modified one of the
|
||||
/// components that existed directly on the game and false if it
|
||||
/// either was a child of another component, if it didn't exist at all or if
|
||||
/// it was a component added directly on the game but its priority didn't
|
||||
/// change.
|
||||
@Deprecated('Will be removed in 1.8.0.')
|
||||
bool changePriority(
|
||||
Component component,
|
||||
int priority,
|
||||
) {
|
||||
component.priority = priority;
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
@ -18,7 +18,6 @@ class FpsTextComponent<T extends TextRenderer> extends TextComponent {
|
||||
super(
|
||||
priority: priority ?? double.maxFinite.toInt(),
|
||||
) {
|
||||
positionType = PositionType.viewport;
|
||||
add(fpsComponent);
|
||||
}
|
||||
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import 'package:collection/collection.dart';
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/src/effects/provider_interfaces.dart';
|
||||
import 'package:flutter/widgets.dart' show EdgeInsets;
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
/// The [HudMarginComponent] positions itself by a margin to the edge of the
|
||||
/// screen instead of by an absolute position on the screen or on the game, so
|
||||
/// if the game is resized the component will move to keep its margin.
|
||||
/// parent instead of by an absolute position on the screen or on the game, so
|
||||
/// if the parent is resized the component will move to keep its margin.
|
||||
///
|
||||
/// Note that the margin is calculated to the [Anchor], not to the edge of the
|
||||
/// component.
|
||||
@ -13,15 +15,7 @@ import 'package:meta/meta.dart';
|
||||
/// If you set the position of the component instead of a margin when
|
||||
/// initializing the component, the margin to the edge of the screen from that
|
||||
/// position will be used.
|
||||
class HudMarginComponent<T extends FlameGame> extends PositionComponent
|
||||
with HasGameRef<T> {
|
||||
@override
|
||||
PositionType positionType = PositionType.viewport;
|
||||
|
||||
/// Instead of setting a position of the [HudMarginComponent] a margin
|
||||
/// from the edges of the viewport can be used instead.
|
||||
EdgeInsets? margin;
|
||||
|
||||
class HudMarginComponent extends PositionComponent {
|
||||
HudMarginComponent({
|
||||
this.margin,
|
||||
super.position,
|
||||
@ -36,19 +30,33 @@ class HudMarginComponent<T extends FlameGame> extends PositionComponent
|
||||
'Either margin or position must be defined',
|
||||
);
|
||||
|
||||
/// Instead of setting a position of the [HudMarginComponent] a margin
|
||||
/// from the edges of the viewport can be used instead.
|
||||
EdgeInsets? margin;
|
||||
|
||||
late ReadonlySizeProvider? _sizeProvider;
|
||||
|
||||
@override
|
||||
@mustCallSuper
|
||||
Future<void> onLoad() async {
|
||||
super.onLoad();
|
||||
// If margin is not null we will update the position `onGameResize` instead
|
||||
void onMount() {
|
||||
super.onMount();
|
||||
_sizeProvider =
|
||||
ancestors().firstWhereOrNull((c) => c is ReadonlySizeProvider)
|
||||
as ReadonlySizeProvider?;
|
||||
assert(
|
||||
_sizeProvider != null,
|
||||
'The parent of a HudMarginComponent needs to provide a size, for example '
|
||||
'by being a PositionComponent.',
|
||||
);
|
||||
final sizeProvider = _sizeProvider!;
|
||||
|
||||
if (margin == null) {
|
||||
final screenSize = gameRef.size;
|
||||
final topLeft = anchor.toOtherAnchorPosition(
|
||||
position,
|
||||
Anchor.topLeft,
|
||||
scaledSize,
|
||||
);
|
||||
final bottomRight = screenSize -
|
||||
final bottomRight = sizeProvider.size -
|
||||
anchor.toOtherAnchorPosition(
|
||||
position,
|
||||
Anchor.bottomRight,
|
||||
@ -63,26 +71,30 @@ class HudMarginComponent<T extends FlameGame> extends PositionComponent
|
||||
} else {
|
||||
size.addListener(_updateMargins);
|
||||
}
|
||||
if (sizeProvider.size is NotifyingVector2) {
|
||||
(sizeProvider.size as NotifyingVector2).addListener(_updateMargins);
|
||||
}
|
||||
_updateMargins();
|
||||
}
|
||||
|
||||
@override
|
||||
void onGameResize(Vector2 size) {
|
||||
super.onGameResize(size);
|
||||
_updateMargins();
|
||||
if (isMounted &&
|
||||
(parent is FlameGame ||
|
||||
(parent! as ReadonlySizeProvider).size is NotifyingVector2)) {
|
||||
_updateMargins();
|
||||
}
|
||||
}
|
||||
|
||||
void _updateMargins() {
|
||||
final screenSize = positionType == PositionType.viewport
|
||||
? gameRef.camera.viewport.effectiveSize
|
||||
: gameRef.canvasSize;
|
||||
final margin = this.margin!;
|
||||
final x = margin.left != 0
|
||||
? margin.left + scaledSize.x / 2
|
||||
: screenSize.x - margin.right - scaledSize.x / 2;
|
||||
: _sizeProvider!.size.x - margin.right - scaledSize.x / 2;
|
||||
final y = margin.top != 0
|
||||
? margin.top + scaledSize.y / 2
|
||||
: screenSize.y - margin.bottom - scaledSize.y / 2;
|
||||
: _sizeProvider!.size.y - margin.bottom - scaledSize.y / 2;
|
||||
position.setValues(x, y);
|
||||
position = Anchor.center.toOtherAnchorPosition(
|
||||
position,
|
||||
|
||||
@ -54,6 +54,9 @@ class IsometricTileMapComponent extends PositionComponent {
|
||||
/// Note: this must be measured in the destination space.
|
||||
double? tileHeight;
|
||||
|
||||
/// Where the tileset's image is stored.
|
||||
Sprite _renderSprite;
|
||||
|
||||
IsometricTileMapComponent(
|
||||
this.tileset,
|
||||
this.matrix, {
|
||||
@ -78,10 +81,8 @@ class IsometricTileMapComponent extends PositionComponent {
|
||||
/// tile size.
|
||||
double get effectiveTileHeight => tileHeight ?? (effectiveTileSize.y / 2);
|
||||
|
||||
Sprite _renderSprite;
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
_renderSprite.image = tileset.image;
|
||||
final size = effectiveTileSize;
|
||||
for (var i = 0; i < matrix.length; i++) {
|
||||
for (var j = 0; j < matrix[i].length; j++) {
|
||||
@ -114,8 +115,9 @@ class IsometricTileMapComponent extends PositionComponent {
|
||||
effectiveTileSize.x / 2,
|
||||
(effectiveTileSize.y / 2) / scalingFactor,
|
||||
)..multiply(scale);
|
||||
final pos = Vector2(i.toDouble(), j.toDouble())..multiply(halfTile);
|
||||
return cartToIso(pos) - halfTile;
|
||||
final cartesianPosition = Vector2(i.toDouble(), j.toDouble())
|
||||
..multiply(halfTile);
|
||||
return cartToIso(cartesianPosition) - halfTile;
|
||||
}
|
||||
|
||||
/// Get the position of the center of the surface of the isometric tile in
|
||||
|
||||
@ -17,6 +17,7 @@ import 'package:meta/meta.dart';
|
||||
/// Do note that this only works with the old style camera and not the
|
||||
/// [CameraComponent].
|
||||
mixin ComponentViewportMargin on PositionComponent, HasGameRef {
|
||||
// TODO(Lukas): Don't use PositionType here and use CameraComponent.
|
||||
@override
|
||||
PositionType positionType = PositionType.viewport;
|
||||
|
||||
@ -66,6 +67,7 @@ mixin ComponentViewportMargin on PositionComponent, HasGameRef {
|
||||
|
||||
void _updateMargins() {
|
||||
final screenSize = positionType == PositionType.viewport
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
? gameRef.camera.viewport.effectiveSize
|
||||
: gameRef.canvasSize;
|
||||
final margin = this.margin!;
|
||||
|
||||
@ -1,9 +1,8 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/src/events/flame_game_mixins/has_draggables_bridge.dart';
|
||||
import 'package:flame/src/game/mixins/has_draggables.dart';
|
||||
import 'package:flame/src/gestures/events.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated('Will be removed in Flame v2, use the DragCallbacks mixin instead.')
|
||||
mixin Draggable on Component {
|
||||
bool _isDragged = false;
|
||||
bool get isDragged => _isDragged;
|
||||
|
||||
@ -3,6 +3,10 @@ import 'package:flame/src/game/mixins/has_hoverables.dart';
|
||||
import 'package:flame/src/gestures/events.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated('''
|
||||
Will be removed in Flame v2, use the HoverCallbacks mixin instead.
|
||||
https://github.com/flame-engine/flame/issues/2142
|
||||
''')
|
||||
mixin Hoverable on Component {
|
||||
bool _isHovered = false;
|
||||
bool get isHovered => _isHovered;
|
||||
|
||||
@ -13,6 +13,7 @@ import 'package:meta/meta.dart';
|
||||
///
|
||||
/// See [MultiTapGestureRecognizer] for the description of each individual
|
||||
/// event.
|
||||
@Deprecated('Will be removed in Flame v2, use the TapCallbacks mixin instead.')
|
||||
mixin Tappable on Component {
|
||||
bool onTapDown(TapDownInfo info) => true;
|
||||
bool onLongTapDown(TapDownInfo info) => true;
|
||||
|
||||
@ -80,6 +80,8 @@ class ParallaxComponent<T extends FlameGame> extends PositionComponent
|
||||
if (!isFullscreen) {
|
||||
return;
|
||||
}
|
||||
// TODO(Lukas): Use CameraComponent here instead.
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
final newSize = gameRef.camera.viewport.effectiveSize;
|
||||
this.size.setFrom(newSize);
|
||||
parallax?.resize(newSize);
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'dart:ui' hide Offset;
|
||||
|
||||
import 'package:collection/collection.dart';
|
||||
|
||||
@ -1,44 +0,0 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/src/effects/controllers/duration_effect_controller.dart';
|
||||
import 'package:flutter/animation.dart' show Curve, Curves;
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
/// Effect controller that oscillates around 0 following a noise curve.
|
||||
///
|
||||
/// The [frequency] parameter controls smoothness/jerkiness of the oscillations.
|
||||
/// It is roughly proportional to the total number of swings for the duration
|
||||
/// of the effect.
|
||||
///
|
||||
/// The [taperingCurve] describes how the effect fades out over time. The
|
||||
/// curve that you supply will be flipped along the X axis, so that the effect
|
||||
/// starts at full force, and gradually reduces to zero towards the end.
|
||||
///
|
||||
/// This effect controller can be used to implement various shake effects. For
|
||||
/// example, putting into a `MoveEffect.by` will create a shake motion, where
|
||||
/// the magnitude and the direction of shaking is controlled by the effect's
|
||||
/// `offset`.
|
||||
@Deprecated('Use flame_noise instead. Will be removed in Flame 1.8.0')
|
||||
class NoiseEffectController extends DurationEffectController {
|
||||
@Deprecated('Use flame_noise instead. Will be removed in Flame 1.8.0')
|
||||
NoiseEffectController({
|
||||
required double duration,
|
||||
required this.frequency,
|
||||
this.taperingCurve = Curves.easeInOutCubic,
|
||||
Random? random,
|
||||
}) : assert(duration > 0, 'duration must be positive'),
|
||||
assert(frequency > 0, 'frequency parameter must be positive'),
|
||||
noise = SimplexNoise(random),
|
||||
super(duration);
|
||||
|
||||
final double frequency;
|
||||
final Curve taperingCurve;
|
||||
final SimplexNoise noise;
|
||||
|
||||
@override
|
||||
double get progress {
|
||||
final x = timer / duration;
|
||||
final amplitude = taperingCurve.transform(1 - x);
|
||||
return noise.noise2D(x * frequency, 0) * amplitude;
|
||||
}
|
||||
}
|
||||
@ -1,5 +1,4 @@
|
||||
import 'package:flame/src/components/core/component.dart';
|
||||
import 'package:flame/src/components/mixins/draggable.dart';
|
||||
import 'package:flame/src/events/flame_game_mixins/has_draggable_components.dart';
|
||||
import 'package:flame/src/events/messages/drag_cancel_event.dart';
|
||||
import 'package:flame/src/events/messages/drag_end_event.dart';
|
||||
@ -15,7 +14,7 @@ import 'package:meta/meta.dart';
|
||||
/// a drag if the point where the initial touch event has occurred was inside
|
||||
/// the component.
|
||||
///
|
||||
/// This mixin is intended as a replacement of the [Draggable] mixin.
|
||||
/// This mixin is the replacement of the Draggable mixin.
|
||||
mixin DragCallbacks on Component {
|
||||
bool _isDragged = false;
|
||||
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/src/components/core/component.dart';
|
||||
import 'package:flame/src/components/mixins/tappable.dart';
|
||||
import 'package:flame/src/events/flame_game_mixins/has_tappable_components.dart';
|
||||
import 'package:flame/src/events/messages/tap_cancel_event.dart';
|
||||
import 'package:flame/src/events/messages/tap_down_event.dart';
|
||||
@ -13,7 +12,7 @@ import 'package:meta/meta.dart';
|
||||
/// [containsLocalPoint] method -- the component will only be considered
|
||||
/// "tapped" if the point where the tap has occurred is inside the component.
|
||||
///
|
||||
/// This mixin is intended as a replacement of the [Tappable] mixin.
|
||||
/// This mixin is the replacement of the Tappable mixin.
|
||||
mixin TapCallbacks on Component {
|
||||
void onTapDown(TapDownEvent event) {}
|
||||
void onLongTapDown(TapDownEvent event) {}
|
||||
|
||||
@ -14,9 +14,6 @@ import 'package:flame/src/game/game_render_box.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated('This mixin will be removed in 1.8.0')
|
||||
mixin HasDraggableComponents on FlameGame {}
|
||||
|
||||
/// **MultiDragDispatcher** facilitates dispatching of drag events to the
|
||||
/// [DragCallbacks] components in the component tree. It will be attached to
|
||||
/// the [FlameGame] instance automatically whenever any [DragCallbacks]
|
||||
@ -51,6 +48,7 @@ class MultiDragDispatcher extends Component implements MultiDragListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasDraggablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Draggable>(
|
||||
(c) => c.handleDragStart(event.pointerId, info),
|
||||
);
|
||||
@ -86,6 +84,7 @@ class MultiDragDispatcher extends Component implements MultiDragListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasDraggablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Draggable>(
|
||||
(c) => c.handleDragUpdated(event.pointerId, info),
|
||||
);
|
||||
@ -110,6 +109,7 @@ class MultiDragDispatcher extends Component implements MultiDragListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasDraggablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Draggable>(
|
||||
(c) => c.handleDragEnded(event.pointerId, info),
|
||||
);
|
||||
@ -128,6 +128,7 @@ class MultiDragDispatcher extends Component implements MultiDragListener {
|
||||
});
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasDraggablesBridge) {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Draggable>(
|
||||
(c) => c.handleDragCanceled(event.pointerId),
|
||||
);
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import 'package:flame/src/components/mixins/draggable.dart';
|
||||
import 'package:flame/src/events/component_mixins/drag_callbacks.dart';
|
||||
|
||||
/// Mixin that can be added to a game to indicate that is has [Draggable]
|
||||
/// Mixin that can be added to a game to indicate that is has Draggable
|
||||
/// components (in addition to components with [DragCallbacks]).
|
||||
///
|
||||
/// This is a temporary mixin to facilitate the transition between the old and
|
||||
/// the new event system. In the future it will be deprecated.
|
||||
@Deprecated('This mixin will be removed in 1.8.0')
|
||||
mixin HasDraggablesBridge {}
|
||||
|
||||
@ -11,13 +11,6 @@ import 'package:flame/src/game/game_render_box.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated('''This mixin will be removed in 1.8.0
|
||||
|
||||
This mixin does no longer do anything since you can now add tappable
|
||||
components directly to a game without this mixin.
|
||||
''')
|
||||
mixin HasTappableComponents on FlameGame {}
|
||||
|
||||
@internal
|
||||
class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
/// The record of all components currently being touched.
|
||||
@ -48,6 +41,7 @@ class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasTappablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Tappable>(
|
||||
(c) => c.handleTapDown(event.pointerId, info),
|
||||
);
|
||||
@ -76,6 +70,7 @@ class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasTappablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Tappable>(
|
||||
(c) => c.handleLongTapDown(event.pointerId, info),
|
||||
);
|
||||
@ -109,6 +104,7 @@ class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasTappablesBridge) {
|
||||
final info = event.asInfo(game)..handled = event.handled;
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Tappable>(
|
||||
(c) => c.handleTapUp(event.pointerId, info),
|
||||
);
|
||||
@ -129,6 +125,7 @@ class MultiTapDispatcher extends Component implements MultiTapListener {
|
||||
_tapCancelImpl(event);
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
if (game is HasTappablesBridge) {
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game.propagateToChildren<Tappable>(
|
||||
(c) => c.handleTapCancel(event.pointerId),
|
||||
);
|
||||
|
||||
@ -1,10 +1,8 @@
|
||||
import 'package:flame/src/components/mixins/tappable.dart';
|
||||
import 'package:flame/src/events/component_mixins/tap_callbacks.dart';
|
||||
|
||||
/// Mixin that can be added to a game to indicate that is has [Tappable]
|
||||
/// Mixin that can be added to a game to indicate that is has Tappable
|
||||
/// components (in addition to components with [TapCallbacks]).
|
||||
///
|
||||
/// This is a temporary mixin to facilitate the transition between the old and
|
||||
/// the new event system. In the future it will be deprecated.
|
||||
@Deprecated('This mixin will be removed in 1.8.0')
|
||||
mixin HasTappablesBridge {}
|
||||
|
||||
@ -128,6 +128,17 @@ extension Vector2Extension on Vector2 {
|
||||
/// Returns the inverse of this vector.
|
||||
Vector2 inverted() => Vector2(-x, -y);
|
||||
|
||||
/// Translates this Vector2 by [x] and [y].
|
||||
void translate(double x, double y) {
|
||||
setValues(this.x + x, this.y + y);
|
||||
}
|
||||
|
||||
/// Creates a new Vector2 that is the current Vector2 translated by
|
||||
/// [x] and [y].
|
||||
Vector2 translated(double x, double y) {
|
||||
return Vector2(this.x + x, this.y + y);
|
||||
}
|
||||
|
||||
/// Smoothly moves this [Vector2] in the direction [target] by a displacement
|
||||
/// given by a distance [ds] in that direction.
|
||||
///
|
||||
|
||||
@ -9,8 +9,9 @@ import 'package:meta/meta.dart';
|
||||
/// using it in any code other than the FlameGame class is unsafe and
|
||||
/// not recommended.
|
||||
@internal
|
||||
@Deprecated('Will be removed in Flame v2')
|
||||
class CameraWrapper {
|
||||
// TODO(st-pasha): extend from Component
|
||||
@Deprecated('Will be removed in Flame v2')
|
||||
CameraWrapper(this.camera, this.world);
|
||||
|
||||
final Camera camera;
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
@ -37,6 +39,35 @@ class FlameGame extends ComponentTreeRoot
|
||||
late final List<ComponentsNotifier> notifiers = [];
|
||||
|
||||
/// The camera translates the coordinate space after the viewport is applied.
|
||||
@Deprecated('''
|
||||
In the future (maybe as early as v1.9.0) this camera will be removed,
|
||||
please use the CameraComponent instead.
|
||||
|
||||
This is the simplest way of using the CameraComponent:
|
||||
1. Add variables for a CameraComponent and a World to your game class
|
||||
|
||||
final world = World();
|
||||
late final CameraComponent cameraComponent;
|
||||
|
||||
2. In your `onLoad` method, initialize the cameraComponent and add the world
|
||||
to it.
|
||||
|
||||
@override
|
||||
void onLoad() {
|
||||
cameraComponent = CameraComponent(world: world);
|
||||
addAll([cameraComponent, world]);
|
||||
}
|
||||
|
||||
3. Instead of adding the root components directly to your game with `add`,
|
||||
add them to the world.
|
||||
|
||||
world.add(yourComponent);
|
||||
|
||||
4. (Optional) If you want to add a HUD component, instead of using
|
||||
PositionType, add the component as a child of the viewport.
|
||||
|
||||
cameraComponent.viewport.add(yourHudComponent);
|
||||
''')
|
||||
Camera get camera => _cameraWrapper.camera;
|
||||
|
||||
/// This is overwritten to consider the viewport transformation.
|
||||
|
||||
@ -173,12 +173,14 @@ class GestureDetectorBuilder {
|
||||
bool hasMouseDetectors(Game game) {
|
||||
return game is MouseMovementDetector ||
|
||||
game is ScrollDetector ||
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
game is HasHoverables;
|
||||
}
|
||||
|
||||
Widget applyMouseDetectors(Game game, Widget child) {
|
||||
final mouseMoveFn = game is MouseMovementDetector
|
||||
? game.onMouseMove
|
||||
// ignore: deprecated_member_use_from_same_package
|
||||
: (game is HasHoverables ? game.onMouseMove : null);
|
||||
return Listener(
|
||||
child: MouseRegion(
|
||||
|
||||
@ -6,6 +6,9 @@ import 'package:flame/src/gestures/events.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated(
|
||||
'Will be removed in Flame v2, use DragCallbacks without a game mixin instead',
|
||||
)
|
||||
mixin HasDraggables on FlameGame implements MultiDragListener {
|
||||
@mustCallSuper
|
||||
void onDragStart(int pointerId, DragStartInfo info) {
|
||||
|
||||
@ -3,6 +3,12 @@ import 'package:flame/game.dart';
|
||||
import 'package:flame/src/gestures/events.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
@Deprecated(
|
||||
'''
|
||||
Will be removed in Flame v2, use HoverCallbacks without a game mixin instead.
|
||||
https://github.com/flame-engine/flame/issues/2142
|
||||
''',
|
||||
)
|
||||
mixin HasHoverables on FlameGame {
|
||||
@mustCallSuper
|
||||
void onMouseMove(PointerHoverInfo info) {
|
||||
|
||||
@ -18,6 +18,9 @@ import 'package:meta/meta.dart';
|
||||
///
|
||||
/// See [MultiTapGestureRecognizer] for the description of each individual
|
||||
/// event.
|
||||
@Deprecated(
|
||||
'Will be removed in Flame v2, use TapCallbacks without a game mixin instead',
|
||||
)
|
||||
mixin HasTappables on FlameGame implements MultiTapListener {
|
||||
@mustCallSuper
|
||||
void onTapCancel(int pointerId) {
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/input.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
@ -31,22 +32,54 @@ void main() {
|
||||
);
|
||||
|
||||
testWithFlameGame(
|
||||
'position is still correct after zooming and a game resize',
|
||||
'position is still correct after a game resize',
|
||||
(game) async {
|
||||
final world = World();
|
||||
final cameraComponent = CameraComponent(world: world);
|
||||
game.ensureAddAll([world, cameraComponent]);
|
||||
|
||||
final marginComponent = HudMarginComponent(
|
||||
margin: const EdgeInsets.only(right: 10, bottom: 20),
|
||||
size: Vector2.all(20),
|
||||
);
|
||||
await game.ensureAdd(marginComponent);
|
||||
|
||||
await cameraComponent.viewport.ensureAdd(marginComponent);
|
||||
// The position should be (770, 560) since the game size is (800, 600)
|
||||
// and the component has its anchor in the top left corner (which then
|
||||
// is were the margin will be calculated from).
|
||||
// (800, 600) - size(20, 20) - position(10, 20) = (770, 560)
|
||||
expect(marginComponent.position, closeToVector(Vector2(770, 560)));
|
||||
game.update(0);
|
||||
game.camera.zoom = 2.0;
|
||||
game.onGameResize(Vector2.all(500));
|
||||
game.update(0);
|
||||
expect(marginComponent.position, closeToVector(Vector2(770, 560)));
|
||||
},
|
||||
);
|
||||
|
||||
testWithFlameGame(
|
||||
'position is still correct after parent resize and CameraComponent zoom',
|
||||
(game) async {
|
||||
final world = World();
|
||||
final cameraComponent = CameraComponent(world: world);
|
||||
game.ensureAddAll([world, cameraComponent]);
|
||||
|
||||
final parent = PositionComponent(
|
||||
position: Vector2(10, 20),
|
||||
size: Vector2(50, 40),
|
||||
);
|
||||
await world.ensureAdd(parent);
|
||||
|
||||
final marginComponent = HudMarginComponent(
|
||||
margin: const EdgeInsets.only(right: 10, bottom: 20),
|
||||
size: Vector2.all(20),
|
||||
);
|
||||
|
||||
await parent.ensureAdd(marginComponent);
|
||||
expect(marginComponent.position, closeToVector(Vector2(20, 00)));
|
||||
parent.size = Vector2.all(500);
|
||||
expect(marginComponent.position, closeToVector(Vector2(470, 460)));
|
||||
cameraComponent.viewfinder.zoom = 2.0;
|
||||
game.update(0);
|
||||
expect(marginComponent.position, closeToVector(Vector2(470, 460)));
|
||||
},
|
||||
);
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flame/input.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:canvas_test/canvas_test.dart';
|
||||
|
||||
@ -35,7 +35,7 @@ void main() {
|
||||
await game.ensureAddAll(priorityComponents);
|
||||
componentsSorted(components);
|
||||
expect(components.first, firstComponent);
|
||||
game.children.changePriority(firstComponent, 11);
|
||||
firstComponent.priority = 11;
|
||||
game.update(0);
|
||||
expect(components.last, firstComponent);
|
||||
},
|
||||
@ -68,8 +68,8 @@ void main() {
|
||||
componentsSorted(components);
|
||||
final first = components.first;
|
||||
final last = components.last;
|
||||
game.children.changePriority(first, 20);
|
||||
game.children.changePriority(last, -1);
|
||||
first.priority = 20;
|
||||
last.priority = -1;
|
||||
expect(components.first, first);
|
||||
expect(components.last, last);
|
||||
game.update(0);
|
||||
@ -89,7 +89,7 @@ void main() {
|
||||
final children = parentComponent.children;
|
||||
componentsSorted(children);
|
||||
final first = children.first;
|
||||
game.children.changePriority(first, 20);
|
||||
first.priority = 20;
|
||||
expect(children.last, isNot(first));
|
||||
game.update(0);
|
||||
expect(children.last, first);
|
||||
@ -108,8 +108,8 @@ void main() {
|
||||
componentsSorted(children);
|
||||
final first = children.first;
|
||||
final last = children.last;
|
||||
game.children.changePriority(first, 20);
|
||||
game.children.changePriority(last, -1);
|
||||
first.priority = 20;
|
||||
last.priority = -1;
|
||||
expect(children.first, first);
|
||||
expect(children.last, last);
|
||||
game.update(0);
|
||||
@ -131,7 +131,7 @@ void main() {
|
||||
final children = parentComponent.children;
|
||||
componentsSorted(children);
|
||||
final first = children.first;
|
||||
game.children.changePriority(first, 20);
|
||||
first.priority = 20;
|
||||
expect(children.last, isNot(first));
|
||||
game.update(0);
|
||||
expect(children.last, first);
|
||||
@ -165,7 +165,7 @@ void main() {
|
||||
b.assertCalled(0);
|
||||
c.assertCalled(0);
|
||||
|
||||
game.children.changePriority(a, 10);
|
||||
a.priority = 10;
|
||||
game.update(0);
|
||||
|
||||
componentsSorted(game.children);
|
||||
@ -176,10 +176,10 @@ void main() {
|
||||
b.assertCalled(0);
|
||||
c.assertCalled(0);
|
||||
|
||||
// change priority multiple times on c and once on a (and zero on b)
|
||||
game.children.changePriority(c3, 2);
|
||||
game.children.changePriority(c1, 10);
|
||||
game.children.changePriority(a2, 0);
|
||||
// Change priority multiple times on c and once on a (and zero on b).
|
||||
c3.priority = 2;
|
||||
c1.priority = 10;
|
||||
a2.priority = 0;
|
||||
game.update(0);
|
||||
|
||||
a.assertCalled(1);
|
||||
@ -191,9 +191,9 @@ void main() {
|
||||
componentsSorted(b.children);
|
||||
componentsSorted(c.children);
|
||||
|
||||
// change of b now
|
||||
game.children.changePriority(b1, 2);
|
||||
game.children.changePriority(a1, 1); // no-op!
|
||||
// Change of b now.
|
||||
b1.priority = 2;
|
||||
a1.priority = 1; // no-op!
|
||||
game.update(0);
|
||||
|
||||
a.assertCalled(0);
|
||||
|
||||
@ -1,62 +0,0 @@
|
||||
// TODO(spydon): Tracked in https://github.com/flame-engine/flame/issues/2329
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/effects.dart';
|
||||
import 'package:flame_test/flame_test.dart';
|
||||
import 'package:flutter/animation.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
void main() {
|
||||
group('NoiseEffectController', () {
|
||||
test('general properties', () {
|
||||
final ec = NoiseEffectController(duration: 1, frequency: 12);
|
||||
expect(ec.duration, 1.0);
|
||||
expect(ec.frequency, 12.0);
|
||||
expect(ec.taperingCurve, Curves.easeInOutCubic);
|
||||
expect(ec.started, true);
|
||||
expect(ec.completed, false);
|
||||
expect(ec.progress, 0);
|
||||
expect(ec.isRandom, false);
|
||||
});
|
||||
|
||||
test('progression', () {
|
||||
final random = Random(567890);
|
||||
final ec = NoiseEffectController(
|
||||
duration: 1,
|
||||
frequency: 3,
|
||||
random: random,
|
||||
);
|
||||
final observed = <double>[];
|
||||
for (var t = 0.0; t < 1.0; t += 0.1) {
|
||||
observed.add(ec.progress);
|
||||
ec.advance(0.1);
|
||||
}
|
||||
expect(observed, [
|
||||
0.0,
|
||||
-0.4852269950897251,
|
||||
0.7905631204866628,
|
||||
0.25384428741054194,
|
||||
0.06718741964100555,
|
||||
0.08011164287850409,
|
||||
-0.008746065536907871,
|
||||
-0.07181264736289301,
|
||||
-0.014005001721806985,
|
||||
0.00985567863632108,
|
||||
-0.000015661267181374608,
|
||||
]);
|
||||
});
|
||||
|
||||
test('errors', () {
|
||||
expect(
|
||||
() => NoiseEffectController(duration: 0, frequency: 1),
|
||||
failsAssert('duration must be positive'),
|
||||
);
|
||||
expect(
|
||||
() => NoiseEffectController(duration: 1, frequency: 0),
|
||||
failsAssert('frequency parameter must be positive'),
|
||||
);
|
||||
});
|
||||
});
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
// ignore_for_file: deprecated_member_use_from_same_package
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
import 'package:flame/events.dart';
|
||||
import 'package:flame/game.dart';
|
||||
@ -159,8 +161,7 @@ void main() {
|
||||
}
|
||||
|
||||
class _GameWithDualDraggableComponents extends FlameGame
|
||||
with HasDraggablesBridge // ignore: deprecated_member_use_from_same_package
|
||||
{
|
||||
with HasDraggablesBridge {
|
||||
_GameWithDualDraggableComponents({super.children});
|
||||
}
|
||||
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user