CameraComponent en Flame para seguir componentes en Flutter y uso de World

Actualmente tenemos un fondo más grande que el que se puede mostrar en la ventana, dando como resultado de que existe una gran parte del mismo que no puede ser visible; para poder aprovechar este fondo no visible y que se muestre a medida que el player se desplaza, tenemos que colocar un seguimiento de la cámara al player.

El concepto de cámara se ha trabajado en cualquier motor de videojuegos como Unity o Unreal o software de creación de contenido en 3D/2D como Blender debe de ser conocido por ti; pero en esencia, la cámara no es más que un mecanismo por el cual podemos observar una parte del mundo, en donde el mundo no es más que todos los componentes que están dentro del juego; y mediante coordenadas, podemos desplazar este observador.

En este tipo de juegos, usualmente la cámara sigue al player y con esto, podemos visualizar partes del fondo que no son visibles a medida que el player se desplaza por el mundo.

Puedes obtener más información al respecto en:

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

En Flame tenemos un componente llamado CameraComponent con la cual, podemos indicar el área de visualización.

World

El componente World, tal cual indica su nombre, es un componente que puede ser usado para albergar todos los demás componentes que conforman el mundo de juego. En el caso del juego que estamos implementando sería:

  • El background.
  • Los tiles.
  • El player.
  • Los meteoritos.

Todos estos componentes, deben ser agregados en una instancia del componente World para que se encuentren en la misma capa y con esto, poder interactuar con el player.

Usar un componente World es tan sencillo como, crear la instancia:

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

Y en este punto, todos los componentes que formen parte del juego, deben ser agregados a la instancia de world:

world.add(<COMPONENT>);

El componente de World y CameraComponent están diseñados para trabajar en conjunto; una instancia de la clase CameraComponent "mira" el mundo/world y esta es la razón por la cual introducimos el componente world junto con el componente de cámara.

Usualmente no es obligatorio usar un componente World para nuestros juegos en Flame, ya que, podemos agregar los componentes de nuestro juego directamente en la instancia de Flame:

add(player);

O en los mismos componentes, pero, en este tipo de juegos que tenemos un mundo más grande del que puede entrar en la pantalla, es necesario usar una cámara que siga o vigile a nuestro player.

CameraComponent 

CameraComponent como comentamos antes, es el componente empleado para observar al mundo, tenemos varias propiedades para establecer en este componente y que "mire" exactamente a donde queramos y que siga a un componente:

  • El Viewport es una ventana a través de la cual se ve el mundo. Esa ventana tiene cierto tamaño, forma y posición en la pantalla.
  • El Viewfinder es responsable de saber qué ubicación en el mundo del juego subyacente estamos mirando actualmente. El Viewfinder también controla el nivel de zoom y el ángulo de rotación de la vista; esta propiedad es clave en estos juegos ya que, es la que se actualiza para "mirar" al jugador.

Como cualquier otro juego, la cámara debe de observar al jugador que es el que se va desplazando por el mundo, aunque, por suerte esta actualización se realiza de manera automática usando la función de follow(), que recibe como parámetro, un componente a observar y actualizar la posición de la cámara mientras este se desplaza por la pantalla:

cameraComponent.follow(player);

El Viewfinder, permite personalizar aspectos en la visualización como el anchor, con el cual, podemos especificar el centro de la cámara; es decir, nuestro mundo luce como el siguiente:

La cámara debe de estar centrada en la esquina inferior izquierda, es decir, en el bottomLeft:

Y para esto:

cameraComponent.viewfinder.anchor = Anchor.bottomLeft

Implementar el componente de cámara

Aclarado el funcionamiento del componente de mundo y cámara, vamos a implementar la lógica en nuestra aplicación:

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());
  }
}

Puntos claves

Definimos y agregamos a Flame el objeto world:

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

Al usar el componente de cámara, es necesario emplear un objeto world:

 

cameraComponent = CameraComponent(world: world);

Con una instancia del componente anterior, podemos personalizar varios aspectos sobre la visualización como lo es, que la cámara siga a un componente mediante la función de follow() la cual recibe como parámetros al componente a seguir:

cameraComponent.follow(player);

El centro de la cámara, se define en la esquina inferior izquierda, pero, si la dejamos de esta manera:

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

Al posicionar la cámara que está siguiendo al player, veremos que el player es visible en parte:

Esto se debe a que la cámara "sigue" al anchor del player, el cual está alienado en el centro:

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

Para evitar este comportamiento, podemos especificar valores numéricos para el anchor de la cámara que no sean tan restringidos como el valor anterior.

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

Y con esto tenemos una visualización completa del player:

Sumado a lo anterior, también podemos colocar restricciones sobre la visualización de la cámara (el área visible por la cámara, que en este caso, es justamente es el tamaño de la imagen o mapa); es importante mencionar, que el área de visualización corresponde a un rectángulo con el tamaño del fondo; por lo tanto:

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

Y desde el main, pasamos la posición de la cámara:

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);
}

Como puedes apreciar, no tenemos de manera directa la posición de la cámara, por lo tanto, debemos de hacer un cálculo; como comentamos anteriormente, mediante el Viewfinder:

cameraComponent.viewfinder

Tenemos la posición de la cámara, la cual se va actualizando mediante el player se va moviendo por pantalla, y podemos obtener con:

cameraComponent.viewfinder.position

Pero, esto nos da la posición de la cámara en la esquina inferior, es decir, la parte de abajo, y necesitamos generar los meteoritos en la parte de arriba, por tal motivo, le restamos el alto de la cámara que podemos obtener con:

cameraComponent.viewport.size.y

Este material forma parte de mi curso y libro completo sobre el desarrollo de juegos en 2D con Flutter y Flame.

- Andrés Cruz

In english
Andrés Cruz

Desarrollo con Laravel, Django, Flask, CodeIgniter, HTML5, CSS3, MySQL, JavaScript, Vue, Android, iOS, Flutter

Andrés Cruz En Udemy

Acepto recibir anuncios de interes sobre este Blog.