Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Ability to use selfPositioning in SpawnComponent #2927

Merged
merged 3 commits into from
Dec 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 19 additions & 2 deletions doc/flame/components.md
Original file line number Diff line number Diff line change
Expand Up @@ -853,9 +853,13 @@ spawn components along the edges of the shape set the `within` argument to false
This would for example spawn new components of the type `MyComponent` every 0.5 seconds randomly
within the defined circle:

The `factory` function takes an `int` as an argument, which is the index of the component that is
being spawned, so if for example 4 components have been spawned already the 5th component will have
the index 4, since the indexing starts at 0.

```dart
SpawnComponent(
factory: () => MyComponent(size: Vector2(10, 20)),
factory: (i) => MyComponent(size: Vector2(10, 20)),
period: 0.5,
area: Circle(Vector2(100, 200), 150),
);
Expand All @@ -868,13 +872,26 @@ between each new spawned component is between 0.5 to 10 seconds.

```dart
SpawnComponent.periodRange(
factory: () => MyComponent(size: Vector2(10, 20)),
factory: (i) => MyComponent(size: Vector2(10, 20)),
minPeriod: 0.5,
maxPeriod: 10,
area: Circle(Vector2(100, 200), 150),
);
```

If you want to set the position yourself within the `factory` function, you can use set
`selfPositioning = true` in the constructors and you will be able to set the positions yourself and
ignore the `area` argument.

```dart
SpawnComponent(
factory: (i) =>
MyComponent(position: Vector2(100, 200), size: Vector2(10, 20)),
selfPositioning: true,
period: 0.5,
);
```


## SvgComponent

Expand Down
33 changes: 26 additions & 7 deletions packages/flame/lib/src/components/spawn_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import 'package:flame/math.dart';
/// components.
/// If you want to use a non static time interval, use the
/// [SpawnComponent.periodRange] constructor.
/// If you want to set the position of the spawned components yourself inside of
/// the [factory], set [selfPositioning] to true.
/// {@endtemplate}
class SpawnComponent extends Component {
/// {@macro spawn_component}
Expand All @@ -22,9 +24,14 @@ class SpawnComponent extends Component {
required double period,
this.area,
this.within = true,
this.selfPositioning = false,
Random? random,
super.key,
}) : _period = period,
}) : assert(
!(selfPositioning && area != null),
"Don't set an area when you are using selfPositioning=true",
),
_period = period,
_random = random ?? randomFallback;

/// Use this constructor if you want your components to spawn within an
Expand All @@ -38,9 +45,14 @@ class SpawnComponent extends Component {
required double maxPeriod,
this.area,
this.within = true,
this.selfPositioning = false,
Random? random,
super.key,
}) : _period = minPeriod +
}) : assert(
!(selfPositioning && area != null),
"Don't set an area when you are using selfPositioning=true",
),
_period = minPeriod +
(random ?? randomFallback).nextDouble() * (maxPeriod - minPeriod),
_random = random ?? randomFallback;

Expand All @@ -56,6 +68,11 @@ class SpawnComponent extends Component {
/// Whether the random point should be within the [area] or along its edges.
bool within;

/// Whether the spawned components positions shouldn't be given a position,
/// so that they can continue to have the position that they had after they
/// came out of the [factory].
bool selfPositioning;

/// The timer that is used to control when components are spawned.
late final Timer timer;

Expand Down Expand Up @@ -87,7 +104,7 @@ class SpawnComponent extends Component {

@override
FutureOr<void> onLoad() async {
if (area == null) {
if (area == null && !selfPositioning) {
final parentPosition =
ancestors().whereType<PositionProvider>().firstOrNull?.position ??
Vector2.zero();
Expand Down Expand Up @@ -120,10 +137,12 @@ class SpawnComponent extends Component {
repeat: true,
onTick: () {
final component = factory(amount);
component.position = area!.randomPoint(
random: _random,
within: within,
);
if (!selfPositioning) {
component.position = area!.randomPoint(
random: _random,
within: within,
);
}
parent?.add(component);
updatePeriod();
amount++;
Expand Down
3 changes: 1 addition & 2 deletions packages/flame/lib/src/particles/scaled_particle.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,7 @@ import 'package:flame/src/components/mixins/single_child_particle.dart';
import 'package:flame/src/particles/curved_particle.dart';
import 'package:flame/src/particles/particle.dart';

/// A particle which rotates its child over the lifespan
/// between two given bounds in radians
/// A particle which scales its child over the lifespan to the set scale.
class ScaledParticle extends CurvedParticle with SingleChildParticle {
@override
Particle child;
Expand Down
30 changes: 30 additions & 0 deletions packages/flame/test/components/spawn_component_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -110,5 +110,35 @@ void main() {
isTrue,
);
});

testWithFlameGame('Can self position', (game) async {
final random = Random(0);
final spawn = SpawnComponent(
factory: (_) => PositionComponent(position: Vector2.all(1000)),
period: 1,
selfPositioning: true,
random: random,
);
final world = game.world;
await world.ensureAdd(spawn);
game.update(0.5);
expect(world.children.length, 1);
game.update(0.5);
game.update(0.0);
expect(world.children.length, 2);
game.update(1.0);
game.update(0.0);
expect(world.children.length, 3);

for (var i = 0; i < 1000; i++) {
game.update(random.nextDouble());
}
expect(
world.children
.query<PositionComponent>()
.every((c) => c.position == Vector2.all(1000)),
isTrue,
);
});
});
}