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.

Transcripción del vídeo

Otra actualización importante que tenemos en flame a partir de la versión 1.8 es el uso de cámara componentes en vez del componente o la propiedad de cámara es decir Recuerda que anteriormente para emplear la cámara en nuestra aplicación o en el juego era simplemente que emplear una propiedad llamada Camera que la teníamos de gratis al utilizar aquí flame Y a partir de aquí la podemos personalizar definiendo el elemento a seguir es decir el componente a seguir al igual que los límites Pero esto ya está como duplicate y que por supuesto en versiones futuras la van a remover tal cual puedes ver por acá a partir de la versión 1.9 Aparentemente ya la van a eliminar por lo tanto en su lugar tal cual te indica por acá creo deberías de emplear el de el componente llamado cámara componente tal cual te indica aquí que utilice cámara componente pero por lo demás como pasaba con las animaciones viene siendo la misma filosofía cambio la implementación.

Pero el concepto es el mismo es decir definir una cámara indicar A quién seguir definir los límites por supuesto agregar aquí al mundo pero bueno también aquí a partir del cámara componente También tenemos un agregado que es el elemento llamado World es decir mundo todo esto es nuevo como te digo por lo tanto Bueno es lo que quiero explicarte aquí un poquito para eso acá voy a regresar Bueno a un fragmento de mi libro en el cual estamos aplicando un poco todo esto bueno básicamente componente de cámara viene siendo Exactamente lo mismo que teníamos antes simplemente es un componente que permite visualizar una parte de nuestro mundo entiéndase con mundo todo el nivel por así decirlo:

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

En este caso, nuestro mundo sería el representado por lo que tenemos por acá en la carpeta assets nuestro mundo sería todo esto específicamente los tiles, pero no te puede mostrar el Tail porque es netamente un xml Pero sería todo esto por lo tanto con la cámara nosotros indicamos una porción de lo que nosotros queremos visualizar que por ejemplo sería justamente por acá pero a partir de esto nosotros podemos indicar que podemos seguir un componente un componente de flame que puede ser cualquiera Pero lógicamente sería el componente jugable que sería el de Player por lo tanto a medida que el Player:

cameraComponent.follow(player);

Se va desplazando por todo el mundo básicamente la cámara lo sigue era lo que estamos haciendo antes por lo tanto no debería haber mayor sorpresa y bueno Esto es exactamente lo mismo pero en vez de emplear la propiedad de cámara empleamos el componente llamado cámara componente aún así, el pequeño cambio los pequeños cambios parten a partir de ahora en el cual ahora También tenemos un componente llamado World, es decir, de mundo el cual va a ser el en el caso de que quieras emplear el componente de cámara como viene siendo para este juego viene siendo lo que nosotros estamos agregamos directamente por acá hacíamos por ejemplo una Player que agregamos este componente directamente a flame en este caso para tener más control ahora le agregamos directamente al mundo es una buena idea realmente porque ahora podemos tener varios mundos:

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

Y con esto los mundos no van a chocar y ahí puedes hacer varias cosas interesantes ahora tenemos más ese Control pero bueno lo que se refiere a nuestro juego vamos a tener líneas adicionales en lo que se refiere al mundo no es mucho más que decir tal cual puedes ver la implementación es extremadamente sencilla simplemente definimos una propiedad variable lo que tú quieras llamada World y a partir de esta la referenciamos para agregar nuestros componentes pero no le tenemos que indicar tamaños ni nada por el estilo y automáticamente el World va a ser básicamente todo lo que nosotros tengamos para el juego Entonces al emplear el World básicamente tenemos que agregar en el mismo todos los elementos jugables no los que vamos a interactuar de alguna manera en este caso sería el power es decir la imagen que estemos trabado antes el Player también lo estas me faltó por acá y los meteoritos por supuesto ahorita te voy a mostrar Qué pasa si agregas uno en una capa diferente vas a ver ahí bueno un comportamiento un poco extraño pero básicamente eso.

En pocas palabras ahora tenemos que emplear el World cuando empleamos el componente de cámara y ahora todos los elementos jugables lo tenemos que agregar al mundo eso sería prácticamente todo y por eso que puedes ver Bueno ya te explique un poquito esto ahora en vez de emplear directamente a empleamos World que es la propiedad que creamos y el componente y es por eso que puedes ver esta implementación en lo que se refiere componente de cámara al ser un componente Bueno lo inicializamos, ella recibe el World el mundo es decir el mundo que va a observar como te digo esto es interesante porque Puedes cambiar luego el mundo para observar otra cosa y bueno de ahí variar ahí tu nivel indicamos mediante follow el componente que va a seguir que sería el de Player que sigue Exactamente igual y por aquí le definimos los límites que ahora la implementación cambia un poquito porque está recibida si analizamos acá tal cual puedes ver aquí para definir los límites y aquí recibes un Snap Bueno un shape entonces bueno por eso es que cambia aquí un poquito la implementación pero es la misma lógica Y a partir de aquí también le definimos donde se va a centrar Recuerda que el ancho de componente de ancho es para indicar dónde se va a centrar que se va a centrar sería en el botón left pero yo te voy a ya vas a ver qué pasa si le indicó esto directamente Pero sería todo básicamente bueno otra cosa interesante que es para definir la parte visible que es el finder por aquí te indico la propiedad más importante que tiene cámara componente que sería el viewport que va a ser la ventana en la cual se ve el mundo por aquí podemos tener el tamaño la forma y la posición y el viewport que viene siendo el responsable de saber la ubicación es decir dónde va a estar nuestra cámara como te indicaba por defecto La idea es que la cámara se ponga por acá junto con nuestro Player Pero a medida que se va moviendo también se va actualizando el bifander es decir el Big fonder viene siendo como que el equivalente al posición pero por aquí tenemos más control ya que podemos aplicarle zoom un ángulo entre otros aspectos que puedes consultar a la documentación oficial pero bueno como te digo es la parte que nos va a indicar la posición y bueno más que decir realmente aquí te indico el ejemplo que está en la parte de la cámara que se va a ver tenemos todo el mundo aquí va a estar nuestro Player en la que queremos seguir y bueno básicamente eso me da que se vaya moviendo nuestra playa se va también moviendo la cámara gracias al follow que colocamos por acá en lo que se refiere también la posición de left se debe aquí por acá es la es donde la queremos posicionar justamente aquí en el botón y el lado izquierdo un poco más que decir Entonces al ejecutar nuestro juego vamos a ver qué pasa por aquí lo tenemos ahí está mira que todo sigue más o menos igual aquí cambio un poquito la cosa por ejemplo la parte del se cayó justamente eso que ahora a veces se Cola en estos límites y es precisamente porque ahora tenemos una distribución distinta pero por lo demás puedes ver que es prácticamente lo mismo Entonces qué pasaría si le colocó esto el Debut que como te indicaba es el equivalente Mira se ve de esta manera Entonces por qué se está visualizando así es justamente por el ancho de Player recordemos que nuestro Player el ancho viene siendo de tipo Center por lo tanto lo que realmente aquí sigue la cámara es el ancho y no directamente al componente y es por eso que se ve de esta manera porque el centro está justamente por aquí Fíjate que si aquí le cambiamos y le colocamos por ejemplo este el de botón led vas a ver que aquí también cambia la visualización Y en este caso estoy es actualizando el de Player Fíjate que no estoy cambiando nada de la cámara mira que ahora la visualización cambia claro nuestro juego no funciona con el ancho de tipo bottom left por lo tanto es que pasa esto pero bueno era para demostrarte que lo que está siguiendo aquí es el ancho y no directamente el componente y es por eso que por aquí escogí unos valores de por medio a la final esto equivale a colocar aquí 0 y 1 ese Sol llamado botón left si Les comento esto vas a ver que tenemos la misma visualización vale se cayó a veces pasa eso ahí tenemos la misma visualización que teníamos antes cosa que lógicamente no queremos y es por eso que jugamos un poco con los valores para indicarle que sea un poquito más visible unos valores un poquito bueno picando un poco más la raya tal cual puedes ver y eso es básicamente todo por acá aparte de esto esto también te puede causar un poco Bueno aquí estamos agregando es el Player es mediante cámara componente al World a diferencia de por ejemplo el background que lo estamos agregando directamente en el World en la práctica viene siendo Exactamente lo mismo claro es bastante útil agregarlo de esta manera Por si cambias la cámara por si cambias el mundo de la cámara componente es decir tienes varios mundos y decides emplear otro mundo cosa que te dije que puedes hacer perfectamente de esta manera es más limpia ya que no tuvieras que actualizar En dónde se encuentra el Player ya que lo actualizaría automáticamente pero bueno si haces esto vas a ver qué es exactamente lo mismo ahí puedes ver que puede interactuar con otro juego de igual manera Ahí está aquí perdí una vida es exactamente lo mismo y lo dejé para variar aquí un poquito la implementación por lo demás no hay mucho más que decir nuevamente misma idea mismo concepto el de la cámara pero diferente implementación Y es por eso que decidí hacer el bueno esta actualización de esta manera para que sea más limpio posible y simplemente ahora aprendas Cuál es el elemento que tienes que emplear a partir de flame en su versión sería la 1.8.1 o bueno o más exagerada la uno punto nueve Ya que en esa versión esto no va a existir según ellos Ok otra idea interesante para que entiendas un poquito la idea de World es que qué pasaría si agregamos el background por ejemplo a flame y no al World fíjate muy importante Cómo se está comportando todo es decir Bueno aquí morir Se está actualizando tanto la posición en el fondo es decir esta Bueno ese montón de rocas y eso fíjate que va cambiando aquí está la roca por acá si me desplazo se va moviendo y los meteoritos igual los meteoritos son generando a partir de la posición en base a la cámara que por cierto no te lo he mostrado pero yo voy para allá pero todo el juego sigue más o menos igual pero qué pasaría si por ejemplo esto Lo agrego directamente a frame y mira que ni siquiera aparece Bueno aquí hay comportamientos extraños un poco en este caso el bagón en el talle seguramente por la superposición de capas porque a veces pasan cosas un poco extrañas Bueno aquí se cerró porque debe ser que algo no cargó Pero bueno en este caso creo que era por el de que las capas voy a ejecutar aquí nuevamente aquí el de Sky componente lo voy a dejar que es un componente no imprescindible ya que no lo necesitamos realmente voy a agregarlo directamente a flame para ver cómo se comporta mire que ya aquí tenemos una visualización distinta antes aparecía La Roca que era la parte de abajo ahora aparece por ahí una bueno esa ese planeta Fíjate que la posición no cambia es decir tiene un comportamiento diferente ya que el mundo de flame por llamarlo de alguna manera y el mundo de World son dos capas distintas y en lo que puedes ver por acá más interesante en los meteoritos que sería el otro componente jugable como te indicaba todos los componentes que son jugables con los cuales tú quieres que interactuar el Player tiene que estar a la misma capa y en este caso sería la de medidor componente si le quitamos vas a ver que tenemos un raro desplazamiento aquí bueno Es verdad ni siquiera parece Porque las posiciones no están correctas pero si parecieran que creo que se generan por ahí arriba por ahí está por ahí se generó uno el jueguito más difícil de lo que parece Pero bueno Lamentablemente como que no se quiere ver pero el efecto que vas a tener ahí es que el meteorito como que cada vez que se desplace Player también se desplaza el meteorito un poco extraño Por lo tanto prácticamente es imposible que choque con el Player pero como puedes ver no no funciona todo Ok por aquí también cambie la forma en la cual generamos los meteoritos a lo que se refiere es su posición ya que Recuerda que empleamos la posición de la cámara ya que ahora no tenemos si aquí preguntamos Camera component punto posición vas a ver que no tenemos y bueno si preguntamos por el viewfinder que es el que te indicaba que tiene varias cosas entre ellas la posición es la que tuviéramos que emplear Pero bueno ahora también como tenemos un anchor distinto por eso que tenemos que hacer aquí un cálculo adicional ya que aquí estamos obteniendo el del alto Recuerda que nosotros aquí Necesitamos es el alto para generar los meteoritos por aquí arriba Entonces si le pasamos aquí directamente posición y sin esto los meteoritos se van a generar es por la parte de abajo cosa que no queremos fíjate que se generan por acá porque es donde está el posición se generan aquí justamente aquí arriba porque recuerda que le restamos el tamaño de meteorito a la medida que la estamos pasando para que no se bueno se genera por aquí arriba usualmente y no sea visibles Pero bueno aquí tal cual puedes ver le tenemos que restar el tamaño de la pantalla y el tamaño de la pantalla lo tenemos es en el viewport y ahora sí puedes ver que si le restamos el tamaño de la pantalla significa que estamos por acá arriba más la lógica que ya tenemos aquí implementada que era restarle el tamaño del meteorito se genera de la manera equivalente como diríamos antes por lo demás Todo queda exactamente igual.

- Andrés Cruz

In english

Este material forma parte de mi curso y libro completo; puedes adquirirlos desde el apartado de libros y/o cursos Flame: Desarrollo de juegos en 2D con Flutter y Dart.

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.