feat(effects): Added ZigzagEffectController (#1261)

Simple alternating effect controller. Over the course of one period, this controller will proceed
linearly from 0 to 1, then to -1, and then back to 0. Use this for oscillating effects where the
starting position should be the center of the oscillations, rather than the extreme (as provided
by the standard alternating EffectController).
This commit is contained in:
Pasha Stetsenko
2021-12-27 09:19:18 -08:00
committed by GitHub
parent 240634e7a3
commit 59adc5f34c
6 changed files with 145 additions and 2 deletions

View File

@ -61,6 +61,7 @@ There are multiple effect controllers provided by the Flame framework as well:
- [`SequenceEffectController`](#sequenceeffectcontroller)
- [`DelayedEffectController`](#delayedeffectcontroller)
- [`RandomEffectController`](#randomeffectcontroller)
- [`ZigzagEffectController`](#zigzageffectcontroller)
## Built-in effects
@ -469,7 +470,7 @@ duration is re-generated upon each reset, which makes this controller particular
repeated contexts, such as [](#repeatedeffectcontroller) or [](#infiniteeffectcontroller).
```dart
final effect = RandomEffectController.uniform(
final ec = RandomEffectController.uniform(
LinearEffectController(0), // duration here is irrelevant
min: 0.5,
max: 1.5,
@ -481,6 +482,18 @@ of the produced random durations. Two distributions -- `.uniform` and `.exponent
any other can be implemented by the user.
### `ZigzagEffectController`
Simple alternating effect controller. Over the course of one `period`, this controller will proceed
linearly from 0 to 1, then to -1, and then back to 0. Use this for oscillating effects where the
starting position should be the center of the oscillations, rather than the extreme (as provided
by the standard alternating `EffectController`).
```dart
final ec = ZigzagEffectController(period: 2);
```
## See also
* [Examples of various effects](https://examples.flame-engine.org/#/).

View File

@ -0,0 +1,48 @@
import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';
class EffectControllersExample extends FlameGame {
static const description = '''
This page demonstrates application of various non-standard effect
controllers.
The first white square has a ZigzagEffectController with period 1. The
orange square next to it has two move effects, each with a
ZigzagEffectController.
''';
@override
void onMount() {
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
add(
RectangleComponent.square(
position: Vector2(20, 50),
size: 20,
)..add(
MoveEffect.by(
Vector2(0, 20),
InfiniteEffectController(ZigzagEffectController(period: 1)),
),
),
);
add(
RectangleComponent.square(
position: Vector2(70, 50),
size: 20,
paint: Paint()..color = const Color(0xffffbc63),
)..addAll([
MoveEffect.by(
Vector2(0, 20),
InfiniteEffectController(ZigzagEffectController(period: 8 / 7)),
),
MoveEffect.by(
Vector2(10, 0),
InfiniteEffectController(ZigzagEffectController(period: 2 / 3)),
),
]),
);
}
}

View File

@ -3,6 +3,7 @@ import 'package:flame/game.dart';
import '../../commons/commons.dart';
import 'color_effect_example.dart';
import 'effect_controllers_example.dart';
import 'move_effect_example.dart';
import 'opacity_effect_example.dart';
import 'remove_effect_example.dart';
@ -53,5 +54,11 @@ void addEffectsStories(Dashbook dashbook) {
(_) => GameWidget(game: RemoveEffectExample()),
codeLink: baseLink('effects/remove_effect_example.dart'),
info: RemoveEffectExample.description,
)
..add(
'EffectControllers',
(_) => GameWidget(game: EffectControllersExample()),
codeLink: baseLink('effects/effect_controllers_example.dart'),
info: EffectControllersExample.description,
);
}

View File

@ -12,6 +12,7 @@ export 'src/effects/controllers/repeated_effect_controller.dart';
export 'src/effects/controllers/reverse_curved_effect_controller.dart';
export 'src/effects/controllers/reverse_linear_effect_controller.dart';
export 'src/effects/controllers/sequence_effect_controller.dart';
export 'src/effects/controllers/zigzag_effect_controller.dart';
export 'src/effects/effect.dart';
export 'src/effects/move_along_path_effect.dart';
export 'src/effects/move_effect.dart';
@ -21,4 +22,3 @@ export 'src/effects/rotate_effect.dart';
export 'src/effects/scale_effect.dart';
export 'src/effects/size_effect.dart';
export 'src/effects/transform2d_effect.dart';
export 'src/extensions/vector2.dart';

View File

@ -0,0 +1,33 @@
import 'duration_effect_controller.dart';
import 'infinite_effect_controller.dart';
import 'repeated_effect_controller.dart';
/// This effect controller goes from 0 to 1, then back to 0, then to -1, and
/// then again to 0.
///
/// This is similar to an alternating controller, except that it treats the
/// starting position as the "middle ground", and oscillates around it.
///
/// Combine with [RepeatedEffectController] or [InfiniteEffectController] in
/// order to create longer zigzags.
class ZigzagEffectController extends DurationEffectController {
ZigzagEffectController({required double period})
: assert(period > 0, 'Period must be positive: $period'),
_quarterPeriod = period / 4,
super(period);
final double _quarterPeriod;
@override
double get progress {
// Assume zigzag's period is 4 units of length. Within that period, there
// are 3 linear segments: at first it's y = x, for 0 ≤ x ≤ 1, then it's
// y = -x + 2, for 1 ≤ x ≤ 3, and finally it's y = x + (-4), for 3 ≤ x ≤ 4.
final x = timer / _quarterPeriod;
return x <= 1
? x
: x >= 3
? x - 4
: 2 - x;
}
}

View File

@ -0,0 +1,42 @@
import 'package:flame/effects.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('ZigzagEffectController', () {
test('general properties', () {
final ec = ZigzagEffectController(period: 1);
expect(ec.duration, 1);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, 0);
expect(ec.isRandom, false);
});
test('progression', () {
final ec = ZigzagEffectController(period: 4);
final expectedProgress = [
for (var i = 0; i < 10; i++) i * 0.1,
for (var i = 10; i > 0; i--) i * 0.1,
for (var i = 0; i > -10; i--) i * 0.1,
for (var i = -10; i <= 0; i++) i * 0.1,
];
for (final p in expectedProgress) {
expect(ec.progress, closeTo(p, 3e-15));
ec.advance(0.1);
}
expect(ec.completed, true);
});
test('errors', () {
expect(
() => ZigzagEffectController(period: 0),
failsAssert('Period must be positive: 0.0'),
);
expect(
() => ZigzagEffectController(period: -1.1),
failsAssert('Period must be positive: -1.1'),
);
});
});
}