Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: Added glow effect using maskFilter #2129

Merged
merged 20 commits into from
Oct 29, 2022
Merged
Show file tree
Hide file tree
Changes from 17 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 30 additions & 5 deletions doc/flame/effects.md
Original file line number Diff line number Diff line change
Expand Up @@ -82,17 +82,17 @@ The base `Effect` class is not usable on its own (it is abstract), but it provid
functionality inherited by all other effects. This includes:

- The ability to pause/resume the effect using `effect.pause()` and `effect.resume()`. You can
check whether the effect is currently paused using `effect.isPaused`.
check whether the effect is currently paused using `effect.isPaused`.

- The ability to reverse the effect's time direction using `effect.reverse()`. Use
`effect.isReversed` to check if the effect is currently running back in time.
`effect.isReversed` to check if the effect is currently running back in time.

- Property `removeOnFinish` (which is true by default) will cause the effect component to be
removed from the game tree and garbage-collected once the effect completes. Set this to false
if you plan to reuse the effect after it is finished.
removed from the game tree and garbage-collected once the effect completes. Set this to false
if you plan to reuse the effect after it is finished.

- Optional user-provided `onComplete`, which will be invoked when the effect has just
completed its execution but before it is removed from the game.
completed its execution but before it is removed from the game.

- The `reset()` method reverts the effect to its original state, allowing it to run once again.

Expand Down Expand Up @@ -431,6 +431,31 @@ Currently this effect can only be applied to components that have a `HasPaint` m
uses multiple paints, the effect can target any individual color using the `paintId` parameter.


### GlowEffect

This effect will apply the glowing shade around target relative to the specified
`glow-strength`. The color of shade will be targets paint color. For example, the following effect
will apply the glowing shade around target by strength of `10`:

```{flutter-app}
:sources: ../flame/examples
:page: glow_effect
:show: widget code infobox
:width: 180
:height: 160
```

```dart

dipakp2726 marked this conversation as resolved.
Show resolved Hide resolved
final effect = GlowEffect(
10.0,
EffectController(duration: 3),
);
```

Currently this effect can only be applied to components that have a `HasPaint` mixin.


### `SequenceEffect`

This effect can be used to run multiple other effects one after another. The constituent effects
Expand Down
29 changes: 29 additions & 0 deletions doc/flame/examples/lib/glow_effect.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/game.dart';

import 'package:flutter/material.dart';

class GlowEffectExample extends FlameGame {
@override
Future<void> onLoad() async {
final paint = Paint()..color = const Color(0xff39FF14);

add(
CircleComponent(
radius: canvasSize.y / 4,
position: canvasSize / 2,
dipakp2726 marked this conversation as resolved.
Show resolved Hide resolved
anchor: Anchor.center,
paint: paint,
)..add(
GlowEffect(
10.0,
EffectController(
duration: 2,
infinite: true,
),
),
),
);
}
}
2 changes: 2 additions & 0 deletions doc/flame/examples/lib/main.dart
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import 'package:doc_flame_examples/decorator_rotate3d.dart';
import 'package:doc_flame_examples/decorator_shadow3d.dart';
import 'package:doc_flame_examples/decorator_tint.dart';
import 'package:doc_flame_examples/drag_events.dart';
import 'package:doc_flame_examples/glow_effect.dart';
import 'package:doc_flame_examples/move_along_path_effect.dart';
import 'package:doc_flame_examples/move_by_effect.dart';
import 'package:doc_flame_examples/move_to_effect.dart';
Expand Down Expand Up @@ -67,6 +68,7 @@ void main() {
'rive_example': RiveExampleGame.new,
'ray_cast': RayCastExample.new,
'ray_trace': RayTraceExample.new,
'glow_effect': GlowEffectExample.new,
'remove_effect': RemoveEffectGame.new,
'color_effect': ColorEffectExample.new,
};
Expand Down
39 changes: 39 additions & 0 deletions examples/lib/stories/effects/glow_effect_example.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
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';

void main() {
runApp(GameWidget(game: GlowEffectExample()));
}

class GlowEffectExample extends FlameGame with TapDetector {
static const String description = '''
In this example we show how the `GlowEffect` can be used.
''';

@override
Future<void> onLoad() async {
final paint = Paint()
..color = const Color(0xff39FF14)
..style = PaintingStyle.stroke;

add(
CircleComponent(
radius: 50,
position: Vector2(300, 400),
paint: paint,
)..add(
GlowEffect(
10.0,
EffectController(
duration: 3,
reverseDuration: 1.5,
infinite: true,
),
),
),
);
}
}
1 change: 1 addition & 0 deletions packages/flame/lib/effects.dart
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export 'src/effects/controllers/speed_effect_controller.dart';
export 'src/effects/controllers/zigzag_effect_controller.dart';
export 'src/effects/effect.dart';
export 'src/effects/effect_target.dart';
export 'src/effects/glow_effect.dart';
export 'src/effects/move_along_path_effect.dart';
export 'src/effects/move_by_effect.dart';
export 'src/effects/move_effect.dart';
Expand Down
6 changes: 5 additions & 1 deletion packages/flame/lib/src/components/mixins/has_paint.dart
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import 'dart:ui';

import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame/src/effects/provider_interfaces.dart';
import 'package:flame/src/palette.dart';
import 'package:meta/meta.dart';

Expand All @@ -14,8 +15,11 @@ import 'package:meta/meta.dart';
/// [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 {
mixin HasPaint<T extends Object> on Component
implements OpacityProvider, PaintProvider {
late final Map<T, Paint> _paints = {};

@override
Paint paint = BasicPalette.white.paint();

@internal
Expand Down
30 changes: 30 additions & 0 deletions packages/flame/lib/src/effects/glow_effect.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import 'dart:ui';

import 'package:flame/effects.dart';
import 'package:flame/src/effects/provider_interfaces.dart';

/// Change the MaskFilter on Paint of a component over time.
///
/// This effect applies incremental changes to the MaskFilter on Paint of a
/// component and
/// requires that any other effect or update logic applied to the same component
/// also used incremental updates.
dipakp2726 marked this conversation as resolved.
Show resolved Hide resolved
class GlowEffect extends Effect with EffectTarget<PaintProvider> {
GlowEffect(this.strength, super.controller, {this.style = BlurStyle.outer});

final BlurStyle style;
dipakp2726 marked this conversation as resolved.
Show resolved Hide resolved
final double strength;

@override
void apply(double progress) {
final _value = strength * progress;

target.paint.maskFilter = MaskFilter.blur(style, _value);
}

@override
void reset() {
super.reset();
target.paint.maskFilter = null;
}
}
10 changes: 10 additions & 0 deletions packages/flame/lib/src/effects/provider_interfaces.dart
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import 'dart:ui';

import 'package:flame/components.dart';

/// Interface for a component that can be affected by move effects.
Expand Down Expand Up @@ -64,3 +66,11 @@ abstract class OpacityProvider {
double get opacity;
set opacity(double value);
}

/// Interface for a component that can be affected by Paint effects.
///
/// See [HasPaint] for an example implementation.
abstract class PaintProvider {
Paint get paint;
set paint(Paint value);
}
37 changes: 37 additions & 0 deletions packages/flame/test/effects/glow_effect.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import 'package:flame/components.dart';
import 'package:flame/effects.dart';
import 'package:flame_test/flame_test.dart';
import 'package:flutter_test/flutter_test.dart';

void main() {
group('GlowEffect', () {
testWithFlameGame('can apply to component having HasPaint', (game) async {
final component = _PaintComponent();

await game.ensureAdd(component);

await component.add(
GlowEffect(1, EffectController(duration: 1)),
);

game.update(0);

expect(component.children.length, 1);
expect(component.paint.maskFilter, isNotNull);

expect(
component.paint.maskFilter.toString(),
'MaskFilter.blur(BlurStyle.outer, 0.0)',
);

game.update(1);

expect(
component.paint.maskFilter.toString(),
'MaskFilter.blur(BlurStyle.outer, 1.0)',
);
});
});
}

class _PaintComponent extends Component with HasPaint {}