mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-02 20:13:50 +08:00
Generalize position effect tests
This commit is contained in:
@ -23,7 +23,7 @@ When an effect is completed the callback `onComplete` will be called, it can be
|
||||
## Common for MoveEffect, ScaleEffect and RotateEffect (SimplePositionComponentEffects)
|
||||
A common thing for `MoveEffect`, `ScaleEffect` and `RotateEffect` is that it takes `duration` and `speed` as arguments.
|
||||
|
||||
- Duration means the time it takes for one iteration from beginning to end without alternation
|
||||
- Duration means the time it takes for one iteration from beginning to end, with alternation taken into account (but not `isInfinite`).
|
||||
- Speed is the speed of the effect
|
||||
- pixels/s for `MoveEffect`
|
||||
- pixels/s for `ScaleEffect`
|
||||
|
||||
@ -43,10 +43,11 @@ class MyGame extends BaseGame with TapDetector {
|
||||
currentTap - Vector2(50, 20),
|
||||
currentTap + Vector2.all(30),
|
||||
],
|
||||
duration: 1.0,
|
||||
duration: 5.0,
|
||||
curve: Curves.linear,
|
||||
isInfinite: false,
|
||||
isInfinite: true,
|
||||
isAlternating: false,
|
||||
onComplete: () => print(DateTime.now()),
|
||||
);
|
||||
|
||||
final scale = ScaleEffect(
|
||||
@ -58,20 +59,21 @@ class MyGame extends BaseGame with TapDetector {
|
||||
);
|
||||
|
||||
final rotate = RotateEffect(
|
||||
radians: (dx + dy) % pi,
|
||||
angle: (dx + dy) % pi,
|
||||
duration: 3,
|
||||
curve: Curves.decelerate,
|
||||
isInfinite: false,
|
||||
isAlternating: false,
|
||||
);
|
||||
|
||||
final combination = CombinedEffect(
|
||||
effects: [move, rotate, scale],
|
||||
isInfinite: false,
|
||||
isAlternating: true,
|
||||
offset: 0.5,
|
||||
onComplete: () => print("onComplete callback"),
|
||||
);
|
||||
//final combination = CombinedEffect(
|
||||
// effects: [move, rotate, scale],
|
||||
// isInfinite: false,
|
||||
// isAlternating: true,
|
||||
// offset: 0.5,
|
||||
// onComplete: () => print("onComplete callback"),
|
||||
//);
|
||||
greenSquare.addEffect(move);
|
||||
print(DateTime.now());
|
||||
}
|
||||
}
|
||||
|
||||
@ -60,7 +60,7 @@ class MyGame extends BaseGame with TapDetector {
|
||||
));
|
||||
|
||||
orangeSquare.addEffect(RotateEffect(
|
||||
radians: (dx + dy) % (2 * pi),
|
||||
angle: (dx + dy) % (2 * pi),
|
||||
speed: 1.0, // Radians per second
|
||||
curve: Curves.easeInOut,
|
||||
isInfinite: true,
|
||||
|
||||
@ -63,7 +63,7 @@ class MyGame extends BaseGame with TapDetector {
|
||||
);
|
||||
|
||||
final rotate = RotateEffect(
|
||||
radians: (dx + dy) % pi,
|
||||
angle: (dx + dy) % pi,
|
||||
duration: 0.8,
|
||||
curve: Curves.decelerate,
|
||||
isInfinite: false,
|
||||
|
||||
@ -73,10 +73,6 @@ abstract class ComponentEffect<T extends Component> {
|
||||
@mustCallSuper
|
||||
void initialize(T component) {
|
||||
this.component = component;
|
||||
|
||||
/// You need to set the travelTime during the initialization of the
|
||||
/// extending effect
|
||||
travelTime = null;
|
||||
}
|
||||
|
||||
void dispose() => _isDisposed = true;
|
||||
|
||||
@ -93,9 +93,10 @@ class MoveEffect extends SimplePositionComponentEffect {
|
||||
);
|
||||
lastPosition = v;
|
||||
}
|
||||
speed ??= pathLength / duration;
|
||||
duration ??= pathLength / speed;
|
||||
travelTime = duration;
|
||||
final double totalPathLength = isAlternating ? pathLength * 2 : pathLength;
|
||||
speed ??= totalPathLength / duration;
|
||||
duration ??= totalPathLength / speed;
|
||||
travelTime = isAlternating ? duration / 2 : duration;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -108,10 +109,10 @@ class MoveEffect extends SimplePositionComponentEffect {
|
||||
|
||||
@override
|
||||
void update(double dt) {
|
||||
super.update(dt);
|
||||
if (hasFinished()) {
|
||||
return;
|
||||
}
|
||||
super.update(dt);
|
||||
_currentSubPath ??= _percentagePath.first;
|
||||
if (!curveDirection.isNegative && _currentSubPath.endAt < curveProgress ||
|
||||
curveDirection.isNegative && _currentSubPath.startAt > curveProgress) {
|
||||
|
||||
@ -4,12 +4,12 @@ import 'package:meta/meta.dart';
|
||||
import 'effects.dart';
|
||||
|
||||
class RotateEffect extends SimplePositionComponentEffect {
|
||||
double radians;
|
||||
double angle;
|
||||
double _startAngle;
|
||||
double _delta;
|
||||
|
||||
RotateEffect({
|
||||
@required this.radians, // As many radians as you want to rotate
|
||||
@required this.angle, // As many radians as you want to rotate
|
||||
double duration, // How long it should take for completion
|
||||
double speed, // The speed of rotation in radians/s
|
||||
Curve curve,
|
||||
@ -32,10 +32,10 @@ class RotateEffect extends SimplePositionComponentEffect {
|
||||
void initialize(_comp) {
|
||||
super.initialize(_comp);
|
||||
if (!isAlternating) {
|
||||
endAngle = _comp.angle + radians;
|
||||
endAngle = _comp.angle + angle;
|
||||
}
|
||||
_startAngle = component.angle;
|
||||
_delta = isRelative ? radians : radians - _startAngle;
|
||||
_delta = isRelative ? angle : angle - _startAngle;
|
||||
speed ??= _delta / duration;
|
||||
duration ??= _delta / speed;
|
||||
travelTime = duration;
|
||||
|
||||
89
test/effects/move_effect_test.dart
Normal file
89
test/effects/move_effect_test.dart
Normal file
@ -0,0 +1,89 @@
|
||||
import 'dart:math';
|
||||
|
||||
import 'package:flame/components/position_component.dart';
|
||||
import 'package:flame/effects/effects.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import 'effect_test_utils.dart';
|
||||
|
||||
void main() {
|
||||
final Random random = Random();
|
||||
Vector2 randomVector2() => (Vector2.random(random) * 100)..round();
|
||||
final List<Vector2> path = List.generate(3, (i) => randomVector2());
|
||||
Square component() => Square(position: randomVector2());
|
||||
|
||||
MoveEffect effect(bool isInfinite, bool isAlternating) {
|
||||
return MoveEffect(
|
||||
path: path,
|
||||
duration: random.nextDouble() * 10,
|
||||
isInfinite: isInfinite,
|
||||
isAlternating: isAlternating,
|
||||
);
|
||||
}
|
||||
|
||||
testWidgets('MoveEffect can move', (WidgetTester tester) async {
|
||||
final MoveEffect moveEffect = effect(false, false);
|
||||
effectTest(
|
||||
tester,
|
||||
component(),
|
||||
moveEffect,
|
||||
expectedPosition: path.last,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets(
|
||||
'MoveEffect will stop moving after it is done',
|
||||
(WidgetTester tester) async {
|
||||
final MoveEffect moveEffect = effect(false, false);
|
||||
effectTest(
|
||||
tester,
|
||||
component(),
|
||||
moveEffect,
|
||||
expectedPosition: path.last,
|
||||
iterations: 1.5,
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
testWidgets('MoveEffect can alternate', (WidgetTester tester) async {
|
||||
final MoveEffect moveEffect = effect(false, true);
|
||||
final PositionComponent positionComponent = component();
|
||||
effectTest(
|
||||
tester,
|
||||
positionComponent,
|
||||
moveEffect,
|
||||
expectedPosition: positionComponent.position.clone(),
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('MoveEffect alternation can peak', (WidgetTester tester) async {
|
||||
final MoveEffect moveEffect = effect(false, true);
|
||||
final PositionComponent positionComponent = component();
|
||||
print(path);
|
||||
print(positionComponent.position);
|
||||
effectTest(
|
||||
tester,
|
||||
positionComponent,
|
||||
moveEffect,
|
||||
expectedPosition: path.last,
|
||||
hitEdges: true,
|
||||
iterations: 1.4,
|
||||
);
|
||||
});
|
||||
|
||||
testWidgets('MoveEffect can be infinite', (WidgetTester tester) async {
|
||||
final MoveEffect moveEffect = effect(true, false);
|
||||
final PositionComponent positionComponent = component();
|
||||
print(path);
|
||||
print(positionComponent.position);
|
||||
effectTest(
|
||||
tester,
|
||||
positionComponent,
|
||||
moveEffect,
|
||||
expectedPosition: path.last,
|
||||
iterations: 1.0,
|
||||
hasFinished: false,
|
||||
);
|
||||
});
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
import 'dart:math';
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/anchor.dart';
|
||||
import 'package:flame/components/position_component.dart';
|
||||
import 'package:flame/effects/effects.dart';
|
||||
import 'package:flame/extensions/vector2.dart';
|
||||
import 'package:flame/game.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
void main() {
|
||||
final Random random = Random();
|
||||
Vector2 randomVector2() => (Vector2.random(random) * 100)..round();
|
||||
final List<Vector2> path = List.generate(3, (i) => randomVector2());
|
||||
|
||||
void moveEffectTest(
|
||||
WidgetTester tester,
|
||||
bool isInfinite,
|
||||
bool isAlternating,
|
||||
Vector2 expected, {
|
||||
bool hasFinished = true,
|
||||
double iterations = 1.0,
|
||||
}) async {
|
||||
final BaseGame game = BaseGame();
|
||||
final Vector2 position = randomVector2();
|
||||
final PositionComponent component = Square(position);
|
||||
final double duration = random.nextDouble() * 10;
|
||||
bool isCallbackCalled = false;
|
||||
final MoveEffect effect = MoveEffect(
|
||||
path: path,
|
||||
duration: duration,
|
||||
isInfinite: isInfinite,
|
||||
isAlternating: isAlternating,
|
||||
onComplete: () => isCallbackCalled = true,
|
||||
);
|
||||
game.add(component);
|
||||
component.addEffect(effect);
|
||||
await tester.pumpWidget(game.widget);
|
||||
double timeLeft = iterations * duration;
|
||||
while(timeLeft > 0) {
|
||||
final double stepDelta = random.nextDouble() / 10;
|
||||
game.update(stepDelta);
|
||||
timeLeft -= stepDelta;
|
||||
}
|
||||
expect(component.position, expected);
|
||||
expect(effect.hasFinished(), hasFinished);
|
||||
expect(isCallbackCalled, hasFinished);
|
||||
game.update(0.0001);
|
||||
expect(component.effects.isEmpty, hasFinished);
|
||||
}
|
||||
|
||||
testWidgets('MoveEffect can move', (WidgetTester tester) async {
|
||||
moveEffectTest(tester, false, false, path.last);
|
||||
});
|
||||
|
||||
testWidgets('MoveEffect can alternate', (WidgetTester tester) async {
|
||||
print(path);
|
||||
moveEffectTest(tester, false, true, path.first, iterations: 2.00);
|
||||
});
|
||||
|
||||
testWidgets('MoveEffect can be infinite', (WidgetTester tester) async {
|
||||
print(path);
|
||||
moveEffectTest(
|
||||
tester,
|
||||
true,
|
||||
false,
|
||||
path.last,
|
||||
iterations: 2,
|
||||
hasFinished: false,
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
class Square extends PositionComponent {
|
||||
Square(Vector2 position, {double angle = 0.0}) {
|
||||
size = Vector2.all(100.0);
|
||||
this.position = position;
|
||||
this.angle = angle;
|
||||
anchor = Anchor.center;
|
||||
}
|
||||
|
||||
@override
|
||||
void render(Canvas canvas) {
|
||||
super.render(canvas);
|
||||
canvas.drawRect(size.toRect(), Paint()..color = Colors.red);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user