diff --git a/packages/flame/lib/effects.dart b/packages/flame/lib/effects.dart index ac4ec21afa..d3382e11e4 100644 --- a/packages/flame/lib/effects.dart +++ b/packages/flame/lib/effects.dart @@ -9,6 +9,7 @@ export 'src/effects/controllers/duration_effect_controller.dart'; export 'src/effects/controllers/effect_controller.dart'; export 'src/effects/controllers/infinite_effect_controller.dart'; export 'src/effects/controllers/linear_effect_controller.dart'; +export 'src/effects/controllers/mixins/has_single_child_effect_controller.dart'; export 'src/effects/controllers/pause_effect_controller.dart'; export 'src/effects/controllers/random_effect_controller.dart'; export 'src/effects/controllers/repeated_effect_controller.dart'; diff --git a/packages/flame/lib/src/effects/controllers/delayed_effect_controller.dart b/packages/flame/lib/src/effects/controllers/delayed_effect_controller.dart index d4b7c825a8..940c71da88 100644 --- a/packages/flame/lib/src/effects/controllers/delayed_effect_controller.dart +++ b/packages/flame/lib/src/effects/controllers/delayed_effect_controller.dart @@ -1,9 +1,9 @@ -import 'package:flame/src/effects/controllers/effect_controller.dart'; -import 'package:flame/src/effects/effect.dart'; +import 'package:flame/effects.dart'; /// An effect controller that waits for [delay] seconds before running the /// child controller. While waiting, the progress will be reported at 0. -class DelayedEffectController extends EffectController { +class DelayedEffectController extends EffectController + with HasSingleChildEffectController { DelayedEffectController(EffectController child, {required this.delay}) : assert(delay >= 0, 'Delay must be non-negative: $delay'), _child = child, @@ -14,6 +14,9 @@ class DelayedEffectController extends EffectController { final double delay; double _timer; + @override + EffectController get child => _child; + @override bool get isRandom => _child.isRandom; @@ -65,14 +68,12 @@ class DelayedEffectController extends EffectController { @override void setToStart() { _timer = 0; + super.setToStart(); } @override void setToEnd() { _timer = delay; - _child.setToEnd(); + super.setToEnd(); } - - @override - void onMount(Effect parent) => _child.onMount(parent); } diff --git a/packages/flame/lib/src/effects/controllers/infinite_effect_controller.dart b/packages/flame/lib/src/effects/controllers/infinite_effect_controller.dart index 0f900f9f97..abfcf3b77d 100644 --- a/packages/flame/lib/src/effects/controllers/infinite_effect_controller.dart +++ b/packages/flame/lib/src/effects/controllers/infinite_effect_controller.dart @@ -1,12 +1,17 @@ -import 'package:flame/src/effects/controllers/effect_controller.dart'; -import 'package:flame/src/effects/effect.dart'; +import 'package:flame/effects.dart'; /// Effect controller that wraps a [child] effect controller and repeats it /// infinitely. -class InfiniteEffectController extends EffectController { - InfiniteEffectController(this.child) : super.empty(); +class InfiniteEffectController extends EffectController + with HasSingleChildEffectController { + InfiniteEffectController(EffectController child) + : _child = child, + super.empty(); - final EffectController child; + final EffectController _child; + + @override + EffectController get child => _child; @override bool get completed => false; @@ -45,17 +50,4 @@ class InfiniteEffectController extends EffectController { } return 0; } - - @override - void setToStart() { - child.setToStart(); - } - - @override - void setToEnd() { - child.setToEnd(); - } - - @override - void onMount(Effect parent) => child.onMount(parent); } diff --git a/packages/flame/lib/src/effects/controllers/mixins/has_single_child_effect_controller.dart b/packages/flame/lib/src/effects/controllers/mixins/has_single_child_effect_controller.dart new file mode 100644 index 0000000000..9dd0d1d063 --- /dev/null +++ b/packages/flame/lib/src/effects/controllers/mixins/has_single_child_effect_controller.dart @@ -0,0 +1,28 @@ +import 'package:flame/effects.dart'; +import 'package:meta/meta.dart'; + +/// This mixin must be used with [EffectController]s that wrap a single child +/// effect controller of type [T]. +mixin HasSingleChildEffectController + on EffectController { + /// Returns the wrapped child effect controller. + T get child; + + @mustCallSuper + @override + void setToStart() { + child.setToStart(); + } + + @mustCallSuper + @override + void setToEnd() { + child.setToEnd(); + } + + @mustCallSuper + @override + void onMount(Effect parent) { + child.onMount(parent); + } +} diff --git a/packages/flame/lib/src/effects/controllers/random_effect_controller.dart b/packages/flame/lib/src/effects/controllers/random_effect_controller.dart index 83ba219b82..1851562e98 100644 --- a/packages/flame/lib/src/effects/controllers/random_effect_controller.dart +++ b/packages/flame/lib/src/effects/controllers/random_effect_controller.dart @@ -1,8 +1,6 @@ import 'dart:math'; -import 'package:flame/src/effects/controllers/duration_effect_controller.dart'; -import 'package:flame/src/effects/controllers/effect_controller.dart'; -import 'package:flame/src/effects/effect.dart'; +import 'package:flame/effects.dart'; /// An [EffectController] that wraps another effect controller [child] and /// randomizes its duration after each reset. @@ -14,9 +12,11 @@ import 'package:flame/src/effects/effect.dart'; /// The child's duration is randomized first at construction, and then at each /// reset (`setToStart`). Thus, the child has a concrete well-defined duration /// at any point in time. -class RandomEffectController extends EffectController { - RandomEffectController(this.child, this.randomGenerator) +class RandomEffectController extends EffectController + with HasSingleChildEffectController { + RandomEffectController(DurationEffectController child, this.randomGenerator) : assert(!child.isInfinite, 'Child cannot be infinite'), + _child = child, super.empty() { _initializeDuration(); } @@ -52,9 +52,12 @@ class RandomEffectController extends EffectController { ); } - final DurationEffectController child; + final DurationEffectController _child; final RandomVariable randomGenerator; + @override + DurationEffectController get child => _child; + @override bool get isRandom => true; @@ -73,18 +76,12 @@ class RandomEffectController extends EffectController { @override double recede(double dt) => child.recede(dt); - @override - void setToEnd() => child.setToEnd(); - @override void setToStart() { - child.setToStart(); + super.setToStart(); _initializeDuration(); } - @override - void onMount(Effect parent) => child.onMount(parent); - void _initializeDuration() { final duration = randomGenerator.nextValue(); assert( diff --git a/packages/flame/lib/src/effects/controllers/repeated_effect_controller.dart b/packages/flame/lib/src/effects/controllers/repeated_effect_controller.dart index 31506cb1fb..de3ccd7884 100644 --- a/packages/flame/lib/src/effects/controllers/repeated_effect_controller.dart +++ b/packages/flame/lib/src/effects/controllers/repeated_effect_controller.dart @@ -1,19 +1,20 @@ -import 'package:flame/src/effects/controllers/effect_controller.dart'; -import 'package:flame/src/effects/effect.dart'; +import 'package:flame/effects.dart'; /// Effect controller that repeats [child] controller a certain number of times. /// /// The [repeatCount] must be positive, and [child] controller cannot be /// infinite. The child controller will be reset after each iteration (except /// the last). -class RepeatedEffectController extends EffectController { - RepeatedEffectController(this.child, this.repeatCount) +class RepeatedEffectController extends EffectController + with HasSingleChildEffectController { + RepeatedEffectController(EffectController child, this.repeatCount) : assert(repeatCount > 0, 'repeatCount must be positive'), assert(!child.isInfinite, 'child cannot be infinite'), + _child = child, _remainingCount = repeatCount, super.empty(); - final EffectController child; + final EffectController _child; final int repeatCount; /// How many iterations this controller has remaining. When this reaches 0 @@ -21,6 +22,9 @@ class RepeatedEffectController extends EffectController { int get remainingIterationsCount => _remainingCount; int _remainingCount; + @override + EffectController get child => _child; + @override double get progress => child.progress; @@ -74,15 +78,12 @@ class RepeatedEffectController extends EffectController { @override void setToStart() { _remainingCount = repeatCount; - child.setToStart(); + super.setToStart(); } @override void setToEnd() { _remainingCount = 0; - child.setToEnd(); + super.setToEnd(); } - - @override - void onMount(Effect parent) => child.onMount(parent); } diff --git a/packages/flame/lib/src/effects/controllers/speed_effect_controller.dart b/packages/flame/lib/src/effects/controllers/speed_effect_controller.dart index dfed43a3c2..f3091b47e8 100644 --- a/packages/flame/lib/src/effects/controllers/speed_effect_controller.dart +++ b/packages/flame/lib/src/effects/controllers/speed_effect_controller.dart @@ -1,6 +1,4 @@ -import 'package:flame/src/effects/controllers/duration_effect_controller.dart'; -import 'package:flame/src/effects/controllers/effect_controller.dart'; -import 'package:flame/src/effects/effect.dart'; +import 'package:flame/effects.dart'; import 'package:flame/src/effects/measurable_effect.dart'; /// This controller can force execution of an effect at a predefined speed. @@ -15,12 +13,14 @@ import 'package:flame/src/effects/measurable_effect.dart'; /// - the [speed] cannot be zero (or negative), /// - the [child] controller must be a [DurationEffectController], /// - the parent effect must be a [MeasurableEffect]. -class SpeedEffectController extends EffectController { - SpeedEffectController(this.child, {required this.speed}) +class SpeedEffectController extends EffectController + with HasSingleChildEffectController { + SpeedEffectController(DurationEffectController child, {required this.speed}) : assert(speed > 0, 'Speed must be positive: $speed'), + _child = child, super.empty(); - final DurationEffectController child; + final DurationEffectController _child; final double speed; late MeasurableEffect _parentEffect; @@ -30,6 +30,9 @@ class SpeedEffectController extends EffectController { /// (which will happen at the first call to `advance()`). bool _initialized = false; + @override + DurationEffectController get child => _child; + @override bool get isRandom => true; @@ -68,13 +71,13 @@ class SpeedEffectController extends EffectController { @override void setToEnd() { _initialized = false; - child.setToEnd(); + super.setToEnd(); } @override void setToStart() { _initialized = false; - child.setToStart(); + super.setToStart(); } @override @@ -84,6 +87,6 @@ class SpeedEffectController extends EffectController { 'SpeedEffectController can only be applied to a MeasurableEffect', ); _parentEffect = parent as MeasurableEffect; - child.onMount(parent); + super.onMount(parent); } } diff --git a/packages/flame/test/effects/controllers/mixins/has_single_child_effect_controller_test.dart b/packages/flame/test/effects/controllers/mixins/has_single_child_effect_controller_test.dart new file mode 100644 index 0000000000..dd7e9c7933 --- /dev/null +++ b/packages/flame/test/effects/controllers/mixins/has_single_child_effect_controller_test.dart @@ -0,0 +1,51 @@ +import 'package:flame/effects.dart'; +import 'package:mocktail/mocktail.dart'; +import 'package:test/test.dart'; + +void main() { + group('HasSingleChildEffectController', () { + test('child getter should return the wrapped child effect controller', () { + final childController = _MockEffectController(); + final controller = _TestEffectController(childController); + + expect(controller.child, equals(childController)); + }); + + test('setToStart should call setToStart on the child effect controller', + () { + final childController = _MockEffectController(); + final controller = _TestEffectController(childController); + controller.setToStart(); + verify(childController.setToStart).called(1); + }); + + test('setToEnd should call setToEnd on the child effect controller', () { + final childController = _MockEffectController(); + final controller = _TestEffectController(childController); + controller.setToEnd(); + verify(childController.setToEnd).called(1); + }); + + test('onMount should call onMount on the child effect controller', () { + final childController = _MockEffectController(); + final controller = _TestEffectController(childController); + final parentEffect = _MockEffect(); + controller.onMount(parentEffect); + verify(() => childController.onMount(parentEffect)).called(1); + }); + }); +} + +class _TestEffectController extends _MockEffectController + with HasSingleChildEffectController<_MockEffectController> { + _TestEffectController(_MockEffectController child) : _child = child; + + final _MockEffectController _child; + + @override + _MockEffectController get child => _child; +} + +class _MockEffectController extends Mock implements EffectController {} + +class _MockEffect extends Mock implements Effect {}