feat: Adding ClipComponent (#1769)

Adds a new component called ClipComponent that clips the canvas area based on its size and shape.
This commit is contained in:
Erick
2022-09-13 09:52:11 -03:00
committed by GitHub
parent 96be840899
commit f34d86db1e
9 changed files with 323 additions and 2 deletions

View File

@ -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

View File

@ -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<void> 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<PositionComponent>()
.where(
(component) => component.containsLocalPoint(
position - component.position,
),
)
.toList();
hit.forEach((component) {
component.size += Vector2.all(10);
});
}
}

View File

@ -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,
);
}

View File

@ -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';

View File

@ -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<Component>? 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<Component>? 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<Vector2> points,
Vector2? position,
Vector2? size,
Vector2? scale,
double? angle,
Anchor? anchor,
Iterable<Component>? 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<void> 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);
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 22 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 20 KiB

View File

@ -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',
);
});
});
}