Enemigos que te disparan

Ya estoy retomando el ritmo de los tutoriales, vamos ahora con otro en la Lista. Les enseñaré a hacer enemigos un poco más inteligentes, que puedan dispararle a los jugadores cuando se encuentren cerca. Para esto podemos tomar prestado código del Juego de Plataformas Sunny Land y el Asset de la Máquina de Estados. Si al momento de ver este vídeo todavía estoy mudando los repositorios a GitLab, pueden obtener el código de los artículos.

  1. Vídeo Máquina de Estados: https://youtu.be/JaDlWRRlOkc
  2. Artículos Máquina de Estados: https://indielibre.com/2019/05/04/godot-engine-como-crear-una-maquina-de-estados-i/ y https://indielibre.com/2019/05/07/godot-engine-como-crear-una-maquina-de-estados-ii/

Antes que nada tengan en cuenta que para seguir este vídeo deberían tener lista ya su escena del jugador y la escena del enemigo. Tampoco será cualquier cosa, yo les recomendaría que esos personajes estén controlados por una Máquina de Estados… sin embargo pasaré por todos los scripts que usamos en este ejemplo y los explicaré, no tendrán problema en comprender el tutorial, espero.

Empezamos en la escena del jugador. Como el objetivo del proyecto es enseñar el comportamiento de un enemigo, esta escena no es nada compleja: sólo está lo necesario para que el jugador se mueva. Pueden ver que en su script principal definimos las variables básicas de movimiento y cambiamos la dirección de su imagen según la flecha presionada. En su Máquina de Estados sólo pasamos el estado de Detenido y Movimiento, que no tienen mucho contenido igual. Si de verdad no entienden estos estados básicos, les dejaré unos vídeos donde hablo de ellos.

La escena del enemigo tiene algunos cambios en su estructura de nodos: para detectar al jugador usaremos un nodo llamado Raycast2D que es como una línea recta que detecta la presencia de áreas o cuerpos físicos. Pondremos un RayCast a la izquierda y otro a la derecha del enemigo. Podemos configurar su tamaño desde la propiedad “Cast To” usaré un valor de quinientos en sus ejes verticales. Para que estén rectos vamos a rotarlos noventa grados, al principio apuntarán al cielo, pero al rotarlos quedarán horizontales.

Noten también que en el enemigo añadimos un estado de “Ataque” a su máquina. Además por fuera está un nodo Timer que tarda aproximadamente dos segundos en emitir una señal al script principal del enemigo para cambiar su dirección, cada vez que termina emite “timeout” para que desde el script lo volvamos a activar. Esta no es la forma más eficiente de cambiar la dirección de un enemigo, pero no quería complicarme mucho en este tutorial. Por último, hay un nodo Position2D como hijo del Sprite, se encarga de indicar la posición donde se instanciará la bola de fuego del dragón.

Pasemos entonces al script principal del enemigo: dentro de él definimos una señal de “ataque” que emitiremos más abajo. También es importante guardar en una variable la posición del nodo Position2D para el momento de instanciar el fuego, y si obviamos las variables de velocidad y aceleración necesarias para el movimiento, sólo queda una llamada “player” que guardará al nodo del jugador cuando sea detectado. Si su jugador no es un KinematicBody2D, quiten los dos puntos y dejen el “player = null”.

En la función de proceso y física, comprobaremos dos cosas: cuando un raycast esté colisionando y la variable del jugador siga siendo nula, entonces guarda en la variable del jugador al cuerpo que está detectando el Raycast, cambia la escala del sprite a la dirección donde te mueves y emite la señal de “attack”, “Atacando”, ya explicaré como funciona en un momento. Esta comprobación la aplicamos con el nodo Raycast Izquierdo, después repetimos el proceso con el derecho. Sólo falta la comprobación de que los dos raycasts no estén colisionando para hacer que la variable del jugador vuelva a ser nula. Recuerden que poner un signo de exclamación al comienzo de una comprobación, hace que estemos buscando un comportamiento falso. Por lo tanto, cuando detecte que NO estamos colisionando, el jugador pasa a nulo.

Para finalizar con este script recuerden que deben conectar la señal “timeout” del reloj. En la función resultante usaremos una condición termaria para decir que: si la dirección horizontal es -1, entonces dale un valor de 1 positivo. Sino, en caso de que sea 1 positivo, dale un valor de -1. Pronto dedicaré un vídeo corto a esta condición. Cerramos la función iniciando nuevamente el reloj con $Tiempo.start(), y como su tiempo es de dos segundos, cuando vuelva a cero se repetirá el proceso y el dragón cambiará su dirección.

Entrando ahora en el script de la máquina de estados vamos a añadir una línea más de lo normal: “owner.connect(“attack”, self, “change_state”). Conectando la señal que definimos en el enemigo, diremos que al momento de que se emita un ataque, llamemos a la función que cambia los estados, y el estado que pasaremos será “Atacando”, como definimos arriba. Recuerden que al momento de usar la palabra “owner” estamos accediendo al nodo raíz de la escena, o sea al principal, que es el KinematicBody2D del enemigo.

Antes de copiar el código que nos falta en el estado de “Atacando”, tenemos que crear una nueva escena que será la bola de fuego. El nodo principal es un Area2D y como hijo un Sprite para la imagen, un CollisionShape para la colisión, aunque en este ejemplo no hace falta ustedes seguro lo necesitarán, y un VisibilityNotifier. Este último emite una señal al Script de la bola para avisarle que ha salido de la pantalla y puede eliminarse con queue_free().

Pasemos entonces al Script de esta escena: contiene únicamente dos variables, una para la velocidad de la bala y otra para la dirección. Aplicamos el movimiento lineal en la función _process como hacemos en la mayoría de los casos. Sumando la aceleración por el tiempo a la dirección negativa o positiva que tenga la bala. La dirección se la dará el enemigo cuando la dispare.

En lo último de este Script verán que al recibir la señal de que la bola no se encuentra en la pantalla, se elimina.

Ahora sí podemos continuar en la escena del enemigo, específicamente en su estado de “Atacando”. Cuando inicie, llamaremos a una función de “fuego” que tiene las instrucciones para una nueva instancia de la bola. Para instanciarla creamos una nueva variable y buscamos en la carpeta de proyecto la dirección de la escena de la bola que creamos hace un momento. Cuando tenemos la dirección y estamos cargando un recurso “.tscn”, es decir, una escena, sólo basta con añadir el método “instance()” para crear una nueva instancia. Después faltaría que esa instancia forme parte del nivel o escena donde la estemos creando, y usamos para eso un add_child() a la nueva variable con el valor de la instancia.

Una vez creada y añadida a la escena, esa nueva bala necesitará una posición de acuerdo a nuestras necesidades. Por lo tanto, le pasamos la posición global del Position2D que guardamos en el enemigo. Después le ajustamos su dirección a la misma del enemigo y cerramos la función, y el script, con la línea que marque el final de este estado para seguir con el estado de Movimiento.

Y si probamos esto ahora, tendremos lo que queremos. Un Enemigo que al detectar al jugador, le dispara. Ustedes están encargados de hacer lo que quieran cuando la bola de fuego, o bala, flecha, o lo que sea, toque al jugador.