mirror of
https://github.com/flame-engine/flame.git
synced 2025-11-01 10:38:17 +08:00
feat: Added glow effect using maskFilter (#2129)
This commit is contained in:
@ -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.
|
||||
|
||||
@ -431,6 +431,30 @@ 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
|
||||
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
|
||||
|
||||
29
doc/flame/examples/lib/glow_effect.dart
Normal file
29
doc/flame/examples/lib/glow_effect.dart
Normal file
@ -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: size.y / 4,
|
||||
position: size / 2,
|
||||
anchor: Anchor.center,
|
||||
paint: paint,
|
||||
)..add(
|
||||
GlowEffect(
|
||||
10.0,
|
||||
EffectController(
|
||||
duration: 2,
|
||||
infinite: true,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
@ -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,
|
||||
};
|
||||
|
||||
39
examples/lib/stories/effects/glow_effect_example.dart
Normal file
39
examples/lib/stories/effects/glow_effect_example.dart
Normal file
@ -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,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@ -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';
|
||||
|
||||
@ -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';
|
||||
|
||||
@ -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
|
||||
|
||||
29
packages/flame/lib/src/effects/glow_effect.dart
Normal file
29
packages/flame/lib/src/effects/glow_effect.dart
Normal file
@ -0,0 +1,29 @@
|
||||
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.
|
||||
class GlowEffect extends Effect with EffectTarget<PaintProvider> {
|
||||
GlowEffect(this.strength, super.controller, {this.style = BlurStyle.outer});
|
||||
|
||||
final BlurStyle style;
|
||||
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;
|
||||
}
|
||||
}
|
||||
@ -1,3 +1,5 @@
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flame/components.dart';
|
||||
|
||||
/// Interface for a component that can be affected by move effects.
|
||||
@ -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
packages/flame/test/effects/glow_effect.dart
Normal file
37
packages/flame/test/effects/glow_effect.dart
Normal file
@ -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 {}
|
||||
Reference in New Issue
Block a user