From f34d86db1e459fb5fe36b601631d6c1999fadf8c Mon Sep 17 00:00:00 2001 From: Erick Date: Tue, 13 Sep 2022 09:52:11 -0300 Subject: [PATCH] feat: Adding ClipComponent (#1769) Adds a new component called ClipComponent that clips the canvas area based on its size and shape. --- doc/flame/components.md | 23 ++- .../components/clip_component_example.dart | 87 +++++++++++ .../lib/stories/components/components.dart | 7 + packages/flame/lib/components.dart | 1 + .../lib/src/components/clip_component.dart | 139 ++++++++++++++++++ .../test/_goldens/clip_component_circle.png | Bin 0 -> 25996 bytes .../test/_goldens/clip_component_polygon.png | Bin 0 -> 21995 bytes .../test/_goldens/clip_component_rect.png | Bin 0 -> 20978 bytes .../test/components/clip_component_test.dart | 68 +++++++++ 9 files changed, 323 insertions(+), 2 deletions(-) create mode 100644 examples/lib/stories/components/clip_component_example.dart create mode 100644 packages/flame/lib/src/components/clip_component.dart create mode 100644 packages/flame/test/_goldens/clip_component_circle.png create mode 100644 packages/flame/test/_goldens/clip_component_polygon.png create mode 100644 packages/flame/test/_goldens/clip_component_rect.png create mode 100644 packages/flame/test/components/clip_component_test.dart diff --git a/doc/flame/components.md b/doc/flame/components.md index f498e5d46..91e671989 100644 --- a/doc/flame/components.md +++ b/doc/flame/components.md @@ -27,7 +27,7 @@ void main() { The `Component()` here could of course be any subclass of `Component`. Every `Component` has a few methods that you can optionally implement, which are used by the -`FlameGame` class. +`FlameGame` class. ### Component lifecycle @@ -201,7 +201,7 @@ an assertion error will be thrown. ### Ensuring a component has a given ancestor -When a component requires to have a specific ancestor type somewhere in the +When a component requires to have a specific ancestor type somewhere in the component tree, `HasAncestor` mixin can be used to enforce that relationship. The mixin exposes the `ancestor` field that will be of the given type. @@ -989,6 +989,25 @@ Check the example app [custom_painter_component](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/widgets/custom_painter_example.dart) for details on how to use it. +## ClipComponent + +A `ClipComponent` is a component that will clip the canvas to its size and shape. This means that +if the component itself or any child of the `ClipComponent` renders outside of the +`ClipComponent`'s boundaries, the part that is not inside the area will not be shown. + +A `ClipComponent` receives a builder function that should return the `Shape` that will define the +clipped area, based on its size. + +To make it easier to use that component, there are three factories that offers common shapes: + + - `ClipComponent.rectangle`: Clips the area in the form a rectangle based on its size. + - `ClipComponent.circle`: Clips the area in the form of a circle based on its size. + - `ClipComponent.polygon`: Clips the area in the form of a polygon based on the points received +in the constructor. + +Check the example app +[clip_component](https://github.com/flame-engine/flame/blob/main/examples/lib/stories/components/clip_component_example.dart) +for details on how to use it. ## Effects diff --git a/examples/lib/stories/components/clip_component_example.dart b/examples/lib/stories/components/clip_component_example.dart new file mode 100644 index 000000000..bf05f4a96 --- /dev/null +++ b/examples/lib/stories/components/clip_component_example.dart @@ -0,0 +1,87 @@ +import 'dart:math'; +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/effects.dart'; +import 'package:flame/game.dart'; +import 'package:flame/input.dart'; +import 'package:flutter/material.dart' hide Gradient; + +class _Rectangle extends RectangleComponent { + _Rectangle() + : super( + size: Vector2(200, 200), + anchor: Anchor.center, + paint: Paint() + ..shader = Gradient.linear( + Offset.zero, + const Offset(0, 100), + [Colors.orange, Colors.blue], + ), + children: [ + SequenceEffect( + [ + RotateEffect.by( + pi * 2, + LinearEffectController(.4), + ), + RotateEffect.by( + 0, + LinearEffectController(.4), + ), + ], + infinite: true, + ), + ], + ); +} + +class ClipComponentExample extends FlameGame with TapDetector { + static String description = 'Tap on the objects to increase their size.'; + + @override + Future onLoad() async { + addAll( + [ + ClipComponent.circle( + position: Vector2(100, 100), + size: Vector2.all(50), + children: [_Rectangle()], + ), + ClipComponent.rectangle( + position: Vector2(200, 100), + size: Vector2.all(50), + children: [_Rectangle()], + ), + ClipComponent.polygon( + points: [ + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1), + Vector2(1, 0), + ], + position: Vector2(200, 200), + size: Vector2.all(50), + children: [_Rectangle()], + ), + ], + ); + } + + @override + void onTapUp(TapUpInfo info) { + final position = info.eventPosition.game; + final hit = children + .whereType() + .where( + (component) => component.containsLocalPoint( + position - component.position, + ), + ) + .toList(); + + hit.forEach((component) { + component.size += Vector2.all(10); + }); + } +} diff --git a/examples/lib/stories/components/components.dart b/examples/lib/stories/components/components.dart index d9204d429..491330c5d 100644 --- a/examples/lib/stories/components/components.dart +++ b/examples/lib/stories/components/components.dart @@ -1,5 +1,6 @@ import 'package:dashbook/dashbook.dart'; import 'package:examples/commons/commons.dart'; +import 'package:examples/stories/components/clip_component_example.dart'; import 'package:examples/stories/components/composability_example.dart'; import 'package:examples/stories/components/debug_example.dart'; import 'package:examples/stories/components/game_in_game_example.dart'; @@ -31,5 +32,11 @@ void addComponentsStories(Dashbook dashbook) { (_) => GameWidget(game: GameInGameExample()), codeLink: baseLink('components/game_in_game_example.dart'), info: GameInGameExample.description, + ) + ..add( + 'ClipComponent', + (context) => GameWidget(game: ClipComponentExample()), + codeLink: baseLink('components/clip_component_example.dart'), + info: ClipComponentExample.description, ); } diff --git a/packages/flame/lib/components.dart b/packages/flame/lib/components.dart index 6ffb08966..41909b48e 100644 --- a/packages/flame/lib/components.dart +++ b/packages/flame/lib/components.dart @@ -2,6 +2,7 @@ export 'src/anchor.dart'; export 'src/collisions/has_collision_detection.dart'; export 'src/collisions/hitboxes/screen_hitbox.dart'; +export 'src/components/clip_component.dart'; export 'src/components/core/component.dart'; export 'src/components/core/component_set.dart'; export 'src/components/core/position_type.dart'; diff --git a/packages/flame/lib/src/components/clip_component.dart b/packages/flame/lib/src/components/clip_component.dart new file mode 100644 index 000000000..7d28dc1da --- /dev/null +++ b/packages/flame/lib/src/components/clip_component.dart @@ -0,0 +1,139 @@ +import 'dart:ui'; + +import 'package:flame/components.dart'; +import 'package:flame/experimental.dart'; + +/// A function that creates a shape based on a size represented by a [Vector2] +typedef ShapeBuilder = Shape Function(Vector2 size); + +/// {@template clip_component} +/// A component that will clip its content. +/// {@endtemplate} +class ClipComponent extends PositionComponent { + /// {@macro clip_component} + /// + /// Clips the canvas based its shape and size. + ClipComponent({ + required ShapeBuilder builder, + super.position, + super.size, + super.scale, + super.angle, + super.anchor, + super.children, + super.priority, + }) : _builder = builder; + + /// {@macro circle_clip_component} + /// + /// Clips the canvas in the form of a circle based on its size. + factory ClipComponent.circle({ + Vector2? position, + Vector2? size, + Vector2? scale, + double? angle, + Anchor? anchor, + Iterable? children, + int? priority, + }) { + return ClipComponent( + builder: (size) => Circle(size / 2, size.x / 2), + position: position, + size: size, + scale: scale, + angle: angle, + anchor: anchor, + children: children, + priority: priority, + ); + } + + /// {@macro rectangle_clip_component} + /// + /// Clips the canvas in the form of a rectangle based on its size. + factory ClipComponent.rectangle({ + Vector2? position, + Vector2? size, + Vector2? scale, + double? angle, + Anchor? anchor, + Iterable? children, + int? priority, + }) { + return ClipComponent( + builder: (size) => Rectangle.fromRect(size.toRect()), + position: position, + size: size, + scale: scale, + angle: angle, + anchor: anchor, + children: children, + priority: priority, + ); + } + + /// {@macro polygon_clip_component} + /// + /// Clips the canvas in the form of a polygon based on its size. + factory ClipComponent.polygon({ + required List points, + Vector2? position, + Vector2? size, + Vector2? scale, + double? angle, + Anchor? anchor, + Iterable? children, + int? priority, + }) { + assert( + points.length > 2, + 'PolygonClipComponent requires at least 3 points.', + ); + + return ClipComponent( + builder: (size) { + final translatedPoints = points + .map( + (p) => p.clone()..multiply(size), + ) + .toList(); + return Polygon(translatedPoints); + }, + position: position, + size: size, + scale: scale, + angle: angle, + anchor: anchor, + children: children, + priority: priority, + ); + } + + late Path _path; + late Shape _shape; + final ShapeBuilder _builder; + + @override + Future onLoad() async { + _prepare(); + size.addListener(_prepare); + } + + void _prepare() { + _shape = _builder(size); + _path = _shape.asPath(); + } + + @override + void render(Canvas canvas) => canvas.clipPath(_path); + + @override + bool containsPoint(Vector2 point) { + return _shape.containsPoint(point - position); + } + + @override + bool containsLocalPoint(Vector2 point) { + return _shape.containsPoint(point); + } +} diff --git a/packages/flame/test/_goldens/clip_component_circle.png b/packages/flame/test/_goldens/clip_component_circle.png new file mode 100644 index 0000000000000000000000000000000000000000..cb121386169e186095699df6f6aa8e437b7e7f22 GIT binary patch literal 25996 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 literal 0 HcmV?d00001 diff --git a/packages/flame/test/components/clip_component_test.dart b/packages/flame/test/components/clip_component_test.dart new file mode 100644 index 000000000..86d6589e7 --- /dev/null +++ b/packages/flame/test/components/clip_component_test.dart @@ -0,0 +1,68 @@ +import 'package:flame/components.dart'; +import 'package:flame_test/flame_test.dart'; +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; + +class _Rectangle extends RectangleComponent { + _Rectangle() + : super( + size: Vector2(200, 200), + anchor: Anchor.center, + paint: Paint()..color = Colors.blue, + ); +} + +void main() { + group('ClipComponent', () { + group('RectangleClipComponent', () { + testGolden( + 'renders correctly', + (game) async { + await game.add( + ClipComponent.rectangle( + size: Vector2(100, 100), + children: [_Rectangle()], + ), + ); + }, + goldenFile: '../_goldens/clip_component_rect.png', + ); + }); + + group('CircleClipComponent', () { + testGolden( + 'renders correctly', + (game) async { + await game.add( + ClipComponent.circle( + size: Vector2(100, 100), + children: [_Rectangle()], + ), + ); + }, + goldenFile: '../_goldens/clip_component_circle.png', + ); + }); + + group('PolygonClipComponent', () { + testGolden( + 'renders correctly', + (game) async { + await game.add( + ClipComponent.polygon( + points: [ + Vector2(1, 0), + Vector2(1, 1), + Vector2(0, 1), + Vector2(1, 0), + ], + size: Vector2(100, 100), + children: [_Rectangle()], + ), + ); + }, + goldenFile: '../_goldens/clip_component_polygon.png', + ); + }); + }); +}