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 countdown = Timer(2);
MyGame() {
countdown.start();
}
@override
void update(double dt) {
countdown.update(dt);
@ -103,10 +99,9 @@ class MyGame extends Game {
MyGame() {
interval = Timer(
1,
callback: () => elapsedSecs += 1,
onTick: () => elapsedSecs += 1,
repeat: true,
);
interval.start();
}
@override
@ -135,12 +130,9 @@ class MyFlameGame extends FlameGame {
MyFlameGame() {
add(
TimerComponent(
Timer(
10,
callback: () => print("10 seconds elapsed"),
repeat: true,
)
..start()
period: 10,
repeat: true,
onTick: () => print('10 seconds elapsed'),
)
);
}

View File

@ -27,7 +27,7 @@ class MovableSquare extends SquareComponent
await super.onLoad();
timer = Timer(3.0)
..stop()
..callback = () {
..onTick = () {
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.
''';
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 {
@override
bool debugMode = true;
@ -24,20 +37,10 @@ class GameInGame extends FlameGame with HasDraggableComponents {
await super.onLoad();
composedGame = Composability();
draggablesGame = DraggablesGame(zoom: 1.0);
await add(composedGame);
await add(draggablesGame);
final child = draggablesGame.square;
final timer = Timer(
5,
callback: () {
final newParent = child.parent == draggablesGame
? composedGame.parentSquare
: draggablesGame;
child.changeParent(newParent);
},
repeat: true,
);
timer.start();
add(TimerComponent(timer));
add(GameChangeTimer());
}
}

View File

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

View File

@ -1,6 +1,6 @@
import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flame/input.dart';
import 'package:flame/timer.dart';
import 'package:flutter/material.dart';
class RenderedTimeComponent extends TimerComponent {
@ -10,9 +10,9 @@ class RenderedTimeComponent extends TimerComponent {
final double yOffset;
RenderedTimeComponent(Timer timer, {this.yOffset = 150})
RenderedTimeComponent(double period, {this.yOffset = 150})
: super(
timer,
period: period,
removeOnFinish: true,
);
@ -29,11 +29,11 @@ class RenderedTimeComponent extends TimerComponent {
class TimerComponentGame extends FlameGame with TapDetector, DoubleTapDetector {
@override
void onTap() {
add(RenderedTimeComponent(Timer(1)..start()));
add(RenderedTimeComponent(1));
}
@override
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)
- Renamed `FlameTester` to `GameTester`
- Modified `FlameTester` to be specific for `T extends FlameGame`
- Improved `TimerComponent`
## [1.0.0-releasecandidate.16]
- `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/text_box_component.dart';
export 'src/components/text_component.dart';
export 'src/components/timer_component.dart';
export 'src/extensions/vector2.dart';
export 'src/game/mixins/has_collidables.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?
_lifespan = lifespan;
_timer?.stop();
_timer = Timer(lifespan, callback: () => _shouldBeRemoved = true)..start();
_timer = Timer(lifespan, onTick: () => _shouldBeRemoved = true)..start();
}
/// Wraps this particle with a [TranslatedParticle].

View File

@ -1,18 +1,23 @@
import 'dart:math';
import 'dart:ui';
import 'components/component.dart';
/// Simple utility class that helps handling time counting and implementing
/// interval like events.
///
/// Timer auto start by default.
class Timer {
final double limit;
VoidCallback? callback;
VoidCallback? onTick;
bool repeat;
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
double get current => _current;
@ -32,15 +37,15 @@ class Timer {
if (_current >= limit) {
if (!repeat) {
_running = false;
callback?.call();
onTick?.call();
return;
}
// 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
while (_current >= limit) {
_current -= limit;
callback?.call();
onTick?.call();
}
}
}
@ -73,21 +78,3 @@ class Timer {
_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() {
group('Timer', () {
test('can be started and stopped, discarding progress', () {
final timer = Timer(1.0);
final timer = Timer(1.0, autoStart: false);
expect(timer.isRunning(), false);
timer.start();
expect(timer.isRunning(), true);
@ -15,7 +15,7 @@ void main() {
});
test('can be paused and resumed, retaining progress', () {
final timer = Timer(1.0);
final timer = Timer(1.0, autoStart: false);
expect(timer.isRunning(), false);
timer.start();
expect(timer.isRunning(), true);
@ -29,7 +29,6 @@ void main() {
test('tracks current delta time', () {
final timer = Timer(1.0);
timer.start();
timer.update(0.5);
expect(timer.current, 0.5);
timer.update(0.2);
@ -38,7 +37,6 @@ void main() {
test('tracks progress percent capped at 1.0', () {
final timer = Timer(2.0);
timer.start();
timer.update(0.5);
expect(timer.progress, 0.25);
timer.update(0.5);
@ -47,41 +45,37 @@ void main() {
expect(timer.progress, 1.0);
});
test('callback fires once if non-repeating', () {
var callbackCount = 0;
final timer = Timer(1.0, callback: () => callbackCount++);
timer.start();
test('onTick fires once if non-repeating', () {
var onTickCount = 0;
final timer = Timer(1.0, onTick: () => onTickCount++);
timer.update(0.9);
expect(callbackCount, 0);
expect(onTickCount, 0);
timer.update(0.2);
expect(callbackCount, 1);
expect(onTickCount, 1);
timer.update(1.0);
expect(callbackCount, 1);
expect(onTickCount, 1);
});
test('finishes when complete if non-repeating', () {
final timer = Timer(1.0);
timer.start();
expect(timer.finished, false);
timer.update(1.1);
expect(timer.finished, true);
});
test('callback fires repeatedly if repeating', () {
var callbackCount = 0;
final timer = Timer(1.0, repeat: true, callback: () => callbackCount++);
timer.start();
test('onTick fires repeatedly if repeating', () {
var onTickCount = 0;
final timer = Timer(1.0, repeat: true, onTick: () => onTickCount++);
timer.update(0.9);
expect(callbackCount, 0);
expect(onTickCount, 0);
timer.update(0.2);
expect(callbackCount, 1);
expect(onTickCount, 1);
timer.update(1.0);
expect(callbackCount, 2);
expect(onTickCount, 2);
});
test('does not finish past limit if repeating', () {
final timer = Timer(1.0, repeat: true);
timer.start();
expect(timer.finished, false);
timer.update(1.1);
expect(timer.finished, false);

View File

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

View File

@ -45,7 +45,7 @@ class PlayerComponent extends SpriteAnimationComponent
PlayerComponent()
: 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());
}