CameraComponent in Flame to follow components in Flutter and use of World

We currently have a background that is larger than what can be displayed in the window, resulting in a large part of it that cannot be visible; in order to take advantage of this non-visible background and have it show up as the player scrolls, we need to place a camera following the player.

The camera concept has been worked on in any game engine like Unity or Unreal or 3D/2D content creation software like Blender should be familiar to you; but in essence, the camera is nothing more than a mechanism through which we can observe a part of the world, where the world is nothing more than all the components that are within the game; and using coordinates, we can move this observer.

In these types of games, usually the camera follows the player and with this, we can visualize parts of the background that are not visible as the player moves through the world.

You can get more information about it at:

https://docs.flame-engine.org/latest/flame/camera_component.html

In Flame we have a component called CameraComponent with which we can indicate the display area.

World

The World component, as its name indicates, is a component that can be used to house all the other components that make up the game world. In the case of the game that we are implementing it would be:

  • The background.
  • The tiles.
  • The player.
  • The meteorites.

All these components must be added in an instance of the World component so that they are in the same layer and with this, to be able to interact with the player.

Using a World component is as simple as creating the instance:

final world = World();

And add in Flame:

add(world);

And at this point, all the components that are part of the game must be added to the world instance:

world.add(<COMPONENT>);

The World component and CameraComponent are designed to work together; an instance of the CameraComponent class "looks" at the world and this is why we introduced the world component along with the camera component.

It is usually not mandatory to use a World component for our Flame games, since we can add our game components directly into the Flame instance:

add(player);

Or in the same components, but, in this type of game that we have a bigger world than can fit on the screen, it is necessary to use a camera that follows or watches over our player.

CameraComponent 

CameraComponent, as we mentioned before, is the component used to observe the world, we have several properties to set on this component so that it "looks" exactly where we want and follows a component:

The Viewport is a window through which the world is seen. That window has a certain size, shape, and position on the screen.

The Viewfinder is responsible for knowing what location in the underlying game world we are currently looking at. The Viewfinder also controls the zoom level and rotation angle of the view; this property is key in these games since it is the one that is updated to "look" at the player.

Like any other game, the camera must observe the player who is moving through the world, although luckily this update is done automatically using the follow() function, which receives as a parameter, a component to observe and update the position of the camera as it moves across the screen:

cameraComponent.follow(player);

The Viewfinder allows you to customize aspects of the display such as the anchor, with which we can specify the center of the camera; that is, our world looks like the following:

The camera must be centered in the lower left corner, that is, in the bottomLeft:

And for this:

cameraComponent.viewfinder.anchor = Anchor.bottomLeft

Deploy the camera component

Having clarified how the world and camera component work, let's implement the logic in our application:

class MyGame extends FlameGame *** {
  ***
  late PlayerComponent player;

  final world = World();
  late final CameraComponent cameraComponent;

  @override
  void onLoad() {
    var background = Background();

    add(world);

    world.add(background);

    background.loaded.then(
      (value) {
        player = PlayerComponent();

        cameraComponent = CameraComponent(world: world);
        cameraComponent.follow(player);
        cameraComponent.setBounds(Rectangle.fromLTRB(0, 0, background.size.x, background.size.y)));

        // cameraComponent.viewfinder.anchor = Anchor.bottomLeft;
        cameraComponent.viewfinder.anchor = const Anchor(0.1, 0.9);
        add(cameraComponent);

        cameraComponent.world.add(player);
      },
    );

    add(ScreenHitbox());
  }
}

Key points

We define and add to Flame the world object:

final world = World();
***
add(world);

When using the camera component, it is necessary to use a world object:

cameraComponent = CameraComponent(world: world);

With an instance of the previous component, we can customize various aspects of the display, such as the camera following a component through the follow() function, which receives the component to follow as parameters:

cameraComponent.follow(player);

The center of the camera is defined in the lower left corner, but if we leave it this way:

cameraComponent.viewfinder.anchor = Anchor.bottomLeft; // Anchor(0, 1);

By positioning the camera that is following the player, we will see that the player is partly visible:

This is because the camera "follows" the player's anchor, which is aligned in the center:

 PlayerComponent({required this.mapSize}) : super() {
    anchor = Anchor.center;
    debugMode = true;
  }

To avoid this behavior, we can specify numeric values for the camera's anchor that are not as constrained as the previous value.

cameraComponent.viewfinder.anchor = const Anchor(0.1, 0.9);

And with this we have a complete visualization of the player:

Added to the above, we can also place restrictions on the camera display (the area visible by the camera, which in this case, is precisely the size of the image or map); It is important to mention that the display area corresponds to a rectangle with the size of the background; therefore:

cameraComponent.setBounds(Rectangle.fromLTRB(0, 0, background.size.x, background.size.y)));

And from the main, we pass the position of the camera:

lib\main.dart

@override
void update(double dt) {
  if (elapsedTime > 1.0) {
    Vector2 cp = cameraComponent.viewfinder.position;

    cp.y = cameraComponent.viewfinder.position.y -
        cameraComponent.viewport.size.y;
    world.add(MeteorComponent(cameraPosition: cp));
    elapsedTime = 0.0;
  }
  elapsedTime += dt;
  super.update(dt);
}

As you can see, we do not have the position of the camera directly, therefore, we must make a calculation; as we mentioned before, through the Viewfinder:

cameraComponent.viewfinder

We have the position of the camera, which is updated through the player as it moves across the screen, and we can obtain it with:

cameraComponent.viewfinder.position

But, this gives us the position of the camera in the lower corner, that is, the bottom, and we need to generate the meteorites in the upper part, for this reason, we subtract the height of the camera that we can obtain with:

cameraComponent.viewport.size.y

This material is part of my complete course and book on 2D game development with Flutter and Flame.

Video Transcript

Another important update that we have in flame as of version 1.8 is the use of camera components instead of the component or the camera property, that is to say. Remember that previously to use the camera in our application or in the game it was simply to use a property called Camera that we had for free when using flame here. And from here we can customize it by defining the element to follow, that is to say, the component to follow as well as the limits. But this is already a duplicate and of course in future versions they are going to remove it as you can see here as of version 1.9. Apparently they are going to remove it, therefore instead as it indicates here I think you should use the one of the component called camera component as it indicates here to use camera component but for the rest as it happened with the animations it is being the same philosophy changed the implementation.

But the concept is the same, that is to say, define a camera, indicate who to follow, define the limits of course, add the world here, but well, here also from the camera component. We also have an addition which is the element called World, that is to say, world, all this is new as I told you, therefore, Well, that is what I want to explain to you here a little bit, for that here I am going to return, Well, to a fragment of my book in which we are applying a little bit of all this, well, basically the camera component is exactly the same as we had before, it is simply a component that allows us to visualize a part of our world, understood as world, the entire level, so to speak:

final world = World();
Y agregar en Flame:
add(world);

In this case, our world would be represented by what we have here in the assets folder, our world would be all of this, specifically the tiles, but it cannot show you the Tail because it is purely an xml. But it would be all of this. Therefore, with the camera we indicate a portion of what we want to visualize, which for example would be right here, but from this we can indicate that we can follow a component, a flame component that can be anything. But logically it would be the playable component, which would be the Player component, therefore, as the Player:

cameraComponent.follow(player);

It moves around the world basically the camera follows it that was what we were doing before so there shouldn't be any bigger surprises and well this is exactly the same but instead of using the camera property we use the component called camera component even so, the small change the small changes start from now on in which now we also have a component called World, that is, of world which is going to be the in case you want to use the camera component as it is for this game it is what we are adding directly here we did for example a Player that we added this component directly to flame in this case to have more control now we add it directly to the world it is a good idea really because now we can have several worlds:

final world = World();
Y agregar en Flame:
add(world);


In short now we have to use the World when we use the camera component and now all the playable elements we have to add to the world that would be practically all and that's why you can see Well I already explained this to you a little bit now instead of directly using we use World which is the property that we created and the component and that's why you can see this implementation in what refers to the camera component being a component Well we initialize it, it receives the World the world that is to say the world that it is going to observe as I told you this is interesting because You can then change the world to observe something else and well from there vary your level there we indicate by follow the component that is going to follow which would be that of Player that follows Exactly the same and here we define the limits that now the implementation changes a little bit because it is received if we analyze here as is you can see here to define the limits and here you receive a Snap Well a shape then well that's why the implementation changes here a little bit but it's the same logic And from here we also define where it is going to be centered Remember that the width of the width component is to indicate where it is going to focus that it is going to focus would be on the left button but I'm going to you'll see what happens if I indicated this directly But it would be basically good another interesting thing that is to define the visible part that is the finder here I indicate the most important property that the camera component has that would be the viewport that is going to be the window in which the world is seen here we can have the size, the shape and the position and the viewport that is responsible for knowing the location, that is to say where our camera is going to be as I indicated by default The idea is that the camera is placed here together with our Player But as it moves the bifander is also updated, that is to say the Big background is like the equivalent of the position but here we have more control since we can apply zoom an angle among other aspects that you can consult in the official documentation but well as I say it is the part that is going to indicate the position and well more than really saying here I indicate the example that is in the part of the camera that is going to be seen we have the whole world here our Player is going to be in the one we want follow and well basically that makes me move our beach it also moves the camera thanks to the follow that we placed here in what refers to also the position of left is due here over here is where we want to position it right here on the button and the left side a little more to say So when running our game we will see what happens here we have it there it is look that everything remains more or less the same here the thing changed a little for example the part of the fell exactly that which now sometimes Queues in these limits and it is precisely because now we have a different distribution but for the rest you can see that it is practically the same So what would happen if I placed this the Debut which as I indicated is the equivalent Look it looks like this So why is it being displayed like this is precisely because of the width of Player remember that our Player the width is of type Center therefore what the camera really follows here is the width and not directly to the component and that is why it looks like this because the center is right here Notice that if we change it here and place for example this the led button you will see that here the display also changes And in this case I am updating the Player Note that I am not changing anything about the camera see that now the display changes clearly our game does not work with the bottom left type width therefore this is what happens but well it was to show you that what is it's still here it's the width and not directly the component and that's why here I chose some values in between at the end this is equivalent to placing here 0 and 1 that Sun called left button if I tell you this you'll see that we have the same visualization okay it fell sometimes that happens there we have the same visualization that we had before something that logically we don't want and that's why we play a little with the values to indicate that it is a little more visible some values a little well clicking a little more the line as you can see and that's basically all here apart from this this can also cause you a little Well here we are adding is the Player is through component camera to the World unlike for example the background that we are adding directly in the World in practice it is Exactly the same of course it is quite useful to add it this way In case you change the camera if you change the world of the component camera that is to say you have several worlds and you decide to use another world something that I told you that you can do perfectly this way it is cleaner since you would not have to update Where is the Player since it is it would update automatically but well if you do this you will see that it is exactly the same there you can see that it can interact with another game in the same way There it is here I lost a life it is exactly the same and I left it to vary here a little bit the implementation for the rest there is not much more to say again same idea same concept that of the camera but different implementation And that is why I decided to do the good this update in this way so that it is as clean as possible and simply now you learn What is the element that you have to use from flame in its version would be 1.8. 1 or well or more exaggerated one point nine Since in that version this will not exist according to them Ok another interesting idea so that you understand a little bit the idea of World is what would happen if we added the background for example to flame and not to the World look very important How is everything behaving, that is to say Well here to die Both the position in the background is being updated, that is to say this Well that pile of rocks and that look how it changes here is the rock over here if I move it moves and the meteorites the same meteorites are generated from the position based on the camera which by the way I have not shown you but I am going there but the whole game remains more or less the same but what would happen if for example this I add it directly to frame and look that it does not even appear Well here there are strange behaviors a little in this case the bagon in the waist surely due to the superposition of layers because sometimes strange things happen Well here it closed because it must be that something did not load But well in this case I think it was because of the layers I am going to execute here again here the Sky component I am going to leave it that is a non-essential component since we do not really need it I am going to add it directly to flame to see how it behaves look that now here we have a different visualization before The Rock appeared which was the bottom part now a well that planet appears there Notice that the position does not change, that is to say it has a different behavior since the world of flame to call it somehow and the world of World are two different layers and in what you can see here more interesting in the meteorites that would be the other playable component as you indicated all the components that are playable with which you want to interact the Player has to be on the same layer and in this case it would be the meter component if we remove it you will see that we have a strange displacement here well It is true it does not even seem like it Because the positions are not correct but they do seem that I think they are generated up there around there one is generated the game is more difficult than it seems But well Unfortunately it is as if you do not want to see it but the effect that you are going to have there is that the meteorite as if every time the Player moves the meteorite also moves a little strange Therefore it is practically impossible for it to collide with the Player but as you can see no everything does not work Ok here I also changed the way in which we generate the meteorites what is referred to is their position since Remember that we use the position of the camera since now we do not have if here we ask Camera component point position you will see that we do not have and well if we ask for the viewfinder which is the one that indicated to you that has several things among them the position is the one that we should use But well now also as we have a different anchor That's why we have to do an additional calculation here since here we are getting the height. Remember that we need here is the height to generate the meteorites up here. So if we pass here directly the position and without this the meteorites will be generated from the bottom, which is not what we want, notice that they are generated here because that is where the position is, they are generated right here up here because remember that we subtract the size of the meteorite as we are passing it so that it is not well it is generated up here usually and is not visible. But well here as you can see we have to subtract the size of the screen and the size of the screen we have is in the viewport and now you can see that if we subtract the size of the screen it means that we are up here plus the logic that we already have here implemented which was to subtract the size of the meteorite is generated in the equivalent way as we would say before. For the rest everything remains exactly the same.

- Andrés Cruz

En español

This material is part of my complete course and book; You can purchase them from the books and/or courses section, Flame: Desarrollo de juegos en 2D con Flutter y Dart.

Andrés Cruz

Develop with Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz In Udemy

I agree to receive announcements of interest about this Blog.