flame-engine / flame-engine/flame
TextComponent shadows are not affected by OpacityEffect
- Dominant language
- Dart
- Stars
- 10.8k
- Forks
- 1k
- Avg merge
- 1d 20h
- Merged PRs (30d)
- 21
Description
## Description
When an `OpacityEffect` runs on a `TextComponent` whose `TextPaint` style defines `shadows`, the glyph color fades but the shadows keep their original alpha. The shadows stay at full strength for the whole animation, so a fading text leaves a non-fading shadow or glow behind, which reads as a visual glitch.
There is also a related gotcha in the same code path: the first time the effect updates the paint, the style's fill color is replaced entirely by the component's `HasPaint` paint (white by default), so a colored text visibly snaps to white when the fade begins unless the user remembers to seed `paint.color` with the same color as the style.
## Cause
`TextComponent.onChanged` applies opacity through `TextRenderer.copyWithPaint`, and `TextPaint.copyWithPaint` only swaps the style's `foreground`:
```dart
@override
TextRenderer copyWithPaint(Paint paint) {
return copyWith(
(style) {
return style.copyWith(
foreground: paint,
);
},
);
}
```
`style.shadows` is left untouched, so shadow alpha never follows the component's opacity.
## Steps to reproduce
```dart
final text = TextComponent(
text: '+1 kr',
textRenderer: TextPaint(
style: const TextStyle(
color: Color(0xFF2E9940),
shadows: [Shadow(color: Color(0x99FFFFFF), blurRadius: 4)],
),
),
)..add(OpacityEffect.fadeOut(EffectController(duration: 1)));
```
## Expected behavior
The text and its shadows fade out together, in the text's own color.
## Actual behavior
The text snaps to white when the effect starts (foreground paint replaces the style color) and fades, while the white shadow stays at constant alpha until the component is removed.
## Suggested fix
In `TextPaint.copyWithPaint`, scale each shadow's color alpha by the incoming paint's alpha (and consider multiplying the paint color into the existing style color instead of replacing it, so `OpacityEffect` composes with a styled color the way it does for sprites).
Observed on the `perf/component-set-backing` branch, but the `copyWithPaint` implementation is the same on `main`.
Contributor guide
Research direction
Start with TextComponent.onChanged and TextPaint.copyWithPaint, where OpacityEffect updates the paint. Trace how TextStyle.shadows and the styled foreground color are copied, then add coverage for fading colored text with shadows. Done means the text and its shadows fade together without the initial color snap.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- dart, flutter
- Domain
- game-dev
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Active
- Clarity
- Mostly clear
- Newbie friendliness
- 72/100