Decorators¶
Decorators are classes that can encapsulate certain visual effects and then apply those visual
effects to a sequence of canvas drawing operations. Decorators are not Components, but they can
be applied to components either manually or via the HasDecorator mixin. Likewise, decorators are
not Effects, although they can be used to implement certain Effect
s.
There are a certain number of decorators available in Flame, and it is simple to add one’s own if necessary. We are planning to add shader-based decorators once Flutter fully supports them on the web.
Flame built-in decorators¶
PaintDecorator.blur¶
1import 'package:doc_flame_examples/flower.dart';
2import 'package:flame/game.dart';
3import 'package:flame/rendering.dart';
4
5class DecoratorBlurGame extends FlameGame {
6 @override
7 Future<void> onLoad() async {
8 var step = 0;
9 add(
10 Flower(
11 size: 100,
12 position: canvasSize / 2,
13 onTap: (flower) {
14 final decorator = flower.decorator;
15 step++;
16 if (step == 1) {
17 decorator.addLast(PaintDecorator.blur(3.0));
18 } else if (step == 2) {
19 decorator.replaceLast(PaintDecorator.blur(5.0));
20 } else if (step == 3) {
21 decorator.replaceLast(PaintDecorator.blur(0.0, 20.0));
22 } else {
23 decorator.replaceLast(null);
24 step = 0;
25 }
26 },
27 )..onTapUp(),
28 );
29 }
30}
This decorator applies a Gaussian blur to the underlying component. The amount of blur can be different in the X and Y direction, though this is not very common.
final decorator = PaintDecorator.blur(3.0);
Possible uses:
soft shadows;
“out-of-focus” objects in the distance or very close to the camera;
motion blur effects;
deemphasize/obscure content when showing a popup dialog;
blurred vision when the character is drunk.
PaintDecorator.grayscale¶
1import 'package:doc_flame_examples/flower.dart';
2import 'package:flame/game.dart';
3import 'package:flame/rendering.dart';
4
5class DecoratorGrayscaleGame extends FlameGame {
6 @override
7 Future<void> onLoad() async {
8 var step = 0;
9 add(
10 Flower(
11 size: 100,
12 position: canvasSize / 2,
13 onTap: (flower) {
14 final decorator = flower.decorator;
15 step++;
16 if (step == 1) {
17 decorator.addLast(PaintDecorator.grayscale());
18 } else if (step == 2) {
19 decorator.replaceLast(PaintDecorator.grayscale(opacity: 0.5));
20 } else if (step == 3) {
21 decorator.replaceLast(PaintDecorator.grayscale(opacity: 0.2));
22 } else if (step == 4) {
23 decorator.replaceLast(PaintDecorator.grayscale(opacity: 0.1));
24 } else {
25 decorator.removeLast();
26 step = 0;
27 }
28 },
29 )..onTapUp(),
30 );
31 }
32}
This decorator converts the underlying image into the shades of grey, as if it was a
black-and-white photograph. In addition, you can make the image semi-transparent to the desired
level of opacity
.
final decorator = PaintDecorator.grayscale(opacity: 0.5);
Possible uses:
apply to an NPC to turn them into stone, or into a ghost!
apply to a scene to indicate that it is a memory of the past;
black-and-white photos.
PaintDecorator.tint¶
1import 'dart:ui';
2
3import 'package:doc_flame_examples/flower.dart';
4import 'package:flame/game.dart';
5import 'package:flame/rendering.dart';
6
7class DecoratorTintGame extends FlameGame {
8 @override
9 Future<void> onLoad() async {
10 var step = 0;
11 add(
12 Flower(
13 size: 100,
14 position: canvasSize / 2,
15 onTap: (flower) {
16 final decorator = flower.decorator;
17 step++;
18 if (step == 1) {
19 decorator.addLast(PaintDecorator.tint(const Color(0x88FF0000)));
20 } else if (step == 2) {
21 decorator.replaceLast(PaintDecorator.tint(const Color(0x8800FF00)));
22 } else if (step == 3) {
23 decorator.replaceLast(PaintDecorator.tint(const Color(0x88000088)));
24 } else if (step == 4) {
25 decorator.replaceLast(PaintDecorator.tint(const Color(0x66FFFFFF)));
26 } else if (step == 5) {
27 decorator.replaceLast(PaintDecorator.tint(const Color(0xAA000000)));
28 } else {
29 decorator.removeLast();
30 step = 0;
31 }
32 },
33 )..onTapUp(),
34 );
35 }
36}
This decorator tints the underlying image with the specified color, as if watching it through a
colored glass. It is recommended that the color
used by this decorator was semi-transparent, so
that you can see the details of the image below.
final decorator = PaintDecorator.tint(const Color(0xAAFF0000);
Possible uses:
NPCs affected by certain types of magic;
items/characters in the shadows can be tinted black;
tint the scene red to show bloodlust, or that the character is low on health;
tint green to show that the character is poisoned or sick;
tint the scene deep blue during the night time;
Rotate3DDecorator¶
1import 'package:doc_flame_examples/flower.dart';
2import 'package:flame/game.dart';
3import 'package:flame/rendering.dart';
4
5class DecoratorRotate3DGame extends FlameGame {
6 @override
7 Future<void> onLoad() async {
8 var step = 0;
9 final decorator = Rotate3DDecorator()
10 ..center = Vector2.all(50)
11 ..perspective = 0.01;
12 add(
13 Flower(
14 size: 100,
15 position: canvasSize / 2,
16 decorator: decorator,
17 onTap: (flower) {
18 step++;
19 if (step == 1) {
20 decorator.angleY = -0.8;
21 } else if (step == 2) {
22 decorator.angleX = 1.0;
23 } else if (step == 3) {
24 decorator.angleZ = 0.2;
25 } else if (step == 4) {
26 decorator.angleX = 10;
27 } else if (step == 5) {
28 decorator.angleY = 2;
29 } else {
30 decorator
31 ..angleX = 0
32 ..angleY = 0
33 ..angleZ = 0;
34 step = 0;
35 }
36 },
37 )..onTapUp(),
38 );
39 }
40}
This decorator applies a 3D rotation to the underlying component. You can specify the angles of the rotation, as well as the pivot point and the amount of perspective distortion to apply.
The decorator also supplies the isFlipped
property, which allows you to determine whether the
component is currently being viewed from the front side or from the back. This is useful if you want
to draw a component whose appearance is different in the front and in the back.
final decorator = Rotate3DDecorator(
center: component.center,
angleX: rotationAngle,
perspective: 0.002,
);
Possible uses:
a card that can be flipped over;
pages in a book;
transitions between app routes;
3d falling particles such as snowflakes or leaves.
Shadow3DDecorator¶
1import 'dart:ui';
2
3import 'package:doc_flame_examples/flower.dart';
4import 'package:flame/components.dart';
5import 'package:flame/game.dart';
6import 'package:flame/rendering.dart';
7
8class DecoratorShadowGame extends FlameGame {
9 @override
10 Color backgroundColor() => const Color(0xFFC7C7C7);
11
12 @override
13 Future<void> onLoad() async {
14 final decorator = Shadow3DDecorator(base: Vector2(0, 100));
15 var step = 0;
16 add(Grid());
17 add(
18 Flower(
19 size: 100,
20 position: canvasSize / 2,
21 decorator: decorator,
22 onTap: (flower) {
23 step++;
24 if (step == 1) {
25 decorator.xShift = 200;
26 decorator.opacity = 0.5;
27 } else if (step == 2) {
28 decorator.xShift = 400;
29 decorator.yScale = 3;
30 decorator.blur = 1;
31 } else if (step == 3) {
32 decorator.angle = 1.7;
33 decorator.blur = 2;
34 } else if (step == 4) {
35 decorator.ascent = 20;
36 decorator.angle = 1.7;
37 decorator.blur = 2;
38 flower.y -= 20;
39 } else {
40 decorator.ascent = 0;
41 decorator.xShift = 0;
42 decorator.yScale = 1;
43 decorator.angle = -1.4;
44 decorator.opacity = 0.8;
45 decorator.blur = 0;
46 flower.y += 20;
47 step = 0;
48 }
49 },
50 )..onTapUp(),
51 );
52 }
53}
54
55class Grid extends Component {
56 final paint = Paint()
57 ..color = const Color(0xffa9a9a9)
58 ..style = PaintingStyle.stroke
59 ..strokeWidth = 1;
60 @override
61 void render(Canvas canvas) {
62 for (var i = 0; i < 50; i++) {
63 canvas.drawLine(Offset(0, i * 25), Offset(500, i * 25), paint);
64 canvas.drawLine(Offset(i * 25, 0), Offset(i * 25, 500), paint);
65 }
66 }
67}
This decorator renders a shadow underneath the component, as if the component was a 3D object standing on a plane. This effect works best for games that use isometric camera projection.
The shadow produced by this generator is quite flexible: you can control its angle, length, opacity, blur, etc. For a full description of what properties this decorator has and their meaning, see the class documentation.
final decorator = Shadow3DDecorator(
base: Vector2(100, 150),
angle: -1.4,
xShift: 200,
yScale: 1.5,
opacity: 0.5,
blur: 1.5,
);
The primary purpose of this decorator is to add shadows on the ground to your components. The main limitation is that the shadows are flat and cannot interact with the environment. For example, this decorator cannot handle shadows that fall onto walls or other vertical structures.
Using decorators¶
HasDecorator mixin¶
This Component
mixin adds the decorator
property, which is initially null
. If you set this
property to an actual Decorator
object, then that decorator will apply its visual effect during
the rendering of the component. In order to remove this visual effect, simply set the decorator
property back to null
.
PositionComponent¶
PositionComponent
(and all the derived classes) already has a decorator
property, so for these
components the HasDecorator
mixin is not needed.
In fact, the PositionComponent
uses its decorator in order to properly position the component on
the screen. Thus, any new decorators that you’d want to apply to the PositionComponent
will need
to be chained (see the Multiple decorators section below).
It is also possible to replace the root decorator of the PositionComponent
, if you want to create
an alternative logic for how the component shall be positioned on the screen.
Multiple decorators¶
It is possible to apply several decorators simultaneously to the same component: the Decorator
class supports chaining. That is, if you have an existing decorator on a component and you want to
add another one, then you can call component.decorator.addLast(newDecorator)
– this will add
the new decorator at the end of the existing chain. The method removeLast()
can remove that
decorator later.
Several decorators can be chain that way. For example, if A
is an initial decorator, then
A.addLast(B)
can be followed by either A.addLast(C)
or B.addLast(C)
– and in both cases the
chain A -> B -> C
will be created. In practice, it means that the entire chain can be manipulated
from its root, which usually is component.decorator
.