Gameduino Advance
Introducción al proyecto
Cuando comenzamos a plantearnos de qué queríamos que tratase nuestro proyecto, caímos en el enfoque de planteamiento más común: eficiencia y utilidad. Estuvimos unos días dándole vueltas sin llegar a ninguna conclusión que nos llegase a satisfacer.
Entonces se nos ocurrió darle un cambio completo al enfoque del proyecto. ¿Por qué no en lugar de crear algo que cumpliera las condiciones anteriores no hacemos algo distinto? ¿Por qué no le damos un enfoque lúdico?
Así se nos ocurrió la idea de crear una consola. Pero claro, debido a los recursos con los que contábamos estábamos bastante limitados. Así pues, decidimos crear una especie de consola portátil.
Y nació la idea de Gameduino Advance.
Hardware
Dado que la dificultad del proyecto realmente fue el software, el apartado de hardware no tiene un contenido excesivo. Los materiales utilizados fueron:
- 1 Botón
- Cables varios
- 1 potenciómetro
- 1 pantalla lcd
- 1 joystick
- Placa de arduino
- Buzzer
- 1 resistencia de 10k ohmios
- 2 resistencias de 20k ohmios
Como apuntes importantes, el potenciómetro es usado para modular el contraste de la pantalla lcd de forma que pueda verse de forma óptima y la resistencia utilizada para el joystick es la de 10k ohmios.
Y la implementación del circuito quedaría algo así, siendo los cables rojos las conexiones al voltaje, los negros a tierra, el amarillo el del potenciómetro con respecto a la pantalla y el resto los cables de señal:
Software
El código del proyecto ha sido bastante extenso debido a la complejidad de implementar juegos con varios elementos que debían procesarse a distintos ritmos y en ocasiones a la vez. En total el proyecto cuenta con 581 líneas de código. Obviando el funcionamiento más simple como el menú, el uso de los controles o de la pantalla lcd casi toda la complejidad del proyecto residía en mantener un framerate que no incomodara al jugador mientras manteníamos la información fiable para proporcionar una experiencia de juego agradable y encontrar los tiempos de actualización correctos para cada función de tal manera que en caso de ser necesaria una ejecución paralela se pudiera resolver de manera secuencial con el timing correcto.
BLOQUES:
- ESPECIFICACION DE VARIABLES:
En este bloque encontramos todas las variables necesarias para el funcionamiento de la práctica. Entre las variables generales encontramos la localización de los pines, los valores de los input y las variables comunes a los dos juegos implementados. Después tendríamos variables específicas para cada uno de los juegos, teniendo sustancialmente más variables el juego «impostor invader» respecto a «sus jump» debido a su mayor complejidad y la cantidad de objetos necesarios. Por último, tenemos los arrays de bits que representaran más tarde nuestros «sprites» dentro de los juegos.
- SETUP:
Aquí es donde empieza la ejecución propiamente dicha del proyecto, en el setup se inicializa la pantalla lcd, el buzzer, los caracteres que se dibujaran a partir de los mapas de bits previamente especificados con «lcd.createChar();», las variables temporizadoras con «millis();» y por último el Serial. Una vez ejecutado saltamos al bloque Menú.
- MENU:
El propósito de este bloque es de servir de entrada al bloque Loop ya que los inputs registrados al final de la partida de cada juego al volver al menú podían ser registrados antes de renderizar de nuevo la selección de juego y creando así una sensación incomoda en el jugador al no entender que ha ocurrido. En este bloque simplemente bloquearemos durante unos milisegundos la entrada y mostraremos una pequeña explicación del menú antes de saltar al Loop.
- LOOP:
Aquí es donde el jugador seleccionara el juego que quiera con el uso del «JoyStick» moviéndolo a izquierda o derecha, al solo tener dos juegos implementados se alternara de uno a otro de manera sencilla, si hay un modo de juego seleccionado y se mueve el «JoyStick» alternara el juego seleccionado y renderizara la nueva selección. Primero se comprueba si quiere cambiar la selección y después si ha pulsado el botón para acceder al juego, una vez lo haga se lanzará el juego seleccionado.
- SUS JUMP:
El primer juego implementado y el más simple, cuenta con solo dos objetos, el primero, nuestro personaje «amogus» y los cactus que deberá saltar.
- Variables:
· int score: puntos adquiridos durante la partida.
· bool gameOver: estado del juego (si es «false» no ha acabado).
· int cactusPosition: posición del objeto cactus;
· bool amogusUp: estado del salto del personaje (si es «false» no está saltando).
· int cactusDelay: delay con el que se moverá el cactus.
· int cactusDecrease: cantidad que descenderá el delay con cada nivel.
· int level: nivel en el que nos encontramos.
- Funcionamiento:
Antes de empezar se inicializan las variables con los valores correspondientes. El bucle del juego se realiza en un bucle «while(!gameOver)» que se ejecutara mientras «gameOver» no sea «true». Con cada ciclo del bucle comprobamos el estado de todos los objetos del juego y solo actualizamos los que hayan pasado su temporizador correspondiente, este funciona de la siguiente manera, tenemos una variable temporizadora correspondiente al objeto y cada vez que lo actualizamos su valor cambia al tiempo actual desde el inicio de la placa con «millis()», de esta manera solo entraremos a actualizarlo cuando la resta del «millis()» actual menos la variable temporizadora sea mayor que la variable delay correspondiente. Este método es el que nos permite tener varios objetos ejecutándose simultáneamente de manera secuencial además de mantener los ritmos adecuados para el juego.
Explicado esto el funcionamiento de cada objeto es sencillo, en cada actualización el cactus se mueve a la izquierda tanto en la pantalla como su variable posición, las actualizaciones del «amogus» suceden mucho mas frecuentemente y son las que permiten saltar (saltar tiene su propio delay para que no se pueda saltar de manera permanente) y las que actualizan el «sprite» según su situación (suelo o aire), la actualización del salto propiamente dicho, es decir cuanto debe durar saltando, que al actualizarse vuelve al suelo, y por último la actualización de la puntuación que comprueba si el «amogus» está en el aire cuando un cactus está en su posición.
En caso negativo se acaba el juego y en caso afirmativo aumenta la puntuación. Si la puntuación es múltiplo de 5, o dicho de otra manera cada 5 cactus, aumenta el nivel. Esto implica que el delay del objeto cactos desciende en base a la variable decrease hasta un límite donde no aumentara más la dificultad (con la configuración por defecto que tenemos el nivel máximo se alcanzaría a los 25 saltos, es decir el nivel 5). Una vez perdiéramos iríamos a la sección de la pantalla de fin de juego, donde se mostraría nuestra puntuación obtenida y al pulsar el botón volveríamos a Menú.
- IMPOSTOR INVADER:
Nuestro segundo juego implementado y donde más complicaciones tuvimos en el manejo de los distintos objetos, teniendo la nave espacial, 3 obstáculos, los obstáculos acumulados y la bala.
- Variables:
· int spaceshipPosition[]: posición de la nave. [0] = Posición en X, [1] = Posición en Y.
· int obstacle1Position[]: posición del obstáculo 1. [0] = Posición en X, [1] = Posición en Y.
· int obstacle2Position[]: posición del obstáculo 2. [0] = Posición en X, [1] = Posición en Y.
· int obstacle3Position[]: posición del obstáculo 3. [0] = Posición en X, [1] = Posición en Y.
· int obstaclesAcumulated[]: obstáculos acumulados arriba y abajo. [0] = Obstáculos acumulados arriba, [1] = Obstáculos acumulados abajo.
· int bulletPosition[]: posición de la bala. [0] = Posición en X, [1] = Posición en Y.
· bool firedUp: estado de la bala del juego.
· int obstacleDelay[]: delays de los distintos objetos. [0] = Delay obstáculo 1, [1] = Delay obstáculo 2, [1] = Delay obstáculo 3.
· int obstacleDecrease: cantidad que descenderá el delay de los objetos.
- Funcionamiento:
El funcionamiento de este juego, aunque considerablemente más complejo que el de «Sus Jump» contiene las mismas estructuras y bases. El bucle que ejecuta el juego funciona exactamente igual y cada objeto se actualiza con su propio temporizador teniendo esta vez 3 obstáculos diferenciados.
Debido a los problemas que causaba el tener tantos objetos que actualizar no podíamos usar (display.clear()) ya que cada objeto tenia tiempos de actualización muy distintos y creaba un efecto de refresco en la pantalla muy incómodo por eso cada objeto se encarga de borrar su posición anterior y escribirla en la nueva.
En el caso de la actualización de los objetos aparte de moverse comprueban su colusión contra la nave y si se ha dejado pasar se acumula al final de la pantalla y se reinicia el obstáculo llevándolo de nuevo al comiendo de la pantalla lcd, es decir la posición 15 y aleatorizando si va a estar arriba o abajo. La actualización de la nave se basa en la posición del «JoyStick» si está centrado se redibuja la nave en la misma posición, y si movemos el joystick en cualquier dirección se moverá hacia ella si hay espacio disponible, es decir si está arriba no podrá seguir subiendo solo podrá bajar, el único movimiento distinto es hacia la izquierda que aparte de moverse comprueba si te has cochado contra los obstáculos acumulados y por último comprobamos también si la nave se ha chocado contra algún asteroide al moverse.
Las colisiones se comprueban varias veces debido a que como cada objeto se mueve con un delay distinto es posible que las colisiones no se detectaran si solo lo hicieran por una parte del colisionador / colisionado, es decir, es mejor comprobar si la nave se ha chocado al moverse y si el asteroide se ha chocado al moverse que solo comprobar una ya que podría no detectarse.
Para disparar tendremos que pulsar el botón y no haber ningún disparo en pantalla, es decir solo podemos disparar una vez cada vez, una vez disparamos inicializamos la posición del disparo y este continuara actualizándose por su cuenta.
La actualización del disparo es la que más veces se realiza ya que se mueve muy rápido, aparte de moverse comprueba si se ha chocado con un asteroide, en cuyo caso lo destruye y aumenta en 1 la puntuación sea cual sea el obstáculo, en este caso no es necesario comprobar varias veces las colisiones de la bala ya que al actualizarse tanto es muy complicado que se salte la colisión ya que solo tendría 50ms para ocurrir.
Cada vez que damos a un obstáculo este se reinicia y la bala también permitiendo disparar de nuevo. Igual que con «Sus Jump» la dificultad aumenta cada 5 asteroides reduciéndose el delay de todos los obstáculos en relación a la variable decrease hasta un mínimo de 200ms.
Perdemos si chocamos contra cualquier obstáculo y se nos presentaría la misma pantalla de fin de juego que en «Sus Jump» reiniciándose el ciclo al volver al menú.
- EFECTOS DE SONIDO:
A lo largo de todo el proyecto hemos incluido efectos de sonido para mejorar la experiencia del jugador, estos sonidos suenan en distintos momentos:
– Al cambiar la selección de juego
– Al saltar en «Sus Jump»
– Al disparar en «Impostor Invader»
– Al golpear un obstáculo en «Impostor Invader»
– Al perder la partida
Casi todos los efectos tienen esta estructura: (En este caso el tono duraría 200ms)
for(int i=2000;i>1800;i–){
tone(buzzer,i);
delay(1);
}
Un problema que plantean estos efectos de sonido es que al ser arduino secuencial si tenemos un sonido que dura 100ms durante ese tiempo no se ejecutara nada más que ese sonido, por esa razón los efectos son escasos y solo ocurren en ciertas ocasiones de maneras muy breves ya que podrían dañar más la experiencia del usuario que la mejora que aportan, este punto negativo es especialmente notable en «Impostor Invader» cuando destruimos un objeto ya que la nave quedara quieta durante unos milisegundos, no consideramos que fuese especialmente molesto además de que aportaba más feedback al jugador así que lo mantuvimos.
El único efecto distinto al resto es al perder la partida ya que es un sonido más duradero, casi como una melodía corta, esto es así ya que durante ese tiempo no afecta que no se puedan realizar más acciones.
Debido al tamaño excesivo del código, no podemos subir el archivo completo a la página.