feat: Added NoiseEffectController (#1356)

This commit is contained in:
Pasha Stetsenko
2022-02-06 04:43:12 -08:00
committed by GitHub
parent 9f096053fd
commit fad9d1d54f
5 changed files with 142 additions and 0 deletions

View File

@ -62,6 +62,7 @@ There are multiple effect controllers provided by the Flame framework as well:
- [`SequenceEffectController`](#sequenceeffectcontroller) - [`SequenceEffectController`](#sequenceeffectcontroller)
- [`SpeedEffectController`](#speedeffectcontroller) - [`SpeedEffectController`](#speedeffectcontroller)
- [`DelayedEffectController`](#delayedeffectcontroller) - [`DelayedEffectController`](#delayedeffectcontroller)
- [`NoiseEffectController`](#noiseffectcontroller)
- [`RandomEffectController`](#randomeffectcontroller) - [`RandomEffectController`](#randomeffectcontroller)
- [`SineEffectController`](#sineeffectcontroller) - [`SineEffectController`](#sineeffectcontroller)
- [`ZigzagEffectController`](#zigzageffectcontroller) - [`ZigzagEffectController`](#zigzageffectcontroller)
@ -506,6 +507,16 @@ final ec = DelayedEffectController(LinearEffectController(1), delay: 5);
``` ```
### `NoiseEffectController`
This effect controller exhibits noisy behavior, i.e. it oscillates randomly around zero. Such effect
controller can be used to implement a variety of shake effects.
```dart
final ec = NoiseEffectController(duration: 0.6, frequency: 10);
```
### `RandomEffectController` ### `RandomEffectController`
This controller wraps another controller and makes its duration random. The actual value for the This controller wraps another controller and makes its duration random. The actual value for the

View File

@ -14,6 +14,8 @@ class MoveEffectExample extends FlameGame {
The middle green square has a combination of two movement effects: a The middle green square has a combination of two movement effects: a
`MoveEffect.to` and a `MoveEffect.by` which forces it to periodically jump. `MoveEffect.to` and a `MoveEffect.by` which forces it to periodically jump.
The purple square executes a sequence of shake effects.
At the bottom there are 60 more components which demonstrate movement along At the bottom there are 60 more components which demonstrate movement along
an arbitrary path using `MoveEffect.along`. an arbitrary path using `MoveEffect.along`.
'''; ''';
@ -31,7 +33,9 @@ class MoveEffectExample extends FlameGame {
..style = PaintingStyle.stroke ..style = PaintingStyle.stroke
..strokeWidth = 5.0 ..strokeWidth = 5.0
..color = Colors.greenAccent; ..color = Colors.greenAccent;
final paint3 = Paint()..color = const Color(0xffb372dc);
// Red square, moving back and forth
add( add(
RectangleComponent.square( RectangleComponent.square(
position: Vector2(20, 50), position: Vector2(20, 50),
@ -49,6 +53,8 @@ class MoveEffectExample extends FlameGame {
), ),
), ),
); );
// Green square, moving and jumping
add( add(
RectangleComponent.square( RectangleComponent.square(
position: Vector2(20, 150), position: Vector2(20, 150),
@ -80,6 +86,29 @@ class MoveEffectExample extends FlameGame {
), ),
); );
add(
RectangleComponent.square(
size: 15,
position: Vector2(40, 240),
paint: paint3,
)..add(
SequenceEffect(
[
MoveEffect.by(
Vector2(5, 0),
NoiseEffectController(duration: 1, frequency: 20),
),
MoveEffect.by(Vector2.zero(), LinearEffectController(2)),
MoveEffect.by(
Vector2(0, 10),
NoiseEffectController(duration: 1, frequency: 10),
),
],
infinite: true,
),
),
);
final path1 = Path()..moveTo(200, 250); final path1 = Path()..moveTo(200, 250);
for (var i = 1; i <= 5; i++) { for (var i = 1; i <= 5; i++) {
final x = 200 + 100 * sin(i * tau * 2 / 5); final x = 200 + 100 * sin(i * tau * 2 / 5);

View File

@ -6,6 +6,7 @@ export 'src/effects/controllers/duration_effect_controller.dart';
export 'src/effects/controllers/effect_controller.dart'; export 'src/effects/controllers/effect_controller.dart';
export 'src/effects/controllers/infinite_effect_controller.dart'; export 'src/effects/controllers/infinite_effect_controller.dart';
export 'src/effects/controllers/linear_effect_controller.dart'; export 'src/effects/controllers/linear_effect_controller.dart';
export 'src/effects/controllers/noise_effect_controller.dart';
export 'src/effects/controllers/pause_effect_controller.dart'; export 'src/effects/controllers/pause_effect_controller.dart';
export 'src/effects/controllers/random_effect_controller.dart'; export 'src/effects/controllers/random_effect_controller.dart';
export 'src/effects/controllers/repeated_effect_controller.dart'; export 'src/effects/controllers/repeated_effect_controller.dart';

View File

@ -0,0 +1,42 @@
import 'dart:math';
import 'package:flutter/animation.dart' show Curve, Curves;
import 'package:vector_math/vector_math_64.dart';
import 'duration_effect_controller.dart';
/// Effect controller that oscillates around 0 following a noise curve.
///
/// The [frequency] parameter controls smoothness/jerkiness of the oscillations.
/// It is roughly proportional to the total number of swings for the duration
/// of the effect.
///
/// The [taperingCurve] describes how the effect fades out over time. The
/// curve that you supply will be flipped along the X axis, so that the effect
/// starts at full force, and gradually reduces to zero towards the end.
///
/// This effect controller can be used to implement various shake effects. For
/// example, putting into a `MoveEffect.by` will create a shake motion, where
/// the magnitude and the direction of shaking is controlled by the effect's
/// `offset`.
class NoiseEffectController extends DurationEffectController {
NoiseEffectController({
required double duration,
required this.frequency,
this.taperingCurve = Curves.easeInOutCubic,
Random? random,
}) : assert(duration > 0, 'duration must be positive'),
assert(frequency > 0, 'frequency parameter must be positive'),
noise = SimplexNoise(random),
super(duration);
final double frequency;
final Curve taperingCurve;
final SimplexNoise noise;
@override
double get progress {
final x = timer / duration;
final amplitude = taperingCurve.transform(1 - x);
return noise.noise2D(x * frequency, 0) * amplitude;
}
}

View File

@ -0,0 +1,59 @@
import 'dart:math';
import 'package:flame/effects.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter/animation.dart';
import 'package:test/test.dart';
void main() {
group('NoiseEffectController', () {
test('general properties', () {
final ec = NoiseEffectController(duration: 1, frequency: 12);
expect(ec.duration, 1.0);
expect(ec.frequency, 12.0);
expect(ec.taperingCurve, Curves.easeInOutCubic);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, 0);
expect(ec.isRandom, false);
});
test('progression', () {
final random = Random(567890);
final ec = NoiseEffectController(
duration: 1,
frequency: 3,
random: random,
);
final observed = <double>[];
for (var t = 0.0; t < 1.0; t += 0.1) {
observed.add(ec.progress);
ec.advance(0.1);
}
expect(observed, [
0.0,
-0.4852269950897251,
0.7905631204866628,
0.25384428741054194,
0.06718741964100555,
0.08011164287850409,
-0.008746065536907871,
-0.07181264736289301,
-0.014005001721806985,
0.00985567863632108,
-0.000015661267181374608,
]);
});
test('errors', () {
expect(
() => NoiseEffectController(duration: 0, frequency: 1),
failsAssert('duration must be positive'),
);
expect(
() => NoiseEffectController(duration: 1, frequency: 0),
failsAssert('frequency parameter must be positive'),
);
});
});
}