Improving TimerComponent API (#1085)

* Improving TimerComponent API

* pr suggestion

* Update packages/flame/lib/src/components/timer_component.dart

Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>

* pr suggestion

* docs

* pr suggestions

* fixing

* renaming to onTick

* Update packages/flame/lib/src/components/timer_component.dart

Co-authored-by: Luan Nico <luanpotter27@gmail.com>

* Apply suggestions from code review

Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>

* fixing issues

* fixing flmae bloc isse

* more suggestions

* Update packages/flame/lib/src/timer.dart

Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>

Co-authored-by: Lukas Klingsbo <lukas.klingsbo@gmail.com>
Co-authored-by: Luan Nico <luanpotter27@gmail.com>
This commit is contained in:
Erick
2021-11-15 11:55:18 -03:00
committed by GitHub
parent 520d51462b
commit abc29dbc58
14 changed files with 198 additions and 102 deletions

View File

@ -60,10 +60,6 @@ class MyGame extends Game {
final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF)); final TextConfig textConfig = TextConfig(color: const Color(0xFFFFFFFF));
final countdown = Timer(2); final countdown = Timer(2);
MyGame() {
countdown.start();
}
@override @override
void update(double dt) { void update(double dt) {
countdown.update(dt); countdown.update(dt);
@ -103,10 +99,9 @@ class MyGame extends Game {
MyGame() { MyGame() {
interval = Timer( interval = Timer(
1, 1,
callback: () => elapsedSecs += 1, onTick: () => elapsedSecs += 1,
repeat: true, repeat: true,
); );
interval.start();
} }
@override @override
@ -135,12 +130,9 @@ class MyFlameGame extends FlameGame {
MyFlameGame() { MyFlameGame() {
add( add(
TimerComponent( TimerComponent(
Timer( period: 10,
10, repeat: true,
callback: () => print("10 seconds elapsed"), onTick: () => print('10 seconds elapsed'),
repeat: true,
)
..start()
) )
); );
} }

View File

@ -27,7 +27,7 @@ class MovableSquare extends SquareComponent
await super.onLoad(); await super.onLoad();
timer = Timer(3.0) timer = Timer(3.0)
..stop() ..stop()
..callback = () { ..onTick = () {
gameRef.camera.setRelativeOffset(Anchor.center); gameRef.camera.setRelativeOffset(Anchor.center);
}; };
} }

View File

@ -13,6 +13,19 @@ changes its parent from its original game to the component that is rotating.
After another 5 seconds it changes back to its original parent, and so on. After another 5 seconds it changes back to its original parent, and so on.
'''; ''';
class GameChangeTimer extends TimerComponent with HasGameRef<GameInGame> {
GameChangeTimer() : super(period: 5, repeat: true);
@override
void onTick() {
final child = gameRef.draggablesGame.square;
final newParent = child.parent == gameRef.draggablesGame
? gameRef.composedGame.parentSquare
: gameRef.draggablesGame;
child.changeParent(newParent);
}
}
class GameInGame extends FlameGame with HasDraggableComponents { class GameInGame extends FlameGame with HasDraggableComponents {
@override @override
bool debugMode = true; bool debugMode = true;
@ -24,20 +37,10 @@ class GameInGame extends FlameGame with HasDraggableComponents {
await super.onLoad(); await super.onLoad();
composedGame = Composability(); composedGame = Composability();
draggablesGame = DraggablesGame(zoom: 1.0); draggablesGame = DraggablesGame(zoom: 1.0);
await add(composedGame); await add(composedGame);
await add(draggablesGame); await add(draggablesGame);
final child = draggablesGame.square;
final timer = Timer( add(GameChangeTimer());
5,
callback: () {
final newParent = child.parent == draggablesGame
? composedGame.parentSquare
: draggablesGame;
child.changeParent(newParent);
},
repeat: true,
);
timer.start();
add(TimerComponent(timer));
} }
} }

View File

@ -18,7 +18,7 @@ class TimerGame extends FlameGame with TapDetector {
countdown = Timer(2); countdown = Timer(2);
interval = Timer( interval = Timer(
1, 1,
callback: () => elapsedSecs += 1, onTick: () => elapsedSecs += 1,
repeat: true, repeat: true,
); );
interval.start(); interval.start();

View File

@ -1,6 +1,6 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart'; import 'package:flame/game.dart';
import 'package:flame/input.dart'; import 'package:flame/input.dart';
import 'package:flame/timer.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
class RenderedTimeComponent extends TimerComponent { class RenderedTimeComponent extends TimerComponent {
@ -10,9 +10,9 @@ class RenderedTimeComponent extends TimerComponent {
final double yOffset; final double yOffset;
RenderedTimeComponent(Timer timer, {this.yOffset = 150}) RenderedTimeComponent(double period, {this.yOffset = 150})
: super( : super(
timer, period: period,
removeOnFinish: true, removeOnFinish: true,
); );
@ -29,11 +29,11 @@ class RenderedTimeComponent extends TimerComponent {
class TimerComponentGame extends FlameGame with TapDetector, DoubleTapDetector { class TimerComponentGame extends FlameGame with TapDetector, DoubleTapDetector {
@override @override
void onTap() { void onTap() {
add(RenderedTimeComponent(Timer(1)..start())); add(RenderedTimeComponent(1));
} }
@override @override
void onDoubleTap() { void onDoubleTap() {
add(RenderedTimeComponent(Timer(5)..start(), yOffset: 180)); add(RenderedTimeComponent(5, yOffset: 180));
} }
} }

View File

@ -27,6 +27,7 @@
- `HitboxShape` takes parents ancestors transformations into consideration (not scaling) - `HitboxShape` takes parents ancestors transformations into consideration (not scaling)
- Renamed `FlameTester` to `GameTester` - Renamed `FlameTester` to `GameTester`
- Modified `FlameTester` to be specific for `T extends FlameGame` - Modified `FlameTester` to be specific for `T extends FlameGame`
- Improved `TimerComponent`
## [1.0.0-releasecandidate.16] ## [1.0.0-releasecandidate.16]
- `changePriority` no longer breaks game loop iteration - `changePriority` no longer breaks game loop iteration

View File

@ -24,6 +24,7 @@ export 'src/components/sprite_component.dart';
export 'src/components/sprite_group_component.dart'; export 'src/components/sprite_group_component.dart';
export 'src/components/text_box_component.dart'; export 'src/components/text_box_component.dart';
export 'src/components/text_component.dart'; export 'src/components/text_component.dart';
export 'src/components/timer_component.dart';
export 'src/extensions/vector2.dart'; export 'src/extensions/vector2.dart';
export 'src/game/mixins/has_collidables.dart'; export 'src/game/mixins/has_collidables.dart';
export 'src/text.dart'; export 'src/text.dart';

View File

@ -0,0 +1,49 @@
import 'dart:ui';
import '../../components.dart';
/// A component that uses a [Timer] instance which you can react to when it has finished.
class TimerComponent extends Component {
late final Timer timer;
final bool removeOnFinish;
final VoidCallback? _onTick;
/// Creates a [TimerComponent]
///
/// [period] The period of time in seconds that the tick will be called
/// [repeat] When true, this will continue running after [period] is reached
/// [autoStart] When true, will start upon instantiation (default is true)
/// [onTick] When provided, will be called everytime [period] is reached. This
/// overrides the [onTick] method
TimerComponent({
required double period,
bool repeat = false,
bool autoStart = true,
this.removeOnFinish = false,
VoidCallback? onTick,
}) : _onTick = onTick {
timer = Timer(
period,
repeat: repeat,
onTick: this.onTick,
autoStart: autoStart,
);
}
/// Called everytime the [timer] reached a tick.
/// The default implementation calls the closure received on the
/// constructor and can be overriden to add custom logic.
void onTick() {
_onTick?.call();
}
@override
void update(double dt) {
super.update(dt);
timer.update(dt);
if (removeOnFinish && timer.finished) {
removeFromParent();
}
}
}

View File

@ -93,7 +93,7 @@ abstract class Particle {
// TODO: Maybe make it into a setter/getter? // TODO: Maybe make it into a setter/getter?
_lifespan = lifespan; _lifespan = lifespan;
_timer?.stop(); _timer?.stop();
_timer = Timer(lifespan, callback: () => _shouldBeRemoved = true)..start(); _timer = Timer(lifespan, onTick: () => _shouldBeRemoved = true)..start();
} }
/// Wraps this particle with a [TranslatedParticle]. /// Wraps this particle with a [TranslatedParticle].

View File

@ -1,18 +1,23 @@
import 'dart:math'; import 'dart:math';
import 'dart:ui'; import 'dart:ui';
import 'components/component.dart';
/// Simple utility class that helps handling time counting and implementing /// Simple utility class that helps handling time counting and implementing
/// interval like events. /// interval like events.
///
/// Timer auto start by default.
class Timer { class Timer {
final double limit; final double limit;
VoidCallback? callback; VoidCallback? onTick;
bool repeat; bool repeat;
double _current = 0; double _current = 0;
bool _running = false; bool _running;
Timer(this.limit, {this.callback, this.repeat = false}); Timer(
this.limit, {
this.onTick,
this.repeat = false,
bool autoStart = true,
}) : _running = autoStart;
/// The current amount of ms that has passed on this iteration /// The current amount of ms that has passed on this iteration
double get current => _current; double get current => _current;
@ -32,15 +37,15 @@ class Timer {
if (_current >= limit) { if (_current >= limit) {
if (!repeat) { if (!repeat) {
_running = false; _running = false;
callback?.call(); onTick?.call();
return; return;
} }
// This is used to cover the rare case of _current being more than // This is used to cover the rare case of _current being more than
// two times the value of limit, so that the callback is called the // two times the value of limit, so that the onTick is called the
// correct number of times // correct number of times
while (_current >= limit) { while (_current >= limit) {
_current -= limit; _current -= limit;
callback?.call(); onTick?.call();
} }
} }
} }
@ -73,21 +78,3 @@ class Timer {
_running = true; _running = true;
} }
} }
/// Simple component which wraps a [Timer] instance allowing it to be easily
/// used inside a FlameGame game.
class TimerComponent extends Component {
Timer timer;
final bool removeOnFinish;
TimerComponent(this.timer, {this.removeOnFinish = false});
@override
void update(double dt) {
super.update(dt);
timer.update(dt);
if (removeOnFinish && timer.finished) {
removeFromParent();
}
}
}

View File

@ -0,0 +1,82 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';
class MyTimerComponent extends TimerComponent {
int count = 0;
MyTimerComponent()
: super(
period: 1,
repeat: true,
removeOnFinish: false,
);
@override
void onTick() {
count++;
}
}
class NonRepeatingTimerComponent extends TimerComponent {
NonRepeatingTimerComponent()
: super(
period: 1,
repeat: false,
removeOnFinish: true,
);
}
void main() {
group('TimerComponent', () {
final tester = FlameTester(() => FlameGame());
tester.test('runs the tick method', (game) {
final timer = MyTimerComponent();
game.add(timer);
game.update(0);
game.update(1.2);
game.update(0);
expect(timer.count, equals(1));
});
tester.test('never remove from the game when is repeating', (game) {
game.add(MyTimerComponent());
game.update(0);
game.update(1.2);
game.update(0);
expect(game.children.length, equals(1));
});
tester.test('is removed from the game when is finished', (game) {
game.add(NonRepeatingTimerComponent());
game.update(0);
game.update(1.2);
game.update(0);
expect(game.children.length, equals(0));
});
tester.test('calls onTick when provided', (game) {
var called = false;
game.add(
TimerComponent(
period: 1,
onTick: () {
called = true;
},
),
);
game.update(0);
game.update(1.2);
expect(called, isTrue);
});
});
}

View File

@ -4,7 +4,7 @@ import 'package:test/test.dart';
void main() { void main() {
group('Timer', () { group('Timer', () {
test('can be started and stopped, discarding progress', () { test('can be started and stopped, discarding progress', () {
final timer = Timer(1.0); final timer = Timer(1.0, autoStart: false);
expect(timer.isRunning(), false); expect(timer.isRunning(), false);
timer.start(); timer.start();
expect(timer.isRunning(), true); expect(timer.isRunning(), true);
@ -15,7 +15,7 @@ void main() {
}); });
test('can be paused and resumed, retaining progress', () { test('can be paused and resumed, retaining progress', () {
final timer = Timer(1.0); final timer = Timer(1.0, autoStart: false);
expect(timer.isRunning(), false); expect(timer.isRunning(), false);
timer.start(); timer.start();
expect(timer.isRunning(), true); expect(timer.isRunning(), true);
@ -29,7 +29,6 @@ void main() {
test('tracks current delta time', () { test('tracks current delta time', () {
final timer = Timer(1.0); final timer = Timer(1.0);
timer.start();
timer.update(0.5); timer.update(0.5);
expect(timer.current, 0.5); expect(timer.current, 0.5);
timer.update(0.2); timer.update(0.2);
@ -38,7 +37,6 @@ void main() {
test('tracks progress percent capped at 1.0', () { test('tracks progress percent capped at 1.0', () {
final timer = Timer(2.0); final timer = Timer(2.0);
timer.start();
timer.update(0.5); timer.update(0.5);
expect(timer.progress, 0.25); expect(timer.progress, 0.25);
timer.update(0.5); timer.update(0.5);
@ -47,41 +45,37 @@ void main() {
expect(timer.progress, 1.0); expect(timer.progress, 1.0);
}); });
test('callback fires once if non-repeating', () { test('onTick fires once if non-repeating', () {
var callbackCount = 0; var onTickCount = 0;
final timer = Timer(1.0, callback: () => callbackCount++); final timer = Timer(1.0, onTick: () => onTickCount++);
timer.start();
timer.update(0.9); timer.update(0.9);
expect(callbackCount, 0); expect(onTickCount, 0);
timer.update(0.2); timer.update(0.2);
expect(callbackCount, 1); expect(onTickCount, 1);
timer.update(1.0); timer.update(1.0);
expect(callbackCount, 1); expect(onTickCount, 1);
}); });
test('finishes when complete if non-repeating', () { test('finishes when complete if non-repeating', () {
final timer = Timer(1.0); final timer = Timer(1.0);
timer.start();
expect(timer.finished, false); expect(timer.finished, false);
timer.update(1.1); timer.update(1.1);
expect(timer.finished, true); expect(timer.finished, true);
}); });
test('callback fires repeatedly if repeating', () { test('onTick fires repeatedly if repeating', () {
var callbackCount = 0; var onTickCount = 0;
final timer = Timer(1.0, repeat: true, callback: () => callbackCount++); final timer = Timer(1.0, repeat: true, onTick: () => onTickCount++);
timer.start();
timer.update(0.9); timer.update(0.9);
expect(callbackCount, 0); expect(onTickCount, 0);
timer.update(0.2); timer.update(0.2);
expect(callbackCount, 1); expect(onTickCount, 1);
timer.update(1.0); timer.update(1.0);
expect(callbackCount, 2); expect(onTickCount, 2);
}); });
test('does not finish past limit if repeating', () { test('does not finish past limit if repeating', () {
final timer = Timer(1.0, repeat: true); final timer = Timer(1.0, repeat: true);
timer.start();
expect(timer.finished, false); expect(timer.finished, false);
timer.update(1.1); timer.update(1.1);
expect(timer.finished, false); expect(timer.finished, false);

View File

@ -1,35 +1,22 @@
import 'dart:math'; import 'dart:math';
import 'package:flame/components.dart'; import 'package:flame/components.dart';
import 'package:flame/timer.dart';
import './enemy.dart'; import './enemy.dart';
import '../game.dart'; import '../game.dart';
class EnemyCreator extends Component with HasGameRef<SpaceShooterGame> { class EnemyCreator extends TimerComponent with HasGameRef<SpaceShooterGame> {
late Timer enemyCreator;
Random random = Random(); Random random = Random();
EnemyCreator() { EnemyCreator() : super(period: 1, repeat: true);
enemyCreator = Timer(
1,
repeat: true,
callback: () {
gameRef.add(
EnemyComponent(
(gameRef.size.x - 25) * random.nextDouble(),
0,
),
);
},
);
enemyCreator.start();
}
@override @override
void update(double dt) { void onTick() {
super.update(dt); gameRef.add(
enemyCreator.update(dt); EnemyComponent(
(gameRef.size.x - 25) * random.nextDouble(),
0,
),
);
} }
} }

View File

@ -45,7 +45,7 @@ class PlayerComponent extends SpriteAnimationComponent
PlayerComponent() PlayerComponent()
: super(size: Vector2(50, 75), position: Vector2(100, 500)) { : super(size: Vector2(50, 75), position: Vector2(100, 500)) {
bulletCreator = Timer(0.5, repeat: true, callback: _createBullet); bulletCreator = Timer(0.5, repeat: true, onTick: _createBullet);
addHitbox(HitboxRectangle()); addHitbox(HitboxRectangle());
} }