Skip to content

Commit

Permalink
feat: SpawnComponent (#2709)
Browse files Browse the repository at this point in the history
This PR introduces the `SpawnComponent`, which randomly spawns
components within a set area.
  • Loading branch information
spydon committed Sep 10, 2023
1 parent b3d78f5 commit 83f5ea4
Show file tree
Hide file tree
Showing 39 changed files with 593 additions and 40 deletions.
44 changes: 44 additions & 0 deletions doc/flame/components.md
Expand Up @@ -340,6 +340,12 @@ void onDragUpdate(DragUpdateInfo info) {

### PositionType

```{note}
If you are using the `CameraComponent` you should not use `PositionType`, but
instead adding your components directly to the viewport for example if you
want to use them as a HUD.
```

If you want to create a HUD (Head-up display) or another component that isn't positioned in relation
to the game coordinates, you can change the `PositionType` of the component.
The default `PositionType` is `positionType = PositionType.game` and that can be changed to
Expand Down Expand Up @@ -810,6 +816,44 @@ class ButtonComponent extends SpriteGroupComponent<ButtonState>
```


## SpawnComponent

This component is a non-visual component that spawns other components inside of the parent of the
`SpawnComponent`. It's great if you for example want to spawn enemies or power-ups randomly within
an area.

The `SpawnComponent` takes a factory function that it uses to create new components and an area
where the components should be spawned within (or along the edges of).

For the area, you can use the `Circle`, `Rectangle` or `Polygon` class, and if you want to only
spawn components along the edges of the shape set the `within` argument to false (defaults to true).

This would for example spawn new components of the type `MyComponent` every 0.5 seconds randomly
within the defined circle:

```dart
SpawnComponent(
factory: () => MyComponent(size: Vector2(10, 20)),
period: 0.5,
area: Circle(Vector2(100, 200), 150),
);
```

If you don't want the spawning rate to be static, you can use the `SpawnComponent.periodRange`
constructor with the `minPeriod` and `maxPeriod` arguments instead.
In the following example the component would be spawned randomly within the circle and the time
between each new spawned component is between 0.5 to 10 seconds.

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


## SvgComponent

**Note**: To use SVG with Flame, use the [`flame_svg`](https://github.com/flame-engine/flame_svg)
Expand Down
3 changes: 1 addition & 2 deletions doc/flame/examples/lib/drag_events.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flutter/rendering.dart';

class DragEventsGame extends FlameGame {
Expand Down Expand Up @@ -242,5 +243,3 @@ class Star extends PositionComponent with DragCallbacks {
position += event.delta;
}
}

const tau = 2 * pi;
3 changes: 2 additions & 1 deletion doc/flame/examples/lib/router.dart
Expand Up @@ -2,6 +2,7 @@ import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/events.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flame/rendering.dart';
import 'package:flutter/rendering.dart';

Expand Down Expand Up @@ -355,7 +356,7 @@ class Orbit extends PositionComponent {

@override
void update(double dt) {
_angle += dt / revolutionPeriod * Transform2D.tau;
_angle += dt / revolutionPeriod * tau;
planet.position = Vector2(radius, 0)..rotate(_angle);
}
}
Expand Down
3 changes: 1 addition & 2 deletions doc/flame/examples/lib/value_route.dart
Expand Up @@ -6,6 +6,7 @@ import 'package:flame/components.dart';
import 'package:flame/events.dart';
import 'package:flame/experimental.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';

class ValueRouteExample extends FlameGame {
late final RouterComponent router;
Expand Down Expand Up @@ -130,5 +131,3 @@ class Star extends PositionComponent with TapCallbacks {
}
}
}

const tau = pi * 2;
Expand Up @@ -4,6 +4,7 @@ import 'package:flame/camera.dart';
import 'package:flame/components.dart';
import 'package:flame/extensions.dart' show OffsetExtension;
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flame/input.dart';
import 'package:flutter/painting.dart';

Expand Down Expand Up @@ -258,7 +259,6 @@ class Ant extends PositionComponent {
late final Color color;
final Random random;
static const black = Color(0xFF000000);
static const tau = Transform2D.tau;
late final Paint bodyPaint;
late final Paint eyesPaint;
late final Paint legsPaint;
Expand Down
9 changes: 9 additions & 0 deletions examples/lib/stories/components/components.dart
Expand Up @@ -10,6 +10,7 @@ import 'package:examples/stories/components/keys_example.dart';
import 'package:examples/stories/components/look_at_example.dart';
import 'package:examples/stories/components/look_at_smooth_example.dart';
import 'package:examples/stories/components/priority_example.dart';
import 'package:examples/stories/components/spawn_component_example.dart';
import 'package:examples/stories/components/time_scale_example.dart';
import 'package:flame/game.dart';

Expand Down Expand Up @@ -64,6 +65,14 @@ void addComponentsStories(Dashbook dashbook) {
baseLink('components/components_notifier_provider_example.dart'),
info: ComponentsNotifierProviderExampleWidget.description,
)
..add(
'Spawn Component',
(_) => const GameWidget.controlled(
gameFactory: SpawnComponentExample.new,
),
codeLink: baseLink('components/spawn_component_example.dart'),
info: SpawnComponentExample.description,
)
..add(
'Time Scale',
(_) => const GameWidget.controlled(
Expand Down
60 changes: 60 additions & 0 deletions examples/lib/stories/components/spawn_component_example.dart
@@ -0,0 +1,60 @@
import 'package:examples/commons/ember.dart';
import 'package:flame/components.dart';
import 'package:flame/experimental.dart';
import 'package:flame/extensions.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame/math.dart';

class SpawnComponentExample extends FlameGame with TapDetector {
static String description =
'Tap on the screen to start spawning Embers within different shapes.';

@override
void onTapDown(TapDownInfo info) {
final shapeType = Shapes.values.random();
final Shape shape;
final position = info.eventPosition.game;
switch (shapeType) {
case Shapes.rectangle:
shape = Rectangle.fromCenter(
center: info.eventPosition.game,
size: Vector2.all(200),
);
case Shapes.circle:
shape = Circle(info.eventPosition.game, 150);
case Shapes.polygon:
shape = Polygon(
[
Vector2(-1.0, 0.0),
Vector2(-0.8, 0.6),
Vector2(0.0, 1.0),
Vector2(0.6, 0.9),
Vector2(1.0, 0.0),
Vector2(0.3, -0.2),
Vector2(0.0, -1.0),
Vector2(-0.8, -0.5),
].map((vertex) {
return vertex
..scale(200)
..add(position);
}).toList(),
);
}

add(
SpawnComponent(
factory: (_) => Ember(),
period: 0.5,
area: shape,
within: randomFallback.nextBool(),
),
);
}
}

enum Shapes {
rectangle,
circle,
polygon,
}
2 changes: 1 addition & 1 deletion examples/lib/stories/effects/move_effect_example.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flame_noise/flame_noise.dart';
import 'package:flutter/material.dart';

Expand All @@ -26,7 +27,6 @@ class MoveEffectExample extends FlameGame {

@override
void onLoad() {
const tau = Transform2D.tau;
cameraComponent = CameraComponent.withFixedResolution(
world: world,
width: 400,
Expand Down
11 changes: 6 additions & 5 deletions examples/lib/stories/effects/rotate_effect_example.dart
Expand Up @@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flutter/animation.dart';

class RotateEffectExample extends FlameGame {
Expand Down Expand Up @@ -45,7 +46,7 @@ class RotateEffectExample extends FlameGame {
compass.arrow
..add(
RotateEffect.to(
Transform2D.tau,
tau,
EffectController(
duration: 20,
infinite: true,
Expand All @@ -54,7 +55,7 @@ class RotateEffectExample extends FlameGame {
)
..add(
RotateEffect.by(
Transform2D.tau * 0.015,
tau * 0.015,
EffectController(
duration: 0.1,
reverseDuration: 0.1,
Expand All @@ -64,7 +65,7 @@ class RotateEffectExample extends FlameGame {
)
..add(
RotateEffect.by(
Transform2D.tau * 0.021,
tau * 0.021,
EffectController(
duration: 0.13,
reverseDuration: 0.13,
Expand Down Expand Up @@ -98,7 +99,7 @@ class Compass extends PositionComponent {
Future<void> onLoad() async {
_marksPath = Path();
for (var i = 0; i < 12; i++) {
final angle = Transform2D.tau * (i / 12);
final angle = tau * (i / 12);
// Note: rim takes up 0.1radius, so the lengths must be > than that
final markLength = (i % 3 == 0) ? _radius * 0.2 : _radius * 0.15;
_marksPath.moveTo(
Expand Down Expand Up @@ -189,7 +190,7 @@ class CompassRim extends PositionComponent {
final innerRadius = _radius - _width;
final midRadius = _radius - _width / 3;
for (var i = 0; i < numberOfNotches; i++) {
final angle = Transform2D.tau * (i / numberOfNotches);
final angle = tau * (i / numberOfNotches);
_marksPath.moveTo(
_radius + innerRadius * sin(angle),
_radius + innerRadius * cos(angle),
Expand Down
2 changes: 1 addition & 1 deletion examples/lib/stories/effects/scale_effect_example.dart
Expand Up @@ -4,6 +4,7 @@ import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';
import 'package:flame/input.dart';
import 'package:flame/palette.dart';
import 'package:flutter/animation.dart';
Expand Down Expand Up @@ -76,7 +77,6 @@ class Star extends PositionComponent {
Star() {
const smallR = 15.0;
const bigR = 30.0;
const tau = 2 * pi;
shape = Path()..moveTo(bigR, 0);
for (var i = 1; i < 10; i++) {
final r = i.isEven ? bigR : smallR;
Expand Down
2 changes: 1 addition & 1 deletion examples/lib/stories/effects/sequence_effect_example.dart
Expand Up @@ -3,6 +3,7 @@ import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
import 'package:flame/geometry.dart';

class SequenceEffectExample extends FlameGame {
static const String description = '''
Expand All @@ -14,7 +15,6 @@ class SequenceEffectExample extends FlameGame {

@override
Future<void> onLoad() async {
const tau = Transform2D.tau;
EffectController duration(double x) => EffectController(duration: x);
add(
Player()
Expand Down
1 change: 1 addition & 0 deletions packages/flame/lib/components.dart
Expand Up @@ -35,6 +35,7 @@ export 'src/components/nine_tile_box_component.dart';
export 'src/components/parallax_component.dart';
export 'src/components/particle_system_component.dart';
export 'src/components/position_component.dart';
export 'src/components/spawn_component.dart';
export 'src/components/sprite_animation_component.dart';
export 'src/components/sprite_animation_group_component.dart';
export 'src/components/sprite_batch_component.dart';
Expand Down
3 changes: 3 additions & 0 deletions packages/flame/lib/math.dart
@@ -0,0 +1,3 @@
export 'src/math/random_fallback.dart';
export 'src/math/solve_cubic.dart';
export 'src/math/solve_quadratic.dart';

0 comments on commit 83f5ea4

Please sign in to comment.