diff --git a/doc/flame/components.md b/doc/flame/components.md index 396a147b2d7..e62556ebfe8 100644 --- a/doc/flame/components.md +++ b/doc/flame/components.md @@ -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), ); @@ -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 diff --git a/packages/flame/lib/src/components/spawn_component.dart b/packages/flame/lib/src/components/spawn_component.dart index c02507c73fd..947e5d2fb35 100644 --- a/packages/flame/lib/src/components/spawn_component.dart +++ b/packages/flame/lib/src/components/spawn_component.dart @@ -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} @@ -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 @@ -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; @@ -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; @@ -87,7 +104,7 @@ class SpawnComponent extends Component { @override FutureOr onLoad() async { - if (area == null) { + if (area == null && !selfPositioning) { final parentPosition = ancestors().whereType().firstOrNull?.position ?? Vector2.zero(); @@ -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++; diff --git a/packages/flame/lib/src/particles/scaled_particle.dart b/packages/flame/lib/src/particles/scaled_particle.dart index dec690e2ffd..ea9ae3bba51 100644 --- a/packages/flame/lib/src/particles/scaled_particle.dart +++ b/packages/flame/lib/src/particles/scaled_particle.dart @@ -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; diff --git a/packages/flame/test/components/spawn_component_test.dart b/packages/flame/test/components/spawn_component_test.dart index 91bf599d29f..c0ff4154cd3 100644 --- a/packages/flame/test/components/spawn_component_test.dart +++ b/packages/flame/test/components/spawn_component_test.dart @@ -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() + .every((c) => c.position == Vector2.all(1000)), + isTrue, + ); + }); }); }