Skip to content

Commit

Permalink
feat(effects): Added ZigzagEffectController (#1261)
Browse files Browse the repository at this point in the history
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).
  • Loading branch information
st-pasha committed Dec 27, 2021
1 parent 240634e commit 59adc5f
Show file tree
Hide file tree
Showing 6 changed files with 145 additions and 2 deletions.
15 changes: 14 additions & 1 deletion doc/effects.md
Expand Up @@ -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
Expand Down Expand Up @@ -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,
Expand All @@ -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/#/).
Expand Down
48 changes: 48 additions & 0 deletions examples/lib/stories/effects/effect_controllers_example.dart
@@ -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)),
),
]),
);
}
}
7 changes: 7 additions & 0 deletions examples/lib/stories/effects/effects.dart
Expand Up @@ -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';
Expand Down Expand Up @@ -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,
);
}
2 changes: 1 addition & 1 deletion packages/flame/lib/effects.dart
Expand Up @@ -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';
Expand All @@ -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';
@@ -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;
}
}
@@ -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'),
);
});
});
}

0 comments on commit 59adc5f

Please sign in to comment.