I’m currently working on my first flutter flame game. It contains strawberry plants, but besides, I’m doing the graphics myself. And right now it is pretty much in the prototyping stage.

As aesthetics are important, I started with the background image and trying to fit everything else into it. And here is my problem with the usual background image solution…

The problem

All the tutorials I stumbled across so far, just force your device to use portrait mode (or landscape vice versa). But I want my background do scale dynamically, especially with flutter being able to support the web since the last big release.

Therefore I want my image to work on every ratio, but still being playable. Maybe there is a material solution out there that I haven’t found until now. Nonetheless – here is mine.

The solution

My actual game in progress is about setting plants to a ledge below a window. So my playable area is only the window, everything else is just background.

I want to scale the background image in such a way, that the center square fits into the screen, plus some of the background, depending on the screens size and mode.

Further, I define a gameRect that depends on screen and image size and it’s the only instance I will use to place any other game element. All inside this center square.

The actual coding

As we are inside of flame logic, things happen inside the game component. I will leave out all the other classes and concentrate on size, position and resizing of the background.

mygame.dart instantiates a new garden component and calls the custom resize method to calculate the image size according to the games size.

class MyGame extends BaseGame {

Garden garden;
  @override
  Future<void> onLoad() async {
    garden = new Garden(this);
    garden.resize(this.size);
  }

  @override
  void render(Canvas canvas) {
    if(garden != null) garden.render(canvas);
  }

  @override
  void onResize(Vector2 canvasSize) {
    super.onResize(canvasSize);
    if (canvasSize != null && garden != null) garden.resize(canvasSize);
  }
}

In my case, garden.dart is just a super class for different gardens, that behave in very similar ways. The important information is the gameRect that is used for further calculation concerning game components (not included in this blog post).

We first check, whether width or height is larger (it’s landscape or portrait mode). The smaller one will have to be equal to our center square of the background image. The scaleValue is needed to fit the background image into the screen, independent of its resolution.

When rendering, we have to add the leftover of the larger screen size to the calculation of the backgrounds position, to show some background and still center everything.

class Garden {
  final MyGame game;
  Sprite _bgSprite;
  Image bgImage;
  double gameBaseSize;
  Rect gameRect;
  double scaleValue = 1;

  Garden(this.game);

  Future<void> onLoad() async {
    _bgSprite = Sprite(bgImage);
  }

  Future<void> resize(Vector2 canvasSize) async {
    if (game.hasLayout && _bgSprite != null) {
      gameBaseSize = min(canvasSize.x, canvasSize.y);
      scaleValue = gameBaseSize / (_bgSprite.srcSize[0] / 3);

      gameRect = Rect.fromCenter(
          center: Offset(
            canvasSize.x / 2 / scaleValue,
            canvasSize.y / 2 / scaleValue,
          ),
          width: gameBaseSize / scaleValue,
          height: gameBaseSize / scaleValue
      );
    }
    return;
  }

  void render(Canvas c) {
    c.scale(scaleValue);

    if(_bgSprite != null) {
      Vector2 bgOffset = Vector2(
        _bgSprite.srcPosition.x - (_bgSprite.image.width / 3) + (game.size[0] - gameBaseSize) / 2,
        _bgSprite.srcPosition.y - (_bgSprite.image.height / 3) + (game.size[1] - gameBaseSize) / 2,
      );
      _bgSprite.render(
          c,
          position: bgOffset
      );
    }
  }
}

A subclass of garden is ledgegarden.dart. The most important part is, that we have to await for the super.resize to re-calculate the gameRect, before we go on with the resizing.

class LedgeGarden extends Garden {
  LedgeGarden(game) : super(game) {
    onLoad();
  }

  @override
  Future<void> onLoad() async {
    bgImage = await Flame.images.load('backgrounds/bgdebug.png');
    super.onLoad();
  }

  @override
  Future<void> resize(Vector2 canvasSize) async {
    await super.resize(canvasSize).then((value) {
      if (game.hasLayout && gameRect != null) {
        // calculate placement of further components
     }

    return;
  }
}

The result

I’d say it works like a charm. And the code doesn’t seem that much. I played around in the flutter web version and can’t wait to add my game components.

Right now, the solution is not ideal for larger screen sizes when we do not want the center square to take up that much space, but we might want to see more of the background. But for now, thats a secondary problem.

My Flame Game – Ultimate background image

Post navigation


Leave a Reply

Your email address will not be published. Required fields are marked *