Skip to content

Commit

Permalink
Merge branch 'main' into LifecycleOnRemoveMemoryLeakFix
Browse files Browse the repository at this point in the history
  • Loading branch information
spydon authored Jul 29, 2023
2 parents 50f128e + 970babe commit 5df49f9
Show file tree
Hide file tree
Showing 38 changed files with 557 additions and 219 deletions.
1 change: 1 addition & 0 deletions .github/.cspell/flame_dictionary.txt
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ spineboy
spineboys
spydon
stpasha
Supabase
tavian
trex
Videon
Expand Down
5 changes: 3 additions & 2 deletions doc/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ dependencies:
The latest version can be found on [pub.dev](https://pub.dev/packages/flame/install).
then run `pub get` and you are ready to start using it!
then run `flutter pub get` and you are ready to start using it!


## Getting started
Expand Down Expand Up @@ -72,10 +72,11 @@ Flame doesn't bundle any network feature, which may be needed to write online mu

If you are building a multiplayer game, here are some recommendations of packages/services:

- [Nakama](https://github.com/Allan-Nava/nakama-flutter): Nakama is an open-source server designed
- [Nakama](https://github.com/obrunsmann/flutter_nakama/): Nakama is an open-source server designed
to power modern games and apps.
- [Firebase](https://firebase.google.com/): Provides dozens of services that can be used to write
simpler multiplayer experiences.
- [Supabase](https://supabase.com/): A cheaper alternative to Firebase, based on Postgres.


### External assets
Expand Down
2 changes: 1 addition & 1 deletion doc/_sphinx/requirements.txt
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
linkify-it-py==2.0.0
myst-parser==1.0.0
Pygments==2.14.0
Pygments==2.15.0
Sphinx==6.1.3
sphinxcontrib-mermaid==0.8.1
sphinxcontrib-jquery==4.0
Expand Down
103 changes: 64 additions & 39 deletions doc/flame/collision_detection.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ bounding boxes of your components. In Flame the hitboxes are areas of the compon
to collisions (and make [gesture input](inputs/gesture_input.md#gesturehitboxes)) more accurate.

The collision detection system supports three different types of shapes that you can build hitboxes
from, these shapes are Polygon, Rectangle and Circle. Multiple hitbox can be added to a component to
form the area which can be used to either detect collisions or whether it contains a point or not,
from, these shapes are Polygon, Rectangle and Circle. Multiple hitbox can be added
to a component to form the area which can be used to either detect collisions
or whether it contains a point or not,
the latter is very useful for accurate gesture detection. The collision detection does not handle
what should happen when two hitboxes collide, so it is up to the user to implement what will happen
when for example two `PositionComponent`s have intersecting hitboxes.
Expand Down Expand Up @@ -42,7 +43,8 @@ class MyGame extends FlameGame with HasCollisionDetection {
Now when you add `ShapeHitbox`s to components that are then added to the game, they will
automatically be checked for collisions.

You can also add `HasCollisionDetection` directly to another `Component` instead of the `FlameGame`,
You can also add `HasCollisionDetection` directly to another `Component` instead
of the `FlameGame`,
for example to the `World` that is used for the `CameraComponent`.
If that is done, hitboxes that are added in that component's tree will only be compared to other
hitboxes in that subtree, which makes it possible to have several worlds with collision detection
Expand Down Expand Up @@ -96,8 +98,8 @@ class MyCollidable extends PositionComponent with CollisionCallbacks {
}
```

In this example we use Dart's `is` keyword to check what kind of component we collided with. The set
of points is where the edges of the hitboxes intersect.
In this example we use Dart's `is` keyword to check what kind of component we collided with.
The set of points is where the edges of the hitboxes intersect.

Note that the `onCollision` method will be called on both `PositionComponent`s if they have both
implemented the `onCollision` method, and also on both hitboxes. The same goes for the
Expand Down Expand Up @@ -133,7 +135,8 @@ class MyComponent extends PositionComponent {
```

If you don't add any arguments to the hitbox, like above, the hitbox will try to fill its parent as
much as possible. Except for having the hitboxes trying to fill their parents, there are two ways to
much as possible. Except for having the hitboxes trying to fill their parents,
there are two ways to
initiate hitboxes and it is with the normal constructor where you define the hitbox by itself, with
a size and a position etc. The other way is to use the `relative` constructor which defines the
hitbox in relation to the size of its intended parent.
Expand Down Expand Up @@ -201,15 +204,16 @@ The `CollisionType` enum contains the following values:
- `inactive` will not collide with any other `Collidable`s

So if you have hitboxes that you don't need to check collisions against each other you can mark
them as passive by setting `collisionType: CollisionType.passive` in the constructor, this could for
example be ground components or maybe your enemies don't need to check collisions between each
other, then they could be marked as `passive` too.
them as passive by setting `collisionType: CollisionType.passive` in the constructor,
this could for example be ground components or maybe your enemies don't need
to check collisions between each other, then they could be marked as `passive` too.

Imagine a game where there are a lot of bullets, that can't collide with each other, flying towards
the player, then the player would be set to `CollisionType.active` and the bullets would be set to
`CollisionType.passive`.

Then we have the `inactive` type which simply doesn't get checked at all in the collision detection.
Then we have the `inactive` type which simply doesn't get checked at all
in the collision detection.
This could be used for example if you have components outside of the screen that you don't care
about at the moment but that might later come back in to view so they are not completely removed
from the game.
Expand All @@ -222,8 +226,9 @@ them so don't doubt to use them even if your use case isn't listed here.

It should be noted that if you want to use collision detection or `containsPoint` on the `Polygon`,
the polygon needs to be convex. So always use convex polygons or you will most likely run into
problems if you don't really know what you are doing. It should also be noted that you should always
define the vertices in your polygon in a counter-clockwise order.
problems if you don't really know what you are doing.
It should also be noted that you should always define the vertices in your polygon
in a counter-clockwise order.

The other hitbox shapes don't have any mandatory constructor, that is because they can have a
default calculated from the size of the collidable that they are attached to, but since a
Expand Down Expand Up @@ -259,32 +264,36 @@ want the `ScreenHitbox` itself to be notified when something collides with it. S

## CompositeHitbox

In the `CompositeHitbox` you can add multiple hitboxes so that they emulate being one joined hitbox.
In the `CompositeHitbox` you can add multiple hitboxes so that
they emulate being one joined hitbox.

If you want to form a hat for example you might want to use two [](#rectanglehitbox)s to follow that
If you want to form a hat for example you might want
to use two [](#rectanglehitbox)s to follow that
hat's edges properly, then you can add those hitboxes to an instance of this class and react to
collisions to the whole hat, instead of for just each hitbox separately.


## Broad phase

If your game field is small and do not have a lot of collidable components - you don't have to
If your game field isn't huge and does not have a lot of collidable components - you don't have to
worry about the broad phase system that is used, so if the standard implementation is performant
enough for you, you probably don't have to read this section.

A broad phase is the first step of collision detection where potential collisions are calculated.
To calculate these potential collisions are a lot cheaper to calculate than to check the exact
intersections directly and it removes the need to check all hitboxes against each other and
therefore avoiding O(n²). The broad phase produces a set of potential collisions (a set of
`CollisionProspect`s), this set is then used to check the exact intersections between hitboxes, this
is sometimes called narrow phase.
Calculating these potential collisions is faster than to checking the intersections exactly,
and it removes the need to check all hitboxes against each other and
therefore avoiding O(n²).

By default Flame's collision detection is using a sweep and prune broadphase step, if your game
The broad phase produces a set of potential collisions (a set of
`CollisionProspect`s). This set is then used to check the exact intersections between
hitboxes (sometimes called "narrow phase").

By default, Flame's collision detection is using a sweep and prune broadphase step. If your game
requires another type of broadphase you can write your own broadphase by extending `Broadphase` and
manually setting the collision detection system that should be used.

For example if you have implemented a broadphase built on a magic algorithm instead of the standard
sweep and prune, then you would do the following:
For example, if you have implemented a broadphase built on a magic algorithm
instead of the standard sweep and prune, then you would do the following:

```dart
class MyGame extends FlameGame with HasCollisionDetection {
Expand All @@ -301,6 +310,7 @@ class MyGame extends FlameGame with HasCollisionDetection {
If your game field is large and the game contains a lot of collidable
components (more than a hundred), standard sweep and prune can
become inefficient. If it does, you can try to use the quad tree broad phase.

To do this, add the `HasQuadTreeCollisionDetection` mixin to your game instead of
`HasCollisionDetection` and call the `initializeCollisionDetection` function on game load:

Expand All @@ -323,11 +333,12 @@ more efficient:
- `minimumDistance`: minimum distance between objects to consider them as possibly colliding.
If `null` - the check is disabled, it is default behavior
- `maxObjects`: maximum objects count in one quadrant. Default to 25.
- `maxDepth`: - maximum nesting levels inside quadrant. Default to 10
- `maxDepth`: maximum nesting levels inside quadrant. Default to 10

If you use the quad tree system, you can make it even more efficient by implementing the
`onComponentTypeCheck` function of the `CollisionCallbacks` mixin in your components. It is useful if
you need to prevent collisions of items of different types. The result of the calculation is cached so
`onComponentTypeCheck` function of the `CollisionCallbacks` mixin in your components.
It is useful if you need to prevent collisions of items of different types.
The result of the calculation is cached so
you should not check any dynamic parameters here, the function is intended to be used as a pure
type checker:

Expand Down Expand Up @@ -383,16 +394,26 @@ class QuadTreeExample extends FlameGame
```

```{note}
Always experiment with different collision detection approaches
and check how they perform on your game.
It is not unheard of that `QuadTreeBroadphase` is significantly
_slower_ than the default.
Don't assume that the more sophisticated approach is always faster.
```


## Ray casting and Ray tracing

Ray casting and ray tracing are methods for sending out rays from a point in your game and being
able to see what these rays collide with and how they reflect after hitting something.

For all of the following methods, if there are any hitboxes that you wish to ignore, you can add the
`ignoreHitboxes` argument which is a list of the hitboxes that you wish to disregard for the call.
This can be quite useful for example if you are casting rays from within a hitbox, which could be on
your player or NPC; or if you don't want a ray to bounce off a `ScreenHitbox`.
For all of the following methods, if there are any hitboxes that you wish to ignore,
you can add the `ignoreHitboxes` argument which is a list of the hitboxes
that you wish to disregard for the call.
This can be quite useful for example if you are casting rays from within a hitbox,
which could be on your player or NPC;
or if you don't want a ray to bounce off a `ScreenHitbox`.


### Ray casting
Expand All @@ -402,12 +423,14 @@ anything, in Flame's case, hitboxes.

We provide two methods for doing so, `raycast` and `raycastAll`. The first one just casts out
a single ray and gets back a result with information about what and where the ray hit, and some
extra information like the distance, the normal and the reflection ray. The second one, `raycastAll`,
extra information like the distance, the normal and the reflection ray.
The second one, `raycastAll`,
works similarly but sends out multiple rays uniformly around the origin, or within an angle
centered at the origin.

By default, `raycast` and `raycastAll` scan for the nearest hit irrespective of how far it lies from
the ray origin. But in some use cases, it might be interesting to find hits only within a certain
By default, `raycast` and `raycastAll` scan for the nearest hit irrespective of
how far it lies from the ray origin.
But in some use cases, it might be interesting to find hits only within a certain
range. For such cases, an optional `maxDistance` can be provided.

To use the ray casting functionality you have to have the `HasCollisionDetection` mixin on your
Expand Down Expand Up @@ -527,8 +550,9 @@ class MyGame extends FlameGame with HasCollisionDetection {
}
```

In the example above we send out a ray from (0, 100) diagonally down to the right and we say that we
want it the bounce on at most 100 hitboxes, it doesn't necessarily have to get 100 results since at
In the example above we send out a ray from (0, 100) diagonally down to the right
and we say that we want it the bounce on at most 100 hitboxes,
it doesn't necessarily have to get 100 results since at
some point one of the reflection rays might not hit a hitbox and then the method is done.

The method is lazy, which means that it will only do the calculations that you ask for, so you have
Expand All @@ -537,8 +561,8 @@ calculate all the results.

In the for-loop it can be seen how this can be used, in that loop we check whether the current
reflection rays intersection point (where the previous ray hit the hitbox) is further away than 300
pixels from the origin of the starting ray, and if it is we don't care about the rest of the results
(and then they don't have to be calculated either).
pixels from the origin of the starting ray, and if it is we don't care about the rest
of the results (and then they don't have to be calculated either).

If you are concerned about performance you can re-use the `RaycastResult` objects that are created
by the function by sending them in as a list with the `out` argument.
Expand Down Expand Up @@ -569,8 +593,9 @@ need some of the following things (since it is simpler to not involve Forge2D):

## Migration from the collision detection system in v1.0

The collision detection system introduced in v1.1 is easier to use, and much more efficient than the
one that was in v1.0, but while making these improvements some breaking changes had to be made.
The collision detection system introduced in v1.1 is easier to use,
and much more efficient than the one that was in v1.0,
but while making these improvements some breaking changes had to be made.

There is no longer a `Collidable` mixin, instead your game automatically knows when a hitbox has
been added to one of your components when the `HasCollisionDetection` mixin is added to your game.
Expand Down
1 change: 1 addition & 0 deletions doc/flame/other/util.md
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,7 @@ Methods:
- `intersectsSegment`; Whether the segment formed by two `Vector2`s intersects this `Rect`.
- `intersectsLineSegment`: Whether the `LineSegment` intersects the `Rect`.
- `toVertices`: Turns the four corners of the `Rect` into a list of `Vector2`.
- `toFlameRectangle`: Converts this `Rect` into a Flame `Rectangle`.
- `toMathRectangle`: Converts this `Rect` into a `math.Rectangle`.
- `toGeometryRectangle`: Converts this `Rect` into a `Rectangle` from flame-geom.
- `transform`: Transforms the `Rect` using a `Matrix4`.
Expand Down
27 changes: 14 additions & 13 deletions doc/tutorials/klondike/step3.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,13 +55,14 @@ symbol on the canvas. The sprite object is initialized using the
```

Then comes the static list of all `Suit` objects in the game. Note that we
define it as `late`, meaning that it will be only initialized the first time
it is needed. This is important: as we seen above, the constructor tries to
retrieve an image from the global cache, so it can only be invoked after the
image is loaded into the cache.
define it as static variable so it is evaluated lazily (as if it was marked
with the `late` keyword) meaning that it will be only initialized the first
time it is needed. This is important: as we can see above, the constructor
tries to retrieve an image from the global cache, so it can only be invoked
after the image is loaded into the cache.

```dart
static late final List<Suit> _singletons = [
static final List<Suit> _singletons = [
Suit._(0, '♥', 1176, 17, 172, 183),
Suit._(1, '♦', 973, 14, 177, 182),
Suit._(2, '♣', 974, 226, 184, 172),
Expand Down Expand Up @@ -120,7 +121,7 @@ class Rank {
final Sprite redSprite;
final Sprite blackSprite;
static late final List<Rank> _singletons = [
static final List<Rank> _singletons = [
Rank._(1, 'A', 335, 164, 789, 161, 120, 129),
Rank._(2, '2', 20, 19, 15, 322, 83, 125),
Rank._(3, '3', 122, 19, 117, 322, 80, 127),
Expand Down Expand Up @@ -272,7 +273,7 @@ Various properties used in the `_renderBack()` method are defined as follows:
const Radius.circular(KlondikeGame.cardRadius),
);
static final RRect backRRectInner = cardRRect.deflate(40);
static late final Sprite flameSprite = klondikeSprite(1367, 6, 357, 501);
static final Sprite flameSprite = klondikeSprite(1367, 6, 357, 501);
```

I declared these properties as static because they will all be the same across
Expand Down Expand Up @@ -307,9 +308,9 @@ depending on whether the card is of a "red" suit or "black":
Next, we also need the images for the court cards:

```dart
static late final Sprite redJack = klondikeSprite(81, 565, 562, 488);
static late final Sprite redQueen = klondikeSprite(717, 541, 486, 515);
static late final Sprite redKing = klondikeSprite(1305, 532, 407, 549);
static final Sprite redJack = klondikeSprite(81, 565, 562, 488);
static final Sprite redQueen = klondikeSprite(717, 541, 486, 515);
static final Sprite redKing = klondikeSprite(1305, 532, 407, 549);
```

Note that I'm calling these sprites `redJack`, `redQueen`, and `redKing`. This
Expand All @@ -325,11 +326,11 @@ blending mode:
Color(0x880d8bff),
BlendMode.srcATop,
);
static late final Sprite blackJack = klondikeSprite(81, 565, 562, 488)
static final Sprite blackJack = klondikeSprite(81, 565, 562, 488)
..paint = blueFilter;
static late final Sprite blackQueen = klondikeSprite(717, 541, 486, 515)
static final Sprite blackQueen = klondikeSprite(717, 541, 486, 515)
..paint = blueFilter;
static late final Sprite blackKing = klondikeSprite(1305, 532, 407, 549)
static final Sprite blackKing = klondikeSprite(1305, 532, 407, 549)
..paint = blueFilter;
```

Expand Down
Loading

0 comments on commit 5df49f9

Please sign in to comment.