flame-engine / flame

A Flutter based game engine.
https://flame-engine.org
MIT License
9.14k stars 894 forks source link

Paint factory for paint instances used through out the engine. #2198

Open erickzanardo opened 1 year ago

erickzanardo commented 1 year ago

Problem to solve

There is no way to globally customize the default paints used though out the engine. I can't for example, change the default white color paint used on Sprites to a different one without having to explicit pass a custom paint to all the instances.

Proposal

We could provide global factories, which allows the user to specify their own factories, so the paints could be globally customized.

Example:

PaintFactories.sprite = () => Paint()..color = Colors.white;
PaintFactories.debugShapes = () => Paint()..color = Colors.magenta;
// ..etc

The user could then modify those attributes with new closures to customize to the behaviour they want.

wolfenrain commented 1 year ago

I have seen people ask about such on Discord before, I think this is indeed a good problem to solve. But I would like to suggest a slightly different API.

Lets define a PaintFactory, which will still be a global entity, that you can register paint factories on for different types:

PaintFactory.use<SpriteComponent>(() => Pain()..color = Colors.white);

This would allow people to register paint objects for their own components as well without having to overwrite anything in their own component

And then internally these can be resolved through an api that just checks if the given value (this) is of type generic:

// This internal API is just a concept to show how it could work, it would obv need more love 💙

class PaintFactory<T extends Component> {
  PaintFactory._(this.paint);

  final Paint Function() paint;

  bool check(dynamic value) => value is T;

  static final List<PaintFactory<T>> _factories = [];

  static void use<V extends Component>(Paint Function() paint) {
    _factories.add(PaintFactory<V>._(paint));
  }

  static Paint get(dynamic value) {
     return _factories.firstWhere((factory) => factory.check(value), () => Paint());
  }
}

// in `has_paint.dart`
mixin HasPaint on Component {
  late final Paint _paint = PaintFactory.get(this);

  Paint get paint => _paint;
}

The generic for PaintFactory.use could even be T extends Object so that the factories can be used for anything, and not only components. If there is a valid use case for it.

The only downside is the order of registration, as it depends on generics but if we dont provide any defaults we can just document that and then it is up to the user to ensure that.