Search code examples
flutterdartflame

When adding an object in Flame, what criteria should be used to decide whether to add it to the world or to the game?


To make a component follow the camera, it seems necessary to add it to the world. The official documentation mentions this, but the explanation feels somewhat ambiguous: https://docs.flame-engine.org/latest/flame/camera_component.html

From my understanding of the documentation, it appears that if a component isn’t added to the world, it won’t be visible to the camera. However, based on many sample codes I’ve seen, simply calling add (which adds the component to the game) works just fine to display components on the screen.

When I call add directly (instead of adding the component to the world), the component is displayed on the screen. However, it won’t follow the camera.

So, what’s the best practice here? Should components always be added to the world for consistency and safety? Or are there cases where it’s okay to just use add without involving the world?

Camera follows object:

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';


void main() {
  runApp(const FooApp());
}

class FooApp extends StatelessWidget {
  const FooApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: GameWidget.controlled(gameFactory: CameraTrackingExample.new),
    );
  }
}

class CameraTrackingExample extends FlameGame {
  late final MyMap map;
  late final SpriteComponent _appleSpriteComponent;

  final _speed = 200.0;

  @override
  Future<void> onLoad() async {
    super.onLoad();

    map = MyMap();
    world.add(map);

    final appleSprite = await Sprite.load('apple.png');
    _appleSpriteComponent = SpriteComponent(
      position: Vector2(400, 100),
      size: Vector2(100, 100),
      sprite: appleSprite,
      anchor: Anchor.center,
    );
    world.add(_appleSpriteComponent);

    camera.follow(_appleSpriteComponent);
  }

  @override
  void update(double dt) {
    super.update(dt);

    _appleSpriteComponent.position.y += _speed * dt;
  }
}

class MyMap extends PositionComponent {
  MyMap()
      : super(
          size: Vector2.all(length),
        );

  static const double length = 1500;

  @override
  void render(Canvas canvas) {
    super.render(canvas);

    const steps = 10;
    for (var i = steps; i >= 0; i -= 1) {
      canvas.drawRect(
        Rect.fromCenter(
          center: Offset(width * 0.5, height * 0.5),
          width: width / steps * i.toDouble(),
          height: height / steps * i.toDouble(),
        ),
        Paint()..color = Color.fromARGB(255, 0, ((200 / steps) * i).toInt(), 0),
      );
    }
  }
}

Camera does not follows object:

import 'package:flame/components.dart';
import 'package:flame/game.dart';
import 'package:flutter/material.dart';


void main() {
  runApp(const FooApp());
}

class FooApp extends StatelessWidget {
  const FooApp({super.key});

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      debugShowCheckedModeBanner: false,
      home: GameWidget.controlled(gameFactory: CameraTrackingExample.new),
    );
  }
}

class CameraTrackingExample extends FlameGame {
  late final MyMap map;
  late final SpriteComponent _appleSpriteComponent;

  final _speed = 200.0;

  @override
  Future<void> onLoad() async {
    super.onLoad();

    map = MyMap();
    add(map); // 👈 remove world

    final appleSprite = await Sprite.load('apple.png');
    _appleSpriteComponent = SpriteComponent(
      position: Vector2(400, 100),
      size: Vector2(100, 100),
      sprite: appleSprite,
      anchor: Anchor.center,
    );
    add(_appleSpriteComponent); // 👈 remove world

    camera.follow(_appleSpriteComponent);
  }

  @override
  void update(double dt) {
    super.update(dt);

    _appleSpriteComponent.position.y += _speed * dt;
  }
}

class MyMap extends PositionComponent {
  MyMap()
      : super(
          size: Vector2.all(length),
        );

  static const double length = 1500;

  @override
  void render(Canvas canvas) {
    super.render(canvas);

    const steps = 10;
    for (var i = steps; i >= 0; i -= 1) {
      canvas.drawRect(
        Rect.fromCenter(
          center: Offset(width * 0.5, height * 0.5),
          width: width / steps * i.toDouble(),
          height: height / steps * i.toDouble(),
        ),
        Paint()..color = Color.fromARGB(255, 0, ((200 / steps) * i).toInt(), 0),
      );
    }
  }
}

When adding an object in Flame, what criteria should be used to decide whether to add it to the world or to the game?

To reiterate, based on my understanding from reading the documentation, it seems that objects won't be rendered on the screen unless they are always added to the world. That's why I find it puzzling.


References:


Solution

  • Components added directly to the game will still be rendered, but they will not be affected by any transformations done by the camera, like follow as you mention, or if you have set a different viewport etc.

    Most of the time you want to add visual components to the World, but if it is a really simple game that doesn't involve using the camera, you can add everything directly to the FlameGame.

    In some cases you want to add non-visual components to the FlameGame so that they stay in the component tree when you change the world, for example if you're using the world as a level.

    If you want to add components that are static to the screen and on top of the world, add them to the viewport, camera.viewport.add. And if you want to add a static background behind the game, add it to the backdrop, camera.backdrop.add.