RemoveEffect (#1063)

* Added FlameAnimationController class

* Added MainAnimationController class

* Update doc comments

* formatting

* rename MainAnimationController

* Added tests for StandardAnimationController

* Added more tests

* comment

* Added changelog note

* Export StandardAnimationController

* formatting

* Use a default for 'curve' parameter

* rename onsetDelay -> startDelay

* Added Transofm2DEffect

* Added EffectComponent

* Added .transform getter

* formatting

* Rename EffectComponent -> Effect

* Add documentation for the Effect class

* minor

* Added a test for Effect class

* Adding tests for removeOnFinish

* Adding tests for onStart and onFinish

* Also check the effect after reset

* Fix-up merge

* formatting

* added doc-comments

* changelog note

* Added test for transform2DEffect

* Adjusted comments

* Make PositionComponent._transform public

* change changelog

* Added SimpleEffectController

* Added DestroyEffect

* Changelog note

* Rename DestroyEffect -> RemoveEffect

* Added example for RemoveEffect

* flutter format

* Move description of the RemoveEffectExample game

* move the description again

* minor
This commit is contained in:
Pasha Stetsenko
2021-11-02 01:59:35 -07:00
committed by GitHub
parent 8446ca72bf
commit d9984c7bda
8 changed files with 300 additions and 0 deletions

View File

@ -18,4 +18,11 @@ class CircleComponent extends PositionComponent {
super.render(canvas);
canvas.drawCircle(Offset(radius, radius), radius, paint);
}
@override
bool containsPoint(Vector2 point) {
final local = absoluteToLocal(point);
final center = Vector2.all(radius);
return local.distanceToSquared(center) <= radius * radius;
}
}

View File

@ -7,6 +7,7 @@ import 'combined_effect.dart';
import 'infinite_effect.dart';
import 'move_effect.dart';
import 'opacity_effect.dart';
import 'remove_effect_example.dart';
import 'rotate_effect.dart';
import 'scale_effect.dart';
import 'sequence_effect.dart';
@ -67,5 +68,11 @@ void addEffectsStories(Dashbook dashbook) {
'Color Effect',
(_) => GameWidget(game: ColorEffectGame()),
codeLink: baseLink('effects/color_effect.dart'),
)
..add(
'Remove Effect',
(_) => GameWidget(game: RemoveEffectExample()),
codeLink: baseLink('effects/remove_effect_example.dart'),
info: RemoveEffectExample.description,
);
}

View File

@ -0,0 +1,41 @@
import 'dart:math';
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame/src/effects2/remove_effect.dart'; // ignore: implementation_imports
import 'package:flutter/material.dart';
import '../../commons/circle_component.dart';
class RemoveEffectExample extends FlameGame with HasTappableComponents {
static const description = '''
Click on any circle to apply a RemoveEffect, which will make the circle
disappear after a 0.5 second delay.
''';
@override
void onMount() {
super.onMount();
camera.viewport = FixedResolutionViewport(Vector2(400, 600));
final rng = Random();
for (var i = 0; i < 20; i++) {
add(_RandomCircle(rng));
}
}
}
class _RandomCircle extends CircleComponent with Tappable {
_RandomCircle(Random rng) : super(radius: rng.nextDouble() * 30 + 10) {
position.setValues(
rng.nextDouble() * 320 + 40,
rng.nextDouble() * 520 + 40,
);
paint.color = Colors.primaries[rng.nextInt(Colors.primaries.length)];
}
@override
bool onTapDown(TapDownInfo info) {
add(RemoveEffect(delay: 0.5));
return false;
}
}

View File

@ -4,6 +4,7 @@
- Added `StandardEffectController` class
- Refactored `Effect` class to use `EffectController`, added `Transform2DEffect` class
- Clarified `TimerComponent` example
- Added `RemoveEffect` and `SimpleEffectController`
## [1.0.0-releasecandidate.16]
- `changePriority` no longer breaks game loop iteration

View File

@ -0,0 +1,16 @@
import 'effect.dart';
import 'simple_effect_controller.dart';
/// This simple effect, when attached to a component, will cause that component
/// to be removed from the game tree after `delay` seconds.
class RemoveEffect extends Effect {
RemoveEffect({double delay = 0.0})
: super(SimpleEffectController(delay: delay));
@override
void apply(double progress) {
if (progress == 1) {
parent?.removeFromParent();
}
}
}

View File

@ -0,0 +1,54 @@
import 'effect_controller.dart';
import 'standard_effect_controller.dart';
/// Simplest possible [EffectController], which supports an effect progressing
/// linearly over [duration] seconds.
///
/// The [duration] can be 0, in which case the effect will jump from 0 to 1
/// instantaneously.
///
/// The [delay] parameter allows to delay the start of the effect by the
/// specified number of seconds.
///
/// See also: [StandardEffectController]
class SimpleEffectController extends EffectController {
SimpleEffectController({
this.duration = 0.0,
this.delay = 0.0,
}) : assert(duration >= 0, 'duration cannot be negative: $duration'),
assert(delay >= 0, 'delay cannot be negative: $delay');
final double duration;
final double delay;
double _timer = 0.0;
@override
bool get started => _timer >= delay;
@override
bool get completed => _timer >= delay + duration;
@override
bool get isInfinite => false;
@override
double get progress {
// If duration == 0, then `completed == started`, and the middle case
// (which divides by duration) cannot occur.
return completed
? 1
: started
? (_timer - delay) / duration
: 0;
}
@override
void update(double dt) {
_timer += dt;
}
@override
void reset() {
_timer = 0;
}
}

View File

@ -0,0 +1,45 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/src/effects2/remove_effect.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('RemoveEffect', () {
test('no delay', () {
final game = FlameGame();
game.onGameResize(Vector2.all(1));
expect(game.children.length, 0);
final obj = Component();
game.add(obj);
game.update(0);
expect(game.children.length, 1);
// First `game.update()` invokes the destroy effect and schedules `obj`
// for deletion; second `game.update()` processes the deletion queue and
// actually removes the component
obj.add(RemoveEffect());
game.update(0);
game.update(0);
expect(game.children.length, 0);
});
test('delayed', () {
final game = FlameGame();
game.onGameResize(Vector2.all(1));
expect(game.children.length, 0);
final obj = Component();
game.add(obj);
game.update(0);
expect(game.children.length, 1);
obj.add(RemoveEffect(delay: 1));
game.update(0.5);
game.update(0);
expect(game.children.length, 1);
game.update(0.5);
game.update(0);
expect(game.children.length, 0);
});
});
}

View File

@ -0,0 +1,129 @@
import 'package:flame/src/effects2/simple_effect_controller.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
group('SimpleEffectController', () {
test('default', () {
final ec = SimpleEffectController();
expect(ec.duration, 0);
expect(ec.delay, 0);
expect(ec.isInfinite, false);
expect(ec.started, true);
expect(ec.completed, true);
expect(ec.progress, 1);
});
test('simple with duration', () {
final ec = SimpleEffectController(duration: 1);
expect(ec.delay, 0);
expect(ec.duration, 1);
expect(ec.progress, 0);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.isInfinite, false);
ec.update(0.5);
expect(ec.progress, 0.5);
expect(ec.started, true);
expect(ec.completed, false);
ec.update(0.5);
expect(ec.progress, 1);
expect(ec.started, true);
expect(ec.completed, true);
ec.update(0.00001);
expect(ec.progress, 1);
expect(ec.started, true);
expect(ec.completed, true);
});
test('simple with delay', () {
final ec = SimpleEffectController(delay: 1);
expect(ec.isInfinite, false);
expect(ec.started, false);
expect(ec.completed, false);
expect(ec.progress, 0);
expect(ec.delay, 1);
expect(ec.duration, 0);
ec.update(0.5);
expect(ec.started, false);
expect(ec.completed, false);
expect(ec.progress, 0);
ec.update(0.5);
expect(ec.started, true);
expect(ec.completed, true);
expect(ec.progress, 1);
});
test('duration + delay', () {
final ec = SimpleEffectController(duration: 1, delay: 2);
expect(ec.isInfinite, false);
expect(ec.started, false);
expect(ec.completed, false);
expect(ec.duration, 1);
expect(ec.delay, 2);
expect(ec.progress, 0);
ec.update(0.5);
expect(ec.started, false);
expect(ec.progress, 0);
ec.update(0.5);
expect(ec.started, false);
expect(ec.progress, 0);
ec.update(1);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, 0);
ec.update(0.5);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, 0.5);
ec.update(0.5);
expect(ec.started, true);
expect(ec.completed, true);
expect(ec.progress, 1);
});
test('reset', () {
final ec = SimpleEffectController();
ec.reset();
expect(ec.started, true);
expect(ec.completed, true);
expect(ec.progress, 1);
});
test('reset 2', () {
final ec = SimpleEffectController(duration: 2, delay: 1);
ec.update(3);
expect(ec.completed, true);
expect(ec.progress, 1);
ec.reset();
expect(ec.started, false);
expect(ec.completed, false);
expect(ec.progress, 0);
ec.update(1);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, 0);
ec.update(1);
expect(ec.started, true);
expect(ec.completed, false);
expect(ec.progress, closeTo(0.5, 1e-15));
ec.update(1);
expect(ec.started, true);
expect(ec.completed, true);
expect(ec.progress, 1);
});
});
}