Dibujar un círculo en Flame con Flutter

- Andrés Cruz

In english

Para empezar a desarrollar juegos 2D con Flutter y Flame, puedes seguir estos titoriales que traigo a tu disposición en los cuales conoceremos los conceptos básicos para crear juegos en 2D con Flutter.

 

Antes de entrar en materia de la gestión de imágenes o sprites, la detección de colisiones, eventos taps… Debemos de comenzar de a poco e inicialmente, vamos a querer dibujar algo por pantalla, alguna primitiva como un circulo, cosa que es muy sencilla de hacer; estos ejercisios son fundamentales para entender como desarrollar componentes más completos como sprites, animaciones y por supuesto, como intectartuar con todos estas imágenes según alguna acción del usuario.

Dibujar un circulo

Para dibujar un círculo en Flame con Flutter, se puede utilizar la clase Circle del paquete Flame/components.dart. Esta clase permite dibujar un círculo en el canvas en base a coordenadas que serían para definir su centro y tamaño:

Offset(10, 10)

Y tambien el color

BasicPalette.red.paint()

En este ejemplo, veremos cómo dibujar un círculo con la clase FlameGame:

lib/main.dart

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

class MyCircle extends PositionComponent {
  MyCircle() : super();

  @override
  Future<void> onLoad() async {}

  @override
  void render(Canvas canvas) {
    canvas.drawCircle(const Offset(10, 10), 10, BasicPalette.red.paint());

    // canvas.drawRect(Rect.fromCircle(center: const Offset(0, 0), radius: 20),
    //     BasicPalette.red.paint());
  }
}

class MyGame extends FlameGame {
  @override
  Future<void> onLoad() async {
    await add(MyCircle());
  }
}

main() {
  runApp(GameWidget(game: MyGame()));
}

En este otro ejemplo, tenemos la misma aplicación, pero usando la clase Game en su lugar:

lib/main.dart

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

void main() async {
  runApp(GameWidget(game: MyCircle()));
}

class MyCircle with Game {
  @override
  Future<void> onLoad() async {
    super.onLoad();
    // init
  }

  @override
  void render(Canvas canvas) {
    canvas.drawCircle(const Offset(10, 10), 10, BasicPalette.red.paint());

    // canvas.drawRect(Rect.fromCircle(center: const Offset(0, 0), radius: 20),
    //     BasicPalette.red.paint());
  }

  @override
  void update(double deltaTime) {}
}

Ya sea, el programa con la clase de Game o FlameGame, si ejecutas el script anterior, verás un resultado como el siguiente:

Como puedes concluir, aquellos enfoques que nos permita organizar el código en clases, es el enfoque que permite reutilizar más fácilmente los componentes, al igual que extender los mismos o crear otros, por lo tanto, estos ejemplos refuerzan el motivo por el cual se emplea la clase FlameGame en vez de la de Game; aparte de que, con la clase FlameGame tenemos acceso a otras funcionalidades de la API de Flame. 

Como vistes antes, usamos los métodos fundamentales del GameLoop en Flame, que son. la funcion de render y la de update.

La clase FlameGame mantiene una lista de todos los componentes del juego, y estos pueden agregarse dinámicamente al juego según sea necesario; por ejemplo, podríamos agregar varios componentes SpriteComponent enemigos al juego y luego eliminarlos del juego a medida que el jugador mata a los enemigos. Luego, la clase FlameGame iterará sobre estos componentes diciéndole a cada componente que se actualice y se represente a sí mismo.

La clase de GameWidget representa el constructor que usualmente empleamos para crear la instancia del juego en Flame y establecerla en el árbol de widget en Flutter.

Como recomendacion, presiona sobre la clase FlameGame, Game, PositionComponent, SpriteComponent y el resto, para ver el detalle de las mismas, ver que propiedades implementan, como en el caso, de las clases tipo Game:

class PositionComponent extends Component
    implements
        AnchorProvider,
        AngleProvider,
        PositionProvider,
        ScaleProvider,
        CoordinateTransform {
  PositionComponent({
    Vector2? position,
    Vector2? size,
    Vector2? scale,
    double? angle,
    this.nativeAngle = 0,
    Anchor? anchor,
    super.children,
***

O sobre las funciones que puedes implementar y que parámetros puedes enviar y el tipo, como en el caso del Canvas y haz pruebas con las mismas y evalúa el resultado...

Por ejemplo, para dibujar un rectángulo, podemos hacerlo definiendo dos puntos en la pantalla:

canvas.drawRect(Rect.fromPoints(const Offset(10,10), const Offset(500,500)), BasicPalette.purple.paint());

O mediante un círculo, pero, en vez de dibujar un círculo con canvas.drawCircle(), dibujamos un rectángulo:

canvas.drawRect(Rect.fromCircle(center: const Offset(100, 100), radius: 50.0),BasicPalette.brown.paint());

En resumen, con estos ejemplos, van quedando más claramente como funciona Flame, una estructura en base a componentes, que al igual que los widgets, son un elemento de varios de nuestros juegos, vemos como usar los PositionComponent los cuales son widgets básicos y que se heredan de otros tipos de widgets que presentaremos más adelante; elementos como la posición, tamaño, escala entre otras operaciones geométricas, son fundamentales en cualquier motor de videojuegos y en Flame no es la excepción.

Andrés Cruz

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

Andrés Cruz en Udemy