Skip to content

Commit

Permalink
feat: Add paint layers to HasPaint and associated component renders (#…
Browse files Browse the repository at this point in the history
  • Loading branch information
mattmyne authored Oct 24, 2022
1 parent 03e1f33 commit 9e6bf4f
Show file tree
Hide file tree
Showing 7 changed files with 250 additions and 106 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@ class Ball extends CircleComponent
static const degree = math.pi / 180;

@override
Future<void>? onLoad() {
Future<void> onLoad() async {
super.onLoad();
_resetBall;
final hitBox = CircleHitbox(
radius: radius,
Expand All @@ -43,8 +44,6 @@ class Ball extends CircleComponent
addAll([
hitBox,
]);

return super.onLoad();
}

@override
Expand Down
70 changes: 53 additions & 17 deletions packages/flame/lib/src/components/mixins/has_paint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,21 +4,22 @@ import 'dart:ui';
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/src/palette.dart';
import 'package:meta/meta.dart';

/// Adds a collection of paints to a component
/// Adds a collection of paints and paint layers to a component
///
/// Component will always have a main Paint that can be accessed
/// by the [paint] attribute and other paints can be manipulated/accessed
/// using [getPaint], [setPaint] and [deletePaint] by a paintId of generic type
/// [T], that can be omitted if the component only have one paint.
/// [T], that can be omitted if the component only has one paint.
/// [paintLayers] paints should be drawn in list order during the render. The
/// main Paint is the first element.
mixin HasPaint<T extends Object> on Component implements OpacityProvider {
final Map<T, Paint> _paints = {};

late final Map<T, Paint> _paints = {};
Paint paint = BasicPalette.white.paint();

void _assertGenerics() {
assert(T != Object, 'A generics type is missing on the HasPaint mixin');
}
@internal
List<Paint>? paintLayersInternal;

/// Gets a paint from the collection.
///
Expand All @@ -28,7 +29,6 @@ mixin HasPaint<T extends Object> on Component implements OpacityProvider {
return paint;
}

_assertGenerics();
final _paint = _paints[paintId];

if (_paint == null) {
Expand All @@ -40,16 +40,29 @@ mixin HasPaint<T extends Object> on Component implements OpacityProvider {

/// Sets a paint on the collection.
void setPaint(T paintId, Paint paint) {
_assertGenerics();
_paints[paintId] = paint;
}

/// Removes a paint from the collection.
void deletePaint(T paintId) {
_assertGenerics();
_paints.remove(paintId);
}

/// List of paints to use (in order) during render.
List<Paint> get paintLayers {
if (!hasPaintLayers) {
return paintLayersInternal = [];
}
return paintLayersInternal!;
}

set paintLayers(List<Paint> paintLayers) {
paintLayersInternal = paintLayers;
}

/// Whether there are any paint layers defined for the component.
bool get hasPaintLayers => paintLayersInternal?.isNotEmpty ?? false;

/// Manipulate the paint to make it fully transparent.
void makeTransparent({T? paintId}) {
setOpacity(0, paintId: paintId);
Expand Down Expand Up @@ -130,9 +143,13 @@ mixin HasPaint<T extends Object> on Component implements OpacityProvider {
///
/// Note: Each call results in a new [OpacityProvider] and hence the cached
/// opacity ratios are calculated using opacities when this method was called.
OpacityProvider opacityProviderOfList({List<T?>? paintIds}) {
OpacityProvider opacityProviderOfList({
List<T?>? paintIds,
bool includeLayers = true,
}) {
return _MultiPaintOpacityProvider(
paintIds ?? (List<T?>.from(_paints.keys)..add(null)),
includeLayers,
this,
);
}
Expand All @@ -152,19 +169,25 @@ class _ProxyOpacityProvider<T extends Object> implements OpacityProvider {
}

class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
_MultiPaintOpacityProvider(this.paintIds, this.target) {
_MultiPaintOpacityProvider(this.paintIds, this.includeLayers, this.target) {
final maxOpacity = opacity;

_opacityRatios = List<double>.generate(
paintIds.length,
(index) =>
target.getOpacity(paintId: paintIds.elementAt(index)) / maxOpacity,
);
_opacityRatios = [
for (final paintId in paintIds)
target.getOpacity(paintId: paintId) / maxOpacity,
];
_layerOpacityRatios = target.paintLayersInternal
?.map(
(paint) => paint.color.opacity / maxOpacity,
)
.toList(growable: false);
}

final List<T?> paintIds;
final HasPaint<T> target;
final bool includeLayers;
late final List<double> _opacityRatios;
late final List<double>? _layerOpacityRatios;

@override
double get opacity {
Expand All @@ -173,6 +196,11 @@ class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
for (final paintId in paintIds) {
maxOpacity = max(target.getOpacity(paintId: paintId), maxOpacity);
}
if (includeLayers) {
target.paintLayersInternal?.forEach(
(paint) => maxOpacity = max(paint.color.opacity, maxOpacity),
);
}

return maxOpacity;
}
Expand All @@ -185,5 +213,13 @@ class _MultiPaintOpacityProvider<T extends Object> implements OpacityProvider {
paintId: paintIds.elementAt(i),
);
}
if (includeLayers) {
final paintLayersInternal = target.paintLayersInternal;
for (var i = 0; i < (paintLayersInternal?.length ?? 0); ++i) {
paintLayersInternal![i].color = paintLayersInternal[i]
.color
.withOpacity(value * _layerOpacityRatios![i]);
}
}
}
}
23 changes: 21 additions & 2 deletions packages/flame/lib/src/geometry/circle_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import 'package:flame/extensions.dart';
import 'package:flame/geometry.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/utils/solve_quadratic.dart';
import 'package:meta/meta.dart';

class CircleComponent extends ShapeComponent implements SizeProvider {
/// With this constructor you can create your [CircleComponent] from a radius
Expand All @@ -18,6 +19,7 @@ class CircleComponent extends ShapeComponent implements SizeProvider {
super.children,
super.priority,
super.paint,
super.paintLayers,
}) : super(size: Vector2.all((radius ?? 0) * 2));

/// With this constructor you define the [CircleComponent] in relation to the
Expand All @@ -30,8 +32,19 @@ class CircleComponent extends ShapeComponent implements SizeProvider {
super.angle,
super.anchor,
super.paint,
super.paintLayers,
}) : super(size: Vector2.all(relation * min(parentSize.x, parentSize.y)));

@override
@mustCallSuper
Future<void> onLoad() async {
void updateCenterOffset() => _centerOffset = Offset(size.x / 2, size.y / 2);
size.addListener(updateCenterOffset);
updateCenterOffset();
}

late Offset _centerOffset;

/// Get the radius of the circle before scaling.
double get radius {
return min(size.x, size.y) / 2;
Expand All @@ -56,14 +69,20 @@ class CircleComponent extends ShapeComponent implements SizeProvider {
@override
void render(Canvas canvas) {
if (renderShape) {
canvas.drawCircle((size / 2).toOffset(), radius, paint);
if (hasPaintLayers) {
for (final paint in paintLayers) {
canvas.drawCircle(_centerOffset, radius, paint);
}
} else {
canvas.drawCircle(_centerOffset, radius, paint);
}
}
}

@override
void renderDebugMode(Canvas canvas) {
super.renderDebugMode(canvas);
canvas.drawCircle((size / 2).toOffset(), radius, debugPaint);
canvas.drawCircle(_centerOffset, radius, debugPaint);
}

/// Checks whether the represented circle contains the [point].
Expand Down
11 changes: 10 additions & 1 deletion packages/flame/lib/src/geometry/polygon_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ class PolygonComponent extends ShapeComponent {
super.children,
super.priority,
super.paint,
super.paintLayers,
bool? shrinkToBounds,
}) : assert(
_vertices.length > 2,
Expand Down Expand Up @@ -76,6 +77,7 @@ class PolygonComponent extends ShapeComponent {
Anchor? anchor,
int? priority,
Paint? paint,
List<Paint>? paintLayers,
bool? shrinkToBounds,
}) : this(
normalsToVertices(relation, parentSize),
Expand All @@ -86,6 +88,7 @@ class PolygonComponent extends ShapeComponent {
scale: scale,
priority: priority,
paint: paint,
paintLayers: paintLayers,
shrinkToBounds: shrinkToBounds,
);

Expand Down Expand Up @@ -171,7 +174,13 @@ class PolygonComponent extends ShapeComponent {
@override
void render(Canvas canvas) {
if (renderShape) {
canvas.drawPath(_path, paint);
if (hasPaintLayers) {
for (final paint in paintLayers) {
canvas.drawPath(_path, paint);
}
} else {
canvas.drawPath(_path, paint);
}
}
}

Expand Down
5 changes: 5 additions & 0 deletions packages/flame/lib/src/geometry/rectangle_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ class RectangleComponent extends PolygonComponent {
super.children,
super.priority,
super.paint,
super.paintLayers,
}) : super(sizeToVertices(size ?? Vector2.zero(), anchor));

RectangleComponent.square({
Expand All @@ -22,6 +23,7 @@ class RectangleComponent extends PolygonComponent {
super.anchor,
super.priority,
super.paint,
super.paintLayers,
super.children,
}) : super(sizeToVertices(Vector2.all(size), anchor));

Expand All @@ -38,6 +40,7 @@ class RectangleComponent extends PolygonComponent {
super.anchor,
super.priority,
super.paint,
super.paintLayers,
super.shrinkToBounds,
}) : super.relative([
relation.clone(),
Expand All @@ -53,6 +56,7 @@ class RectangleComponent extends PolygonComponent {
Anchor anchor = Anchor.topLeft,
int? priority,
Paint? paint,
List<Paint>? paintLayers,
}) {
return RectangleComponent(
position: anchor == Anchor.topLeft
Expand All @@ -67,6 +71,7 @@ class RectangleComponent extends PolygonComponent {
anchor: anchor,
priority: priority,
paint: paint,
paintLayers: paintLayers,
);
}

Expand Down
6 changes: 6 additions & 0 deletions packages/flame/lib/src/geometry/shape_component.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,14 @@ abstract class ShapeComponent extends PositionComponent with HasPaint {
super.children,
super.priority,
Paint? paint,
List<Paint>? paintLayers,
}) {
this.paint = paint ?? this.paint;
// Only read from this.paintLayers if paintLayers not null to prevent
// unnecessary creation of the paintLayers list.
if (paintLayers != null) {
this.paintLayers = paintLayers;
}
}

bool renderShape = true;
Expand Down
Loading

0 comments on commit 9e6bf4f

Please sign in to comment.