Implementar un Joystick en Flutter y Flame

- Andrés Cruz

In english
Implementar un Joystick en Flutter y Flame

Hasta este momento, hemos usado el teclado como fuente principal para interactuar con el juego, pero, esto trae como inconveniente de que solo podríamos emplear dispositivos que cuenten con un teclado, como sería un PC o Mac, dejando a la aplicación fuera de otros dispositivos como teléfonos con Android e iOS; para que este tipo de aplicación, en la cual es necesario desplazar al player en todos los ejes (o solamente de izquierda a derecha y viceversa, como en el caso del juego del dinosaurio) podemos implementar un joystick virtual; Flame dispone de este tipo de componentes ya listos para usar en nuestra aplicación.

Un joystick virtual luce como el siguiente:

En definitiva, puedes ver que constan de dos componentes:

  1. El círculo translúcido que indica el área de movimiento de la palanca/mando.
  2. El circulo de color solido que se encuentra contenido en el anterior que indica el control para que el usuario pueda mover el joystick virtual, este corresponde a la palanca o mando.

Comencemos implementando una clase joystick como la siguiente:

lib\components\hud\joystick.dart

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

class Joystick extends JoystickComponent {
  Joystick(
      {required PositionComponent knob,
      required PositionComponent background,
      required EdgeInsets margin})
      : super(knob: knob, background: background, margin: margin);
}

La clase JoystickComponent existe en la API de Flame y para implementar la misma, tenemos que definir dos propiedades fundamentales:

  1. knob, que hace referencia al mando.
  2. background, que es el área en la cual se puede desplazar el mando.

Ambos son PositionComponent por lo tanto, pueden ser un Sprite o una figura geométrica como un círculo.

Existen otras propiedades para personalizar el joystick como puedes consultar en la documentación oficial, pero, estas son las principales o claves.

Finalmente, creamos otro archivo y clase, con el cual, implementaremos la clase Joystick personalizada definida anteriormente y que llamaremos como Hud de heads-up display que es un término común en los videojuegos para referirse al apartado de iconos, mapas, vida, etc sobre el personaje. En esta clase implementamos la propiedad de knob y background del joystick como un par de círculos con colores, además, de posicionar el mismo en la pantalla mediante el margen:

lib\components\hud\hud.dart

import 'dart:async';

import 'package:flame/components.dart';
import 'package:flame/palette.dart';
import 'package:flutter/widgets.dart';
import 'package:parallax06/hud/joystick.dart';

class HudComponent extends PositionComponent {
  late Joystick joystick;

  @override
  void onLoad() {
    final joystickKnobPaint = BasicPalette.red.withAlpha(200).paint();
    final joystickBackgroundPaint = BasicPalette.black.withAlpha(100).paint();

    joystick = Joystick(
        knob: CircleComponent(radius: 30.0, paint: joystickKnobPaint),
        background:
            CircleComponent(radius: 100, paint: joystickBackgroundPaint),
        margin: const EdgeInsets.only(left: 40, top: 100));

    add(joystick);
  }
}

Finalmente, desde el main, agregamos una instancia de la clase anterior:

lib\main.dart

import 'package:parallax06/hud/hud_component.dart';
***
class MyGame extends FlameGame
    with HasKeyboardHandlerComponents, HasCollisionDetection {
  ***
  late HudComponent hudComponent;

  @override
  void onLoad() async {
    ***
    hudComponent = HudComponent();
    add(hudComponent);
  }
  ***
}

El componente de Joystick de Flame, tiene todo lo necesario para implementar la movilidad en nuestros componentes; mediante el tipo enumerado:

joystick.direction

Conocemos el estado del knob o mando:

enum JoystickDirection {
  up,
  upLeft,
  upRight,
  right,
  down,
  downRight,
  downLeft,
  left,
  idle,
}

También tenemos otras opciones que podemos usar para determinar el desplazamiento del knob; es decir, mientras más lejos el usuario lleve el mando:

Más alto será el vector:

[-1.0,-67.0] (joystick.delta)

Y mientras más cerca esté el knob del centro (mientras menos se desplace):

Más bajo será el vector:

[-1.0,-21.0] (joystick.delta)

Con este vector también tenemos el ángulo, es decir que si movemos el knob hacia arriba, tendremos un valor negativo en el Y:

[X,-72.0] (joystick.delta)

Y positivo si va hacia abajo:

[X,72.0] (joystick.delta)

Y la misma lógica se aplica para el eje de las X. Con esto, podemos usar de manera directa esta propiedad para desplazar al player sin necesidad de hacer comparaciones adicionales; finalmente, el desplazamiento del player mediante el joystick queda como:

lib\components\player_component.dart

class PlayerComponent extends Character with HasGameRef<MyGame> {
  ***
  final double _maxVelocity = 5.0;

  @override
  void update(double dt) {
    ***
    _movePlayerJoystick(dt);
  }

  void _movePlayerJoystick(double delta) {
    if (gameRef.hudComponent.joystick.direction != JoystickDirection.idle) {
      position.add(gameRef.hudComponent.joystick.delta * _maxVelocity * delta);
    }
  }
} 

Es importante mencionar que podemos tener habilitados (como es el caso para nuestra aplicación) dos o más funciones para el desplazamiento, en nuestro proyecto sería el desplazamiento mediante teclado y también mediante el joystick.

Finalmente, al ejecutar la aplicación, tendremos:

Claro está, que el joystick lo puedes implementar en los juegos anteriores para utilizar el juego en otros dispositivos que no cuenten con teclados.

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

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.