Curso de juegos (9)


Curso “Programación de Juegos”

Curso Gratuito
Entrega 9. La generación de bots enemigos en el juego


Como mencioné en la nota de Inteligencia Artificial (Nota 9), hoy vamos a ver cómo programar enemigos que se muevan de manera independiente al jugador.

La idea era programar 3 tipos de enemigos:

1.    Enemigos sin IA (los que abordamos en esta nota)
2.    Enemigos con un nivel de IA que permita seguimiento del jugador
3.    Enemigos con un nivel de IA que predice los movimientos del jugador

Sin duda el tercer ejemplo es el más interesante, pero como esto es un curso… primero es conveniente conocer en profundidad las interfaces que debemos programar para incluir jugadores tipo bots que tengan voluntad propia.

Ese será el foco de esta nota.

Es de suponer que a esta altura del curso, ya conoce la dinámica: si bien estudiamos TODO el código de los fuentes que vamos creando en el curso, en las notas me concentro principalmente en los fragmentos que son el núcleo de la nota en sí misma.

El programa base que usaremos para estudiar la implementación de enemigos es el programa 01.BAS ( puede descargarlo aqui )

Para concentrarnos sólo en los queremos explicar, recuerde repasar las notas anteriores para entender el funcionamiento general del programa, que sigue teniendo la estructura clásica de este curso de juegos:

1) Inicialización de variables, declaraciones de subrutinas y funciones
2) Dibujo de pantalla
3) Bucle de ejecución con llamadas para capturar los eventos del programa

Por supuesto, las explicaciones más profundas se concentrarán en los fragmentos de código fuente específicos.

Vamos a la idea: ¿Cómo se implementa un bot independiente del jugador?

Básicamente debemos definir algunos marcos de software específico:

a) Un mini sub-sistema que permita hacer que el bot enemigo genere un movimiento. En esta nota, el movimiento será puramente al azar, en otras notas ese movimiento al azar será influenciado por la inteligencia artificial en diferentes grados. A este subsistema le llamaré SUBSISTEMA MOVIMIENTO BOT (SSMB abreviado) para referirme a él en la nota y que usted no se “pierda” en la jerga.

b)  Un mini-subsistema de control de movimiento del enemigo, que debe seguir las mismas premisas (o muy parecidas) que la reglas seguidas por el jugador: debe moverse dentro de los limites del escenario. A este sub-sistema le llamaré SUBSISTEMA CONTROL ESCENARIO BOT (SSCEB abreviado).

c)  Un sistema de control de colisiones para el bot, porque como el jugador humano, no debe atravesar elementos sólidos de la escena. Este código también deberá decidir que hacer teniendo en cuenta los diferentes tipos de colisiones que se generen. A este código le llamaré SUBSISTEMA COLISIONES BOT (SSCB).  En estos programas de ejemplo, para no “ensuciar” la didáctica, dejaremos de lado este subsistema porque dentro de este código hay algunas cosas para aprender que me gustaría tratar casi exclusivamente: Para optimizar nuestro código, deberíamos crear un susbsistema unificado para el control de colisiones (tanto de usuario y para los bots implementados). No tiene sentido crear dos susbistemas de colisiones separado para cada jugador (a no ser que así lo amerite la naturaleza propia del juego). Hacer un sistema de colisión separado para cada jugador (bot o humano) haría perder eficiencia a nuestros programas. Es mejor unificar rutinas.

En algún punto del curso me dedicaré a explicar cómo se optimizan los códigos. Usualmente los programadores intentamos crear un prototipo operativo que funcione bien. A partir de allí intentamos optimizar las rutinas linea por linea y unificarlas en todo lo que sea posible. Pero eso es un tema que hoy no nos preocupa tratar.

SUBSISTEMA MOVIMIENTO BOT (SSMB abreviado)

Obviamente el sistema de hard no “entiende” las premisas de mover un objeto, por lo que debemos pensar y diseñar como hacer que el ordenador o computadora genere un movimiento dentro de nuestro escenario. Todo esto dentro del marco matemático requerido por el sistema (que sólo comprende números).

En nuestro caso, pensemos….. el movimiento se hace en 2D, vale decir adelante, atrás, derecha, izquierda: ¿Cómo hacer que la computadora mueva al bot en un espacio bidimensional?

La idea es sencilla. Mire esta grafica:

El enemigo (en un escenario 2D) debería moverse en 8 direcciones posibles a partir de cualquier punto del tablero.

Lo único que debemos hacer es identificar cada dirección de manera inequívoca para indicarle al sistema hacia donde mover el sprite-bot de nuestro ocasional oponente. Podríamos hacerlo de este modo:

De este modo asignamos un número a cada dirección posible. Como los lenguajes de programación incorporan funciones para generar numeros al azar, simplemente debemos programar el generador de numeros al azar del lenguaje para que genere un número comprendido entre las posibilidades que nosotros mismos determinemos.

En este caso en particular, para nuestro juego debemos indicarle al sistema que genere un número entre 1 y 8 para determinar en qué dirección se moverá nuestro bot. Sencillo ¿no le parece?

Esta es la técnica más popular y rápida para permitir al sistema comprender los movimientos de jugadores humano o bots dentro de los juegos. Existen otras, por supuesto, pero básicamente creo que es la más eficiente y rápida de implementar.

Esta misma técnica se puede emplear para movimientos en 3D. Se van asignando números a todos los puntos del espacio en los que se pueda mover un jugador.

Vea la próxima imagen. Ahí vemos un ejemplo de un jugador que se mueve sobre un piso, en el cual tiene libertad de movimientos en 2D pero también puede moverse espacialmente hacia arriba (saltando, por ejemplo como un MarioBros 3D ). En este caso el programador no incluye movimientos hacia abajo porque el sprite no puede atravesar el piso.

En la proxima figura, se aprecia un escenario que brinda movimientos totales en 3D (todas las direcciones posibles) al jugador o bot. El “enrejado” numerico de las direcciones y movimientos rodea al jugador como si fuera un cubo de rubik, llenando así todas las posibilidades.

Como puede ver, la técnica no es difícil. Vamos a estudiar ahora el código del programa 04a.bas:

Dentro del bloque DO-LOOP que controla la recursividad de nuestro programa tenemos el bloque de instrucciones que detecta la tecla presionada por el jugador humano. (NOTA: si no conoce el término recursividad… ya lo sabe: ¡a los libros! Jeje).

Como el movimiento del bot es independiente del humano, trabajamos la detección de teclas KeyPress = UCASE$(INKEY$) del modo habitual al que venimos trabajando desde el programa 01.bas en adelante, es decir: con un bloque de instrucciones IF-THEN-ELSEIF-ELSE. Si no lo recuerda en detalle, vea las notas anteriores.

Después del control de teclas, se actualiza la pantalla (los datos y también el sprite del jugador humano). Y a partir de allí, la parte del código que nos interesa:

El SSMB (SubSistema de Movimiento del Bot) está formado sólo pur un par de líneas:

      RANDOMIZE TIMER
      Azar = INT(RND(1) * 8) + 1'genera numero entre 1 y 8

La linea que dice RANDOMIZE TIMER inicializa el sistema de generación de números al azar que trae incorprado su lenguaje de programación para que genere una semilla numérica que no se repita ciclo a ciclo. Recuerde que el bucle recursivo DO-LOOP se repite indefinidamente y en cada repetición, el sistema generará un número. Si no lo inicializáramos “refrescándolo” con una semilla variable, el sistema tendería a generar siempre (o varias veces el mismo número).

¿No lo entendió? No importa: se lo explico en terminos humanos: RANDOMIZE le indica al sistema que genere un número al azar. El problema de los cerebros de silicio es que son tontos elevados a la décima potencia. Y no tienen la imaginación suficiente para generar números al azar. Es decir… tienden a “pensar” los mismos números una y otra vez. Por eso su lenguaje de programación lo “instruye” diciéndole: debes generar un número pero que esté basado en una semilla.  Algo así como decirle a usted “piense un número entre 1 y 100”. En el caso de esta línea de código, el número se basa (la “semilla”) en el reloj de tiempo real del sistema (TIMER).

Como el TIMER varia permanentemente (el tiempo no se detiene… desafortunadamente para los viejos como yo…), el sistema genera un número basado en una semilla variable, lo que hace que se parezca más al azar porque siempre tiende a variar. No es azar al 100% pero es suficiente para lo que necesitamos.

La segunda línea tiene el objeto de ordenarle al sistema que (dentro de ciertos parámetros) el número generado esté dentro del rango de movimientos que hemos planificado (en nuestro caso entre 1 y 8).

El número generado entre 1 y 8 se almacena en la variable AZAR. Para ser utilizada posteriormente.

No era tan complicado ¿verdad?

Este trabajo se hace ciclo a ciclo del DO-LOOP por lo que tenemos un movimiento por ciclo.

Cuando ejecute el programa 04a.bas verá que el movimiento es MUY RAPIDO y el bot revolotea como mosquito, a una velocidad ultra rápida. Eso lo controlaremos con una solución implementada en el programa 04b.bas pero no lo veremos ahora. Quiero que se concentre en este par de temas por son cruciales en el programa. Veremos como controlar el “revoloteo” del bot más abajo.

SUBSISTEMA CONTROL ESCENARIO BOT (SSCEB)

El proximo bloque de instrucciones, contenidas en la estructura SELECT-CASE es el SSCEB.

El razonamiento es simple: el bot se mueve, pero debemos controlar que no salga de los límites del escenario.

Estos límites están controlados por variables que no varían (CONSTANTES):

   CONST MnFila = 3                        'minima fila
   CONST MxFila = 20                      ‘maxima fila
   CONST MnCol = 1                        ‘minima columna
   CONST MxCol = 80                      ‘maxima columna

Recordemos como trabajan estas variables: controlan que los movimientos (de todos los elementos móviles del juego – humanos y bots-) se mantengan dentro de los límites del escenario. Vel el grafico que aclara este concepto:


El bloque SELECT-CASE funciona de modo simple:

El bloque debe trabajar con las variables Ef y Ec porque estas variables contienen la posición actual del enemigo:

   Ec = xx                      'contiene la columna en la que esta posicionado el bot
   Ef = xx                      'contiene la fila en la que está posicionado el bot

Dependiendo del movimiento generado por el SSMB, se analizará puntualmente cada movimiento.

La sentencia “CASE x” analiza el movimiento generado que está contenido en la variable AZAR para controlar que no sea un movimiento que saque de los limites del tablero al bot en cuestión. Veamos una de las instrucciones y usted analizará después las otras:

CASE 1: en el caso que se haya generado un movimiento al cuadrante [1] o si lo prefiere “arriba a la izquierda”, el sistema analiza estas condiciones: 

            IF (Ef - 1) >= MnFila THEN Ef = Ef - 1:  ELSE Ef = MnFila                   ‘primer analisis
            IF (Ec - 1) >= MnCol THEN Ec = Ec - 1:  ELSE Ec = MnCol                  ‘segundo analisis

Para moverse al cuadrante superior izquierdo, el sistema debe hacer lo siguiente:

1. Tomar la posición actual de fila del bot y restarle una unidad (Ef-1)
2. Tomar la posición actual de columna del bot y restarle una unidad (Ec-1)

Esas son las dos condiciones que evalua la opción CASE 1 de la estructura SELECT-CASE.

Como a un programador novato puede resultarle algo difícil de leer, vamos a “desplegarlas” un poco para entender la lógica del análisis. Se las escribo de un modo menos críptico (pero recuerde que trabajan igual escritas en un modo u otro):

‘primer analisis: ¿que pasa si a la posición actual de la fila del bot
‘se le resta una unidad?
IF (Ef - 1) >= MnFila THEN
                ‘si al restarle una unidad, el número de fila no es más chico
                ‘que el valor minimo de la fila, la operación es viable
                ‘porque la nueva posición está dentro de la pantalla habilitada
                Ef = Ef – 1
ELSE
                ‘en caso contrario significa que el valor sacaría de la pantalla
                ‘al bot, por lo tanto se le asigna el valor mínimo impidiendo que
                ‘salga de pantalla
                Ef = MnFila
 END IF

‘segundo analisis: ¿que pasa si a la posición actual de la columna del bot
‘se le resta una unidad?
 IF (Ec - 1) >= MnCol THEN
                ‘si al restarle una unidad, el número de columna no es más chico
                ‘que el valor minimo de columna, la operación es viable
                ‘porque la nueva posición está dentro de la pantalla habilitada
                Ec = Ec – 1
ELSE
                ‘en caso contrario significa que el valor sacaría de la pantalla
                ‘al bot, por lo tanto se le asigna el valor mínimo impidiendo que
                ‘salga de pantalla
                Ec = MnCol
END IF

La estructura SELECT-CASE analiza una a una las ocho posiciones posibles que puede generar el subsistema de movimiento SSMB. Esto se hace en cada opción CASE 1, CASE 2, CASE 3, etc.
Toda esta estructura analiza el cambio de valor de la variable AZAR y define finalmente los valores corregidos de la posición actual del bot (almacenados en Ef y Ec).

La validación tiene por fin hacer el cálculo y en caso de que el bot quiera “salirse” de la pantalla, evitarlo porque el sistema lo que hace es asignarle la operación o simplemente le asigna el valor máximo o mínimo permitido, con lo que se evita que el bot salga de los límites definidos de la pantalla.

Le dejo a usted el análisis de las otras opciones CASE X, pero básicamente el razonamiento es el mismo.

Toda la estructura SELECT-CASE puede ser sometida a un análisis de optimización de código, pero es una tarea que también le dejo pendiente. Preferí escribirla de ese modo por la didáctica de la lección.

Con esto, quedan explicadas las interfaces y componentes de software que necesitamos para crear (al menos) un bot que acompañe al jugador humano.

Por supuesto que estas técnicas sirven para crear tantos bots como sean necesarios en el juego. Quizá lo plantearemos en otras notas.



Regulando la velocidad del bot en el programa 04b.bas

Ahora le planteo jugar libremente con el juego del programa 04a.bas y lo primero que notará es la velocidad extremadamente alta con la que se mueve nuestro bot.

En el programa 04b.bas la idea es generar un mini-sistema de control de velocidad del contrincante que permita ajustar la velocidad del bot y llevarlo a diferentes niveles de velocidad que permitan ajustar la misma a la velocidad del equipo que ejecuta al programa.

Si analiza el bucle DO-LOOP, verá que la posición del bot es ajustada en cada ciclo de ejecución del buble (de ahí que se mueva como un mosquito furioso). La idea del control de velocidad, entonces, pasa por hacer que el sistema mueva al bot no en todos los ciclos de ejecución del bucle DO-LOOP, sino sólo en algunos ciclos.

Para eso, vamos a necesitar primero un par de variables de ajuste:

                CicloMx = 50                 'se ajusta para modificar posición cada 50 ciclos DO-LOOP
                CicloAc = 1                    'inicializacion del contador de ciclos

Para lograr el efecto deseado vamos a hacer lo siguiente: la variable CicloAc es un contador incremental (incrementa en uno su valor en cada ciclo DO-LOOP). Cuando lleva al mimso valor de la variable CicloMx, ajusta la posición del bot (es decir lo mueve). Al terminar de moverlo, se setea nuevamente a uno y vuelve a incrementarse con cada ciclo de ejecución.

Con los valores iniciales, el sistema ajusta (o mueve) la posición del bot una vez cada 50 ciclos. Esta velocidad es regulable (el usuario cuenta con las teclas [+] para aumentar la velocidad y [-] para disminuir la velocidad de movimiento del bot.

Dentro del bucle DO-LOOP ahora usted encontrará la estructura

IF CicloAc >= CicloMx THEN / END IF

Que justamente evalúa el ciclo actual y sólo ejecuta los bloque de instrucciones que forman parte del SSCEB  y del SSMB, determinando ahora que se ejecuten sólo cuando la condición se cumple.

En todo momento el usuario puede ajustar la velocidad del movimiento porque el programa detecta la presión de teclas con

KeyPress = UCASE$(INKEY$)

y evalúa con estas nuevas opciones ELSEIF la presión de las teclas de control de velocidad, que son las teclas + (para incrementar) y – para decrementar la velcidad:

      'mas lento
      ELSEIF KeyPress = "-" THEN
                 CicloMx = CicloMx + 1
                 IF CicloMx > 300 THEN CicloMx = 300
               
      'mas rapido
      ELSEIF KeyPress = "+" THEN
                 CicloMx = CicloMx - 1
                 IF CicloMx <= 1 THEN CicloMx = 1

Parece paradójico que para aumentar la velocidad tenga que decrementar la variable CicloMx, pero tenga en cuenta que es un control inverso: al disminuir la cantidad de ciclos en los que que no mueven al bot, el resultado es que el bot se mueve más lento porque el sistema escanea y modifica su posición más frecuentemente (¿recuerda el concepto de frecuencia?).

Pruebe los programas. Modifíquelos, vea que pasa cuando lo modifica, sea proactivo: toque, lea pruebe, modifique.

Póngase a tono si aún no lo hizo porque dentro de poco viene lo mejor: hemos estudiado en estas pocas lecciones los principios fundamentales de variable, controles, interfaces y  sistemas de software necesarios para implementar juegos dinámicos y con jugadores autómatas.

En la próxima nota crearemos un bot con el primer nivel de inteligencia artificial. Verá como un simple juego de texto se transforma en un proyecto sumamente interesante.

Hasta la próxima.


Descarga el programa04a.bas (sin control de velocidad) desde aquí

Descarga el programa04b.bas (con control de velocidad) desde aquí

DescargaQbasic+DosBox desde aquí


----------------------------------------------------------------------

  Curso Gratuito de Programación de VideoJuegos

  1. Introducción

  2. A quienes va dirigido el curso
  3.Temario del curso / Herramientas
  4.Primer programa (codigo fuente) 
  
  ----------------------------------------------------------------------
 
Share:

No hay comentarios:

Publicar un comentario

Buscar

Popular

Vistas de página en total