Skip to content

Commit

Permalink
Fix render order of components and add tests (#1148)
Browse files Browse the repository at this point in the history
  • Loading branch information
luanpotter committed Dec 2, 2021
1 parent ca8f8d3 commit aec2607
Show file tree
Hide file tree
Showing 6 changed files with 172 additions and 62 deletions.
1 change: 1 addition & 0 deletions packages/flame/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
- Fixed position calculation in `HudMarginComponent` when using a viewport
- Add noClip option to `FixedResolutionViewport`
- Add a few missing helpers to SpriteAnimation
- Fix render order of components and add tests

## [1.0.0-releasecandidate.17]
- Added `StandardEffectController` class
Expand Down
34 changes: 12 additions & 22 deletions packages/flame/lib/src/game/camera/camera_wrapper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -22,33 +22,23 @@ class CameraWrapper {
// TODO(st-pasha): it would be easier to keep the world and the
// HUD as two separate component trees.
camera.viewport.render(canvas, (_canvas) {
// First render regular world objects
canvas.save();
camera.apply(canvas);
var hasCamera = false; // so we don't apply unecessary transformations
world.forEach((component) {
if (!component.isHud) {
// TODO(st-pasha): refactor [ParallaxComponent] so that it
// wouldn't require any camera hacks.
if (component is ParallaxComponent) {
canvas.restore();
}
if (!component.isHud && !hasCamera) {
canvas.save();
component.renderTree(canvas);
canvas.restore();
if (component is ParallaxComponent) {
camera.apply(canvas);
}
}
});
canvas.restore();
// Then render the HUD
world.forEach((component) {
if (component.isHud) {
canvas.save();
component.renderTree(canvas);
camera.apply(canvas);
hasCamera = true;
} else if (component.isHud && hasCamera) {
canvas.restore();
hasCamera = false;
}
canvas.save();
component.renderTree(canvas);
canvas.restore();
});
if (hasCamera) {
canvas.restore();
}
});
}
}
2 changes: 1 addition & 1 deletion packages/flame/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ dev_dependencies:
test: ^1.17.10
dartdoc: ^0.42.0
mocktail: ^0.1.4
canvas_test: ^0.1.1
canvas_test: ^0.2.0
flame_test:
path: ../flame_test
flame_lint:
Expand Down
6 changes: 4 additions & 2 deletions packages/flame/test/components/position_component_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -632,7 +632,8 @@ void main() {
..drawLine(const Offset(0, -2), const Offset(0, 2))
..drawLine(const Offset(-2, 0), const Offset(2, 0))
..drawParagraph(null, const Offset(-30, -15))
..drawParagraph(null, const Offset(-20, 10)),
..drawParagraph(null, const Offset(-20, 10))
..translate(0, 0), // canvas.restore
);
});

Expand All @@ -650,7 +651,8 @@ void main() {
..translate(18, 12)
..drawRect(const Rect.fromLTWH(0, 0, 10, 10))
..drawLine(const Offset(5, 3), const Offset(5, 7))
..drawLine(const Offset(3, 5), const Offset(7, 5)),
..drawLine(const Offset(3, 5), const Offset(7, 5))
..translate(0, 0), // canvas.restore
);
});
});
Expand Down
83 changes: 46 additions & 37 deletions packages/flame/test/game/camera_and_viewport_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ void main() {
expect(game.size, Vector2(100.0, 200.00));
});

flameGame.test('fixed ratio viewport has perfect ratio', (game) {
flameGame.test('fixed ratio viewport has perfect ratio', (game) async {
game.camera.viewport = FixedResolutionViewport(Vector2.all(50));
game.onGameResize(Vector2.all(200.0));
expect(game.canvasSize, Vector2.all(200.00));
Expand All @@ -41,17 +41,21 @@ void main() {
expect(viewport.scaledSize, Vector2(200.0, 200.0));
expect(viewport.scale, 4.0);

await game.ensureAdd(_TestComponent(Vector2.zero()));

final canvas = MockCanvas();
game.render(canvas);
expect(
canvas,
MockCanvas()
..clipRect(const Rect.fromLTWH(0, 0, 200, 200))
..scale(4),
..scale(4)
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});

flameGame.test('fixed ratio viewport maxes width', (game) {
flameGame.test('fixed ratio viewport maxes width', (game) async {
game.camera.viewport = FixedResolutionViewport(Vector2.all(50));
game.onGameResize(Vector2(100.0, 200.0));
expect(game.canvasSize, Vector2(100.0, 200.00));
Expand All @@ -62,18 +66,22 @@ void main() {
expect(viewport.scaledSize, Vector2(100.0, 100.0));
expect(viewport.scale, 2.0);

await game.ensureAdd(_TestComponent(Vector2.zero()));

final canvas = MockCanvas();
game.render(canvas);
expect(
canvas,
MockCanvas()
..clipRect(const Rect.fromLTWH(0, 50, 100, 100))
..translate(0, 50)
..scale(2),
..scale(2)
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});

flameGame.test('fixed ratio viewport maxes height', (game) {
flameGame.test('fixed ratio viewport maxes height', (game) async {
game.camera.viewport = FixedResolutionViewport(Vector2(100.0, 400.0));
game.onGameResize(Vector2(100.0, 200.0));
expect(game.canvasSize, Vector2(100.0, 200.00));
Expand All @@ -84,44 +92,45 @@ void main() {
expect(viewport.scaledSize, Vector2(50.0, 200.0));
expect(viewport.scale, 0.5);

await game.ensureAdd(_TestComponent(Vector2.zero()));

final canvas = MockCanvas();
game.render(canvas);
expect(
canvas,
MockCanvas()
..clipRect(const Rect.fromLTWH(25, 0, 50, 200))
..translate(25, 0)
..scale(0.5),
..scale(0.5)
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});
});

group('camera', () {
flameGame.test('default camera applies no translation', (game) {
flameGame.test('default camera applies no translation', (game) async {
game.onGameResize(Vector2.all(100.0));
expect(game.camera.position, Vector2.zero());

final p = _TestComponent(Vector2.all(10.0));
game.add(p);
game.update(0);
await game.ensureAdd(_TestComponent(Vector2.all(10.0)));

final canvas = MockCanvas();
game.render(canvas);
expect(
canvas,
MockCanvas()
..translate(10, 10)
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});

flameGame.test('camera snap movement', (game) {
flameGame.test('camera snap movement', (game) async {
game.onGameResize(Vector2.all(100.0));
expect(game.camera.position, Vector2.zero());

final p = _TestComponent(Vector2.all(10.0));
game.add(p);
game.update(0);
await game.ensureAdd(_TestComponent(Vector2.all(10.0)));

// this puts the top left of the screen on (4,4)
game.camera.moveTo(Vector2.all(4.0));
Expand All @@ -136,7 +145,8 @@ void main() {
MockCanvas()
..translate(-4, -4) // Camera translation
..translate(10, 10) // PositionComponent translation
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});

Expand All @@ -156,12 +166,11 @@ void main() {
expect(game.camera.position, Vector2(0.0, 10.0));
});

flameGame.test('camera follow', (game) {
flameGame.test('camera follow', (game) async {
game.onGameResize(Vector2.all(100.0));

final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center;
game.add(p);
game.update(0);
await game.ensureAdd(p);
game.camera.followComponent(p);

expect(game.camera.position, Vector2.all(0.0));
Expand All @@ -178,17 +187,17 @@ void main() {
MockCanvas()
..translate(40, 30) // Camera translation
..translate(9.5, 19.5) // PositionComponent translation
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
// result: 50 - w/2, 50 - h/2 (perfectly centered)
);
});

flameGame.test('camera follow with relative position', (game) {
flameGame.test('camera follow with relative position', (game) async {
game.onGameResize(Vector2.all(100.0));

final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center;
game.add(p);
game.update(0);
await game.ensureAdd(p);
// this would be a typical vertical shoot-em-up
game.camera.followComponent(p, relativeOffset: const Anchor(0.5, 0.8));

Expand All @@ -206,15 +215,15 @@ void main() {
MockCanvas()
..translate(-550, -1920) // Camera translation
..translate(599.5, 1999.5) // PositionComponent translation
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});
flameGame.test('camera follow with world boundaries', (game) {
flameGame.test('camera follow with world boundaries', (game) async {
game.onGameResize(Vector2.all(100.0));

final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center;
game.add(p);
game.update(0);
await game.ensureAdd(p);
game.camera.followComponent(
p,
worldBounds: const Rect.fromLTWH(-1000, -1000, 2000, 2000),
Expand Down Expand Up @@ -244,12 +253,11 @@ void main() {

flameGame.test(
'camera follow with world boundaries smaller than the screen',
(game) {
(game) async {
game.onGameResize(Vector2.all(200.0));

final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center;
game.add(p);
game.update(0);
await game.ensureAdd(p);
game.camera.followComponent(
p,
worldBounds: const Rect.fromLTWH(0, 0, 100, 100),
Expand Down Expand Up @@ -281,13 +289,12 @@ void main() {
expect(game.camera.position, Vector2.all(-100.0));
});

flameGame.test('camera zoom', (game) {
flameGame.test('camera zoom', (game) async {
game.onGameResize(Vector2.all(200.0));
game.camera.zoom = 2;

final p = _TestComponent(Vector2.all(100.0))..anchor = Anchor.center;
game.add(p);
game.update(0);
await game.ensureAdd(p);

final canvas = MockCanvas();
game.render(canvas);
Expand All @@ -296,7 +303,8 @@ void main() {
MockCanvas()
..scale(2) // Camera zoom
..translate(99.5, 99.5) // PositionComponent translation
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
});

Expand All @@ -317,7 +325,8 @@ void main() {
..translate(100, 100) // camera translation
..scale(2) // camera zoom
..translate(99.5, 99.5) // position component
..drawRect(const Rect.fromLTWH(0, 0, 1, 1)),
..drawRect(const Rect.fromLTWH(0, 0, 1, 1))
..translate(0, 0), // reset camera
);
expect(game.camera.position, Vector2.all(-50.0));
});
Expand All @@ -337,15 +346,15 @@ void main() {
group('viewport & camera', () {
flameGame.test(
'default ratio viewport + camera with world boundaries',
(game) {
(game) async {
final game = FlameGame()
..camera.viewport = FixedResolutionViewport(Vector2.all(100));
game.onGameResize(Vector2.all(200.0));
expect(game.canvasSize, Vector2.all(200.00));
expect(game.size, Vector2.all(100.00));

final p = _TestComponent(Vector2.all(10.0))..anchor = Anchor.center;
game.add(p);
await game.ensureAdd(p);
game.camera.followComponent(
p,
// this could be a typical mario-like platformer, where the player is
Expand Down
Loading

0 comments on commit aec2607

Please sign in to comment.