Universitat Oberta de Catalunya

Videojuegos con Processing

Videojuegos con Processing

Processing es un lenguaje basado en Java que permite, de forma fácil y rápida, realizar proyectos visuales. Seguramente no es tan óptimo como para crear juegos del nivel de los creados con Unity u otro tipo de motores, pero es una muy buena opción para aprender los conceptos básicos que hay detrás de todo videojuego.

¿Qué es un videojuego?

Podemos definir un videojuego como un juego que se desarrolla en una pantalla y en el que una o más personas interactúan mediante algún tipo de dispositivo electrónico. Los videojuegos se pueden clasificar en las siguientes categorías, aunque dentro de ellas podemos encontrar numerosas subcategorías.

  • De tablero. Son juegos que simulan un juego de tablero convencional. Serían, por ejemplo, el solitario, el parchís o la guerra de barcos. En esta categoría también se pueden englobar juegos más actuales como el Apalabrados, el Triviados o el Candy Crush.
  • Plataformas. Son juegos en los que tenemos que controlar un personaje en tercera persona y hay que superar ciertos obstáculos y luchar contra diferentes enemigos hasta conseguir llegar a un punto que nos permite cambiar de nivel. Este tipo de juegos son, por ejemplo, el Mario Bross, Sonic, etc.
  • Estrategia. Son juegos en los que es necesario realizar una serie de acciones concretas para superar cada nivel. Suelen ser simuladores de civilizaciones del tipo Minecraft, Age of Empires o Sim City. Antiguamente eran en 2D pero, con la potencia actual de las gráficas, actualmente todos son ya en 3D.
  • Colaborativos. Son juegos para jugar en grupo. En general, mezclan la esencia de los juegos de plataformas con los de estrategia y hay que jugar con algún compañero para poder superar con éxito cada nivel. Son, por ejemplo Gears of war, Super Mario 3D World o Little Big Planet.
  • Primera persona. Son juegos en los que controlamos un personaje y visualizamos el escenario a través de sus ojos, aunque en algunos juegos es posible cambiar la vista a tercera persona. Este tipo de juego se hizo muy famoso con la llegada de Wolfenstein 3D y Doom.

Hay un aspecto muy importante que no hay que olvidar, y es que la misión principal de cualquier juego es la de divertir, por lo que, sin duda, el factor más determinante para que un juego guste y tenga éxito es que tenga un buen argumento. De nada sirven espectaculares gráficos si posteriormente el argumento del juego o su jugabilidad son deficientes. Juegos como Angry birds o Candy Crush no tienen grandes gráficos, sin embargo, son juegos que han atrapado a millones de personas. Por ello, cuando se diseña un videojuego es importante plantear un buen argumento en el que haya suficientes retos para que el espectador se sienta atraído por ellos. Otro aspecto imprescindible es que el juego siempre se debe poder resolver: aunque tenga un nivel muy alto de dificultad, el juego debe poder terminarse, en caso contrario el jugador se sentirá engañado y perderá el interés por jugar.

Programando un videojuego

Los primeros videojuegos requerían que su desarrollo fuera en lenguaje ensamblador, ya que la potencia de los procesadores era muy escasa y el único modo de conseguir procesos optimizados era el de programar directamente en lenguaje máquina. Con la llegada de procesadores más potentes, el lenguaje preferido fue C, un lenguaje de alto nivel pero con un gran control sobre la memoria y los procesos del procesador, permitiendo un alto nivel de optimización en el proceso de compilación.

Después, con la llegada de los primeros móviles, se estandarizó mucho el desarrollo de juegos en Java, ya que este lenguaje permitía una gran compatibilidad con muchos tipos de dispositivos. Aunque Java es un lenguaje que no ejecuta código nativo, las últimas máquinas virtuales permiten un alto grado de optimización: hay juegos como Minecraft, que están desarrollados en Java, que aprovechan la compatibilidad con OpenGL para poder representar gráficos avanzados.

En los últimos años, con la llegada de múltiples dispositivos y sistemas operativos, han aparecido plataformas de desarrollo especializadas: la más conocida es Unity, que permite desarrollar código en C# y Unityscript (un derivado de Javascript adaptado al entorno de Unity) y que tiene ciertas similitudes estructurales con Processing, ya que incluye una función Start que seria la análoga a setup y Update que equivaldría a draw.

En este artículo vamos a plantear el desarrollo de dos tipos de videojuegos muy básicos pero que nos permitirán aprender diferentes aspectos comunes a la mayoría de videojuegos, como son las máquinas de estados, gestión de animaciones, recursividad, etc.

Buscaminas

El juego del buscaminas se hizo muy popular con la llegada de Window95. Es un juego simple pero adictivo que consiste en localizar las bombas situadas en un tablero. Cada vez que se pulsa una celda que no contiene una bomba, se muestra el número de bombas adyacentes a esa casilla, de forma que podemos predecir dónde pueden estar cada una de las bombas.

Uno de los conceptos más habituales en cualquier juego es el disponer de una máquina de estados. Básicamente, al definir el juego, definimos los diferentes estados en los que puede estar el juego. Por ejemplo, en este caso tenemos INSTRUCCIONES, JUGANDO, GANADOR, PERDEDOR.

La máquina de estado se inicia en el modo instrucciones, eso significa que mientras esté en ese estado se mostrará una pantalla de bienvenida con las instrucciones del juego. Al pulsar una tecla o el botón de la pantalla, cambiaremos de estado y pasaremos a estado JUGANDO. En este estado se presentará una pantalla formada por una cuadrícula de 10 x 10, en la que cada una de las celdas puede contener una bomba.

El juego se ha estructurado en tres clases:

Buscaminas es la clase que contiene el método de inicialización Setup y el de dibujado draw. Contiene además la máquina de estados.

Tablero, que representa el tablero del juego. Contiene diversas rutinas para controlar el estado del juego.

Celda, el tablero está formado por una rejilla de celdas. Cada celda puede o esconder una bomba o estar vacía.

Por norma, una máquina de estados se implementa como una o más variables de tipo entero que identifican qué momento del argumento o historia se está ejecutando. En nuestro caso la variable se ha denominado estado y permite los estados INSTRUCCIONES, JUGANDO, GANADOR y PERDEDOR.

El punto dónde se controla en qué estado estamos es normalmente la rutina de pintado: al tratarse de una variable entera, la forma más óptima de controlar su valor es mediante la sentencia switch, que es mejor que enlazar diferentes sentencias if – else

Otro de los puntos dónde se controla la máquina de estados es en las rutinas de teclado, ratón o joystick porque, dependiendo de en qué estado estemos, los eventos se omiten.

La clase Tablero es la más importante del juego ya que es la que controla la distribución de las bombas, el control de las celdas, etc. En este artículo voy a hablar de la función principal que utiliza recursividad para gestionar el click del ratón sobre una celda; el resto del código está explicado en detalle en la propia clase, que puede descargarse al final del artículo.

La recursividad es la capacidad que tiene una función de llamarse a sí misma para ejecutar una operación repetitiva, por ejemplo, para calcular el factorial de un número. Se utiliza principalmente para simplificar el código y ahorrar controles sobre el estado de la operación. Otro ejemplo en qué se utiliza mucho es en la generación de fractales, ya que son figuras que se representan al producirse múltiples iteraciones.

¿Por qué es interesante el uso de la recursividad en este control?

Porque nos permite optimizar el código, ya que el control de si está marcada, si tiene una bomba, etc. de cada una de las celdas se hace sólo una vez. El resultado reproduce la acción del usuario al clicar las celdas del tablero. Es muy importante que una función recursiva no sea infinita, ya que si no el sistema se bloqueará. En este caso, al realizar los diferentes retornos antes de ejecutar las llamadas a la misma función, controlan que en algún momento termine. Si una rutina recursiva está mal programada, se agotará la pila de variables (stack) y el sistema acabará lanzando una excepción de error.

La clase Celda es la encargada de pintar cada una de las celdas: a partir de las variables estado, visible y marcada, se controla el tipo de celda y si está o no destapada.

En este primer juego hemos visto varios aspectos importantes a destacar:

  • Organización de los elementos por clases, en las que cada clase representa un objeto y tiene sus propias variables y funciones.
  • Máquina de estados, que controla el estado general del juego mediante una variable de tipo numérico.
  • Recursividad, un recurso de programación que permite escribir código optimizado para la gestión de tareas repetitivas.

Tiro al tanque

Este juego sería una versión conceptualmente muy simplificada de un juego tipo Angry birds, pero en lugar de utilizar pájaros se utilizan proyectiles, y su objetivo es destruir un tanque que se acerca a nuestro cañón. El juego es muy simple pero nos va a permitir estudiar los siguientes conceptos:

  • Animación de objetos
  • Física en los videojuegos
  • Detección de colisiones

Animación de objetos

Podemos definir la animación de un objeto como todo cambio que se produce en la pantalla que simula el movimiento real del objeto que representa. Para que el cerebro interprete un cambio de imagen como un movimiento real (persistencia retinaria) es necesario que estos cambios se produzcan a una velocidad constante, a un mínimo de 12 o 14 frames, aunque las velocidades ideales se sitúan por encima de los 24 frames en el cine o los 30 frames de la televisión. Actualmente, la mayoría de juegos, gracias a las tarjetas gráficas de última generación, permiten velocidades cercanas a los 120 frames por segundo.

Las animaciones se producen a partir de los siguientes cambios:

  • Traslación. Una parte de la pantalla (objeto) se desplaza por la pantalla.
  • Rotación. Un objeto gira sobre un punto de la pantalla.
  • Forma. Un objeto cambia su forma o su morfología.

Los diferentes movimientos pueden combinarse, así, por ejemplo, un personaje caminando es producido por un cambio en la forma y un desplazamiento por la pantalla.

Sprites

Los objetos que se mueven por la pantalla se denominan SPRITES. Se hicieron muy populares en la época de los 80 especialmente con el Commodore 64 y con los MSX: estos miniordenadores incorporaban un chip gráfico con soporte por hardware de Sprites de hasta 16 colores. Actualmente los Sprites se generan por software a partir de un conjunto de imágenes. En este juego, la rana, el tanque y la nube son Sprites: la rana simula una animación completa realizando transformaciones de forma, translación  y rotación.  

Para implementar un Sprite se ha creado una clase que se encarga de realizar el movimiento en cada frame, por lo que una vez creada la clase, se pueden incorporar múltiples objetos en movimiento para crear un juego. Para aplicar animación de forma, es necesario generar una imagen con pequeños cambios por frame.

El archivo rana.png es un archivo PNG con transparencia que está formado por 8 imágenes de 64 x 128. En este caso tiene más altura para realizar el salto (frames 2 y 3).

El procedimiento de pintado es muy simple: se carga en memoria toda la imagen en un objeto Pimage, cada vez que el sistema requiere pintar el objeto, se copia la porción del frame que toca en una imagen intermedia y en la posición indicada. Una vez pintado, se incrementa el número de frame a pintar en la siguiente llamada.

Veamos el código detallado paso a paso:

La parte más importante es el cálculo de la porción a copiar, los frames se numeran de izquierda a derecha y de arriba abajo. Por lo tanto, el frame 5 (rana con los ojos cerrados) es la fila 1 y columna 1. Para calcular la fila y la columna se realizan las siguientes operaciones:

Columna = nframe%nFramesX ->  5 % 4 frames = 1

Fila = nFrame/nFramesX  ->  5 / 4 frames = 1

Para calcular la coordenada X e Y del trozo de la imagen a copiar se multiplica el número de columna calculada por el ancho y la fila por el alto. Por lo tanto, la esquina superior izquierda del frame 5 está ubicada en la posición x= 1*64 y = 1*128, por lo que para pintar el frame copiaremos desde la posición (64, 128) una porción de 64 x 128 píxeles. El frame 2 estaría en la posición (192, 0), etc.

Otro aspecto importante es la realización de transformaciones en Processing. La forma más fácil de entenderlo es pensando que el lienzo de Processing es como una hoja de papel. La hoja está situada paralela en su posición inicial (pushMatrix) pero para dibujar objetos rotados o volteados es más cómodo rotar o voltear la hoja (scale) en lugar de forzar el brazo para dibujar un trazo que queremos rotar. Una vez dibujado en la posición correcta, volvemos a situar el papel en la posición horizontal para dibujar otros objetos (popMatrix). Las transformaciones son acumulativas por tanto, si se realizan traslaciones, escalados o rotaciones, cada operación se hace sobre la transformación existente, por lo que se debe tener en cuenta que es necesario recuperar el estado entre transformaciones para no obtener resultados inesperados.

Física en los videojuegos

La física es un elemento muy importante dentro del mundo de los videojuegos, ya que es la vía para reproducir fenómenos reales en un medio virtual. Cuanto mejor se apliquen las fórmulas físicas en los algoritmos de los programas, más real serán los movimientos en un juego.

Algunas de las aplicaciones de la física en los videojuegos son:

  • Caída de objetos (Ley de la gravedad)
  • Lanzamiento de proyectiles (Tiro parabólico)
  • Sistemas de partículas, que permiten la simulación de humo, precipitaciones, etc.
  • Fricción, por la que los cuerpos que se deslizan tienden a frenarse dependiendo del material de la superficie y el objeto.
  • Restitución. Calcula la capacidad de rebote de un objeto contra otra superficie.

En nuestro juego, concretamente, usaremos el  tiro parabólico, que está compuesto por dos tipos de movimientos, el movimiento rectilíneo uniforme  horizontal y el movimiento rectilíneo uniformemente acelerado vertical, donde la aceleración viene dada por la fuerza de la gravedad. Las variables que intervienen son:

  • Fuerza de la gravedad 9,8 m/s
  • Velocidad inicial (velocidad de lanzamiento m/s)
  • Ángulo de lanzamiento
  • Altura de lanzamiento (en este caso 0 m)

En el juego, el usuario puede determinar los valores de las variables V0 y el ángulo (α), la variable t serán los frames que transcurren con la animación desde el momento del lanzamiento y la variable g es una constante que representa la fuerza de la gravedad con valor 9.8. La rutina que dibuja la bala en cada instante de tiempo es la siguiente:

Detección de colisiones

Una gran mayoría de los juegos deben manejar qué efecto se produce cuando dos objetos se encuentran en un mismo punto. En algunos casos se debe producir un efecto rebote y, en otros casos, una destrucción de uno de los objetos o de ambos. La máxima dificultad es la detección de las colisiones cuando los objetos son irregulares.

Básicamente, hay dos formas de realizar el cálculo:

  • Por área. Los objetos ocupan un área rectangular o circular: cuando las áreas se superponen es que hay colisión. Una mejora de este sistema es dividir el objeto en un conjunto de áreas y analizarlas individualmente. Por ejemplo, una persona puede tener un área circular para la cabeza, una rectangular para el tronco, etc. Es decir, se divide la forma compleja en un conjunto de áreas básicas.
  • Píxel a píxel. Se realiza una detección por área rectangular. Si hay una posible colisión, se analiza a partir de la máscara que indica qué píxeles son visibles: si dos píxeles superpuestos son visibles indica que hay colisión. Este sistema es muy preciso pero tiene un alto coste computacional.

En nuestro caso usaremos el método de áreas en su forma más simple, detectando la colisión con un rectángulo.

Esta función está implementada en la clase Sprite con las funciones dentro y colisionan.

Para la función dentro se ha utilizado un rectángulo para realizar la comparación: en este caso se recibe un punto y se comprueba que esté dentro de los límites del rectángulo.

En cambio, en el caso de colisionan, la comprobación se realiza a partir del centro de los dos sprites: si las áreas se superponen es que hay colisión.

Para realizar una comprobación circular, se debería calcular la distancia entre los centros de los dos objetos con la fórmula de cálculo de Álgebra Euclidiana:

Posteriormente basta con comparar ese valor con la suma de los radios de los dos sprites: si esta es mayor no hay colisión, en caso contrario, los objetos colisionan.

Como hemos podido ver en esta segunda parte del artículo, para crear un videojuego hay que crear un argumento (reglas del juego) y los diferentes personajes y elementos que deben aparecer en la trama. Si los personajes son animados, deben crearse los gráficos para cada posición diferente de la animación. Debe existir un módulo que realice las comprobaciones oportunas sobre si se cumplen las reglas del juego y sobre si la partida ha finalizado. Agregar efectos sonoros mejorará las sensaciones del usuario. Obviamente se debería añadir un nivel mayor de dificultad para destruir el tanque, un contador de tiempo restante, obstáculos para complicar el tiro, etc.

Descarga en este link el código fuente completo del artículo.

Enlaces relacionados

Unity

https://unity3d.com/es

Factorial

http://elvex.ugr.es/decsai/java/pdf/7B-Recursividad.pdf

Excepción de error

https://stackoverflow.com/questions/214741/what-is-a-stackoverflowerror

Persistencia retinaria

https://www.youtube.com/watch?v=3-p7k-y_MnA

Sprites de hasta 16 colores

https://www.c64-wiki.com/wiki/Sprite

Las transformaciones son acumulativas

http://www.mywonderland.es/curso_js/processing/pro_trans.html

Movimiento rectilíneo uniforme

https://es.wikipedia.org/wiki/Movimiento_rectil%C3%ADneo_uniforme

Movimiento rectilíneo uniformemente acelerado

https://es.wikipedia.org/wiki/Movimiento_rectil%C3%ADneo_uniformemente_acelerado

Álgebra Euclidiana 

http://es.wikipedia.org/wiki/Distancia

Otros enlaces

Sprite (videojuegos) – Wikipedia, la enciclopedia libre [Artículo en línea]
<https://es.wikipedia.org/wiki/Sprite_(videojuegos)>
[Fecha consulta : 08/08/2017]

Mosaic | Detección de colisiones con lenguaje de programación Processing [Artículo en línea]
<http://mosaic.uoc.edu/2013/04/24/deteccion-de-colisiones-con-lenguaje-de-programacion-processing/>
[Fecha consulta : 08/08/2017]

Historia de la Animación (1800 – 1900) – YouTube [Artículo en línea]
<https://www.youtube.com/watch?v=3-p7k-y_MnA>
[Fecha consulta : 06/08/2017]

Movimiento parabólico [Artículo en línea]
<http://www.universoformulas.com/fisica/cinematica/movimiento-parabolico/>
[Fecha consulta : 04/12/2015]

Deja un comentario