diff --git a/packages/flame/lib/src/components/spawn_component.dart b/packages/flame/lib/src/components/spawn_component.dart index 5aaafcc08f..28171155ab 100644 --- a/packages/flame/lib/src/components/spawn_component.dart +++ b/packages/flame/lib/src/components/spawn_component.dart @@ -16,12 +16,16 @@ import 'package:flame/math.dart'; /// [SpawnComponent.periodRange] constructor. /// If you want to set the position of the spawned components yourself inside of /// the [factory], set [selfPositioning] to true. +/// You can either provide a factory that returns one component or a multiFactory +/// which returns a list of components. In this case the amount parameter will be +/// increased by the number of returned components. /// {@endtemplate} class SpawnComponent extends Component { /// {@macro spawn_component} SpawnComponent({ - required this.factory, required double period, + PositionComponent Function(int amount)? factory, + List Function(int amount)? multiFactory, this.area, this.within = true, this.selfPositioning = false, @@ -33,7 +37,16 @@ class SpawnComponent extends Component { !(selfPositioning && area != null), "Don't set an area when you are using selfPositioning=true", ), + assert( + factory != null || multiFactory != null, + 'You need to provide a factory or multiFactory to create components', + ), + assert( + !(factory != null && multiFactory != null), + 'You need to provide either a factory or a multiFactory not both', + ), _period = period, + multiFactory = multiFactory ?? _wrapFactory(factory!), _random = random ?? randomFallback; /// Use this constructor if you want your components to spawn within an @@ -42,9 +55,10 @@ class SpawnComponent extends Component { /// spawns and [maxPeriod] will be the maximum amount of time before it /// spawns. SpawnComponent.periodRange({ - required this.factory, required double minPeriod, required double maxPeriod, + PositionComponent Function(int amount)? factory, + List Function(int amount)? multiFactory, this.area, this.within = true, this.selfPositioning = false, @@ -58,13 +72,37 @@ class SpawnComponent extends Component { ), _period = minPeriod + (random ?? randomFallback).nextDouble() * (maxPeriod - minPeriod), + multiFactory = multiFactory ?? _wrapFactory(factory!), _random = random ?? randomFallback; + /// The function used to create a new component to spawn. + /// + /// [amount] is the amount of components that the [SpawnComponent] has spawned + /// so far. + /// + /// Be aware: internally the component uses a factory that creates a list of + /// components. + /// If you have set such a factory it was wrapped to create a list. The factory + /// getter wrapps it again to return the first element of the list and fails + /// when the list is empty! + /// + PositionComponent Function(int amount) get factory => + (int amount) => multiFactory.call(amount).elementAt(0); + + set factory(PositionComponent Function(int amount) newFactory) { + multiFactory = _wrapFactory(newFactory); + } + + static List Function(int amount) _wrapFactory( + PositionComponent Function(int amount) newFactory) { + return (int amount) => [newFactory.call(amount)]; + } + /// The function used to create new components to spawn. /// /// [amount] is the amount of components that the [SpawnComponent] has spawned /// so far. - PositionComponent Function(int amount) factory; + List Function(int amount) multiFactory; /// The area where the components should be spawned. Shape? area; @@ -146,16 +184,17 @@ class SpawnComponent extends Component { period: _period, repeat: true, onTick: () { - final component = factory(amount); + final List components = multiFactory(amount); if (!selfPositioning) { - component.position = area!.randomPoint( - random: _random, - within: within, - ); + components + .forEach((component) => component.position = area!.randomPoint( + random: _random, + within: within, + )); } - parent?.add(component); + parent?.addAll(components); updatePeriod(); - amount++; + amount += components.length; }, autoStart: autoStart, tickWhenLoaded: spawnWhenLoaded, diff --git a/packages/flame/test/components/spawn_component_test.dart b/packages/flame/test/components/spawn_component_test.dart index 1874a77403..2c52646063 100644 --- a/packages/flame/test/components/spawn_component_test.dart +++ b/packages/flame/test/components/spawn_component_test.dart @@ -41,6 +41,42 @@ void main() { ); }); + testWithFlameGame('Spawns multiple components within rectangle', + (game) async { + final random = Random(0); + final shape = Rectangle.fromCenter( + center: Vector2(100, 200), + size: Vector2.all(200), + ); + final spawn = SpawnComponent( + multiFactory: (_) => + [PositionComponent(), PositionComponent(), PositionComponent()], + period: 1, + area: shape, + random: random, + ); + final world = game.world; + await world.ensureAdd(spawn); + game.update(0.5); + expect(world.children.length, 1); //1 being the spawnComponent + game.update(0.5); + game.update(0.0); + expect(world.children.length, 4); //1+3 spawned components + game.update(1.0); + game.update(0.0); + expect(world.children.length, 7); //1+2*3 spawned components + + for (var i = 0; i < 1000; i++) { + game.update(random.nextDouble()); + } + expect( + world.children + .query() + .every((c) => shape.containsPoint(c.position)), + isTrue, + ); + }); + testWithFlameGame('Spawns components within circle', (game) async { final random = Random(0); final shape = Circle(Vector2(100, 200), 100); @@ -140,6 +176,39 @@ void main() { isTrue, ); }); + testWithFlameGame('Can self position multiple components ', (game) async { + final random = Random(0); + final spawn = SpawnComponent( + multiFactory: (_) => [ + PositionComponent(position: Vector2.all(1000)), + PositionComponent(position: Vector2.all(1000)), + 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); //1 spawn component + game.update(0.5); + game.update(0.0); + expect(world.children.length, 4); //1+3 spawned components + game.update(1.0); + game.update(0.0); + expect(world.children.length, 7); //1+2*3 spawned components + + for (var i = 0; i < 1000; i++) { + game.update(random.nextDouble()); + } + expect( + world.children + .query() + .every((c) => c.position == Vector2.all(1000)), + isTrue, + ); + }); testWithFlameGame('Does not spawns when auto start is false', (game) async { final random = Random(0);