mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-03 20:36:31 +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)
|
## Common for MoveEffect, ScaleEffect and RotateEffect (SimplePositionComponentEffects)
|
||||||
A common thing for `MoveEffect`, `ScaleEffect` and `RotateEffect` is that it takes `duration` and `speed` as arguments.
|
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
|
- Speed is the speed of the effect
|
||||||
- pixels/s for `MoveEffect`
|
- pixels/s for `MoveEffect`
|
||||||
- pixels/s for `ScaleEffect`
|
- pixels/s for `ScaleEffect`
|
||||||
|
|||||||
@ -43,10 +43,11 @@ class MyGame extends BaseGame with TapDetector {
|
|||||||
currentTap - Vector2(50, 20),
|
currentTap - Vector2(50, 20),
|
||||||
currentTap + Vector2.all(30),
|
currentTap + Vector2.all(30),
|
||||||
],
|
],
|
||||||
duration: 1.0,
|
duration: 5.0,
|
||||||
curve: Curves.linear,
|
curve: Curves.linear,
|
||||||
isInfinite: false,
|
isInfinite: true,
|
||||||
isAlternating: false,
|
isAlternating: false,
|
||||||
|
onComplete: () => print(DateTime.now()),
|
||||||
);
|
);
|
||||||
|
|
||||||
final scale = ScaleEffect(
|
final scale = ScaleEffect(
|
||||||
@ -58,20 +59,21 @@ class MyGame extends BaseGame with TapDetector {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final rotate = RotateEffect(
|
final rotate = RotateEffect(
|
||||||
radians: (dx + dy) % pi,
|
angle: (dx + dy) % pi,
|
||||||
duration: 3,
|
duration: 3,
|
||||||
curve: Curves.decelerate,
|
curve: Curves.decelerate,
|
||||||
isInfinite: false,
|
isInfinite: false,
|
||||||
isAlternating: false,
|
isAlternating: false,
|
||||||
);
|
);
|
||||||
|
|
||||||
final combination = CombinedEffect(
|
//final combination = CombinedEffect(
|
||||||
effects: [move, rotate, scale],
|
// effects: [move, rotate, scale],
|
||||||
isInfinite: false,
|
// isInfinite: false,
|
||||||
isAlternating: true,
|
// isAlternating: true,
|
||||||
offset: 0.5,
|
// offset: 0.5,
|
||||||
onComplete: () => print("onComplete callback"),
|
// onComplete: () => print("onComplete callback"),
|
||||||
);
|
//);
|
||||||
greenSquare.addEffect(move);
|
greenSquare.addEffect(move);
|
||||||
|
print(DateTime.now());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -60,7 +60,7 @@ class MyGame extends BaseGame with TapDetector {
|
|||||||
));
|
));
|
||||||
|
|
||||||
orangeSquare.addEffect(RotateEffect(
|
orangeSquare.addEffect(RotateEffect(
|
||||||
radians: (dx + dy) % (2 * pi),
|
angle: (dx + dy) % (2 * pi),
|
||||||
speed: 1.0, // Radians per second
|
speed: 1.0, // Radians per second
|
||||||
curve: Curves.easeInOut,
|
curve: Curves.easeInOut,
|
||||||
isInfinite: true,
|
isInfinite: true,
|
||||||
|
|||||||
@ -63,7 +63,7 @@ class MyGame extends BaseGame with TapDetector {
|
|||||||
);
|
);
|
||||||
|
|
||||||
final rotate = RotateEffect(
|
final rotate = RotateEffect(
|
||||||
radians: (dx + dy) % pi,
|
angle: (dx + dy) % pi,
|
||||||
duration: 0.8,
|
duration: 0.8,
|
||||||
curve: Curves.decelerate,
|
curve: Curves.decelerate,
|
||||||
isInfinite: false,
|
isInfinite: false,
|
||||||
|
|||||||
@ -73,10 +73,6 @@ abstract class ComponentEffect<T extends Component> {
|
|||||||
@mustCallSuper
|
@mustCallSuper
|
||||||
void initialize(T component) {
|
void initialize(T component) {
|
||||||
this.component = component;
|
this.component = component;
|
||||||
|
|
||||||
/// You need to set the travelTime during the initialization of the
|
|
||||||
/// extending effect
|
|
||||||
travelTime = null;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void dispose() => _isDisposed = true;
|
void dispose() => _isDisposed = true;
|
||||||
|
|||||||
@ -93,9 +93,10 @@ class MoveEffect extends SimplePositionComponentEffect {
|
|||||||
);
|
);
|
||||||
lastPosition = v;
|
lastPosition = v;
|
||||||
}
|
}
|
||||||
speed ??= pathLength / duration;
|
final double totalPathLength = isAlternating ? pathLength * 2 : pathLength;
|
||||||
duration ??= pathLength / speed;
|
speed ??= totalPathLength / duration;
|
||||||
travelTime = duration;
|
duration ??= totalPathLength / speed;
|
||||||
|
travelTime = isAlternating ? duration / 2 : duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -108,10 +109,10 @@ class MoveEffect extends SimplePositionComponentEffect {
|
|||||||
|
|
||||||
@override
|
@override
|
||||||
void update(double dt) {
|
void update(double dt) {
|
||||||
super.update(dt);
|
|
||||||
if (hasFinished()) {
|
if (hasFinished()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
super.update(dt);
|
||||||
_currentSubPath ??= _percentagePath.first;
|
_currentSubPath ??= _percentagePath.first;
|
||||||
if (!curveDirection.isNegative && _currentSubPath.endAt < curveProgress ||
|
if (!curveDirection.isNegative && _currentSubPath.endAt < curveProgress ||
|
||||||
curveDirection.isNegative && _currentSubPath.startAt > curveProgress) {
|
curveDirection.isNegative && _currentSubPath.startAt > curveProgress) {
|
||||||
|
|||||||
@ -4,12 +4,12 @@ import 'package:meta/meta.dart';
|
|||||||
import 'effects.dart';
|
import 'effects.dart';
|
||||||
|
|
||||||
class RotateEffect extends SimplePositionComponentEffect {
|
class RotateEffect extends SimplePositionComponentEffect {
|
||||||
double radians;
|
double angle;
|
||||||
double _startAngle;
|
double _startAngle;
|
||||||
double _delta;
|
double _delta;
|
||||||
|
|
||||||
RotateEffect({
|
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 duration, // How long it should take for completion
|
||||||
double speed, // The speed of rotation in radians/s
|
double speed, // The speed of rotation in radians/s
|
||||||
Curve curve,
|
Curve curve,
|
||||||
@ -32,10 +32,10 @@ class RotateEffect extends SimplePositionComponentEffect {
|
|||||||
void initialize(_comp) {
|
void initialize(_comp) {
|
||||||
super.initialize(_comp);
|
super.initialize(_comp);
|
||||||
if (!isAlternating) {
|
if (!isAlternating) {
|
||||||
endAngle = _comp.angle + radians;
|
endAngle = _comp.angle + angle;
|
||||||
}
|
}
|
||||||
_startAngle = component.angle;
|
_startAngle = component.angle;
|
||||||
_delta = isRelative ? radians : radians - _startAngle;
|
_delta = isRelative ? angle : angle - _startAngle;
|
||||||
speed ??= _delta / duration;
|
speed ??= _delta / duration;
|
||||||
duration ??= _delta / speed;
|
duration ??= _delta / speed;
|
||||||
travelTime = duration;
|
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