Skip to content

Commit

Permalink
feat: Ability to use selfPositioning in SpawnComponent (#2927)
Browse files Browse the repository at this point in the history
This adds the `selfPositioning` argument to the `SpawnComponent` so that
you can set the position of your components within the `factory` without
it getting overwritten be a random position for the `area`.


Closes #2923
  • Loading branch information
spydon committed Dec 17, 2023
1 parent 67f7c12 commit b526aa1
Show file tree
Hide file tree
Showing 4 changed files with 76 additions and 11 deletions.
21 changes: 19 additions & 2 deletions doc/flame/components.md
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
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
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
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,
);
});
});
}

0 comments on commit b526aa1

Please sign in to comment.