Lección 6: Mi primer juego

Requerimientos: Haber leído Lección 5.

En esta lección combinaremos todas las lecciones anteriores para hacer un juego simple.

El juego consiste en un jugador que debe sobrevivir el mayor tiempo posible, sin colisionar con el enemigo.

  • El jugador será nuestro personaje de las lecciones 4 y 5.
  • El enemigo será el cuadrado de la lección 3.
  • El puntaje será el segundero de la lección 2.

Si juntamos lo implementado hasta ahora en las lecciones anteriores, obtenemos el siguiente código:

#include <Geobuino.h>
Geobuino geo;
 
const int ANCHO_BITMAP = 24;
const int ALTO_BITMAP = 24;
 
const unsigned char PROGMEM jugador[] =
{
// ancho, alto,
ANCHO_BITMAP, ALTO_BITMAP,
0x00, 0xfe, 0xfe, 0x8e, 0x76, 0xfa, 0xaa, 0x8a, 0x8a, 0xf6, 0x0e, 0xfe, 0xfe, 0x0e, 0xf6, 0xaa, 0x8a, 0x8a, 0xfa, 0x76, 0x8e, 0xfe, 0xfe, 0x00,
0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xe6, 0xdb, 0xdf, 0xdf, 0xdb, 0xe6, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00,
0x00, 0x7f, 0x7f, 0x78, 0x7a, 0x70, 0x76, 0x76, 0x60, 0x6e, 0x6e, 0x60, 0x6e, 0x6e, 0x60, 0x76, 0x76, 0x76, 0x70, 0x7a, 0x78, 0x7f, 0x7f, 0x00,
};
 
int segundos;
int jugadorX = 70;
int jugadorY = 20;
 
int tamEnemigo = 15;
int enemigoX = 0;
int enemigoY = 0;
int velocidadX = 1;
int velocidadY = 1;
 
 
void setup() {
 segundos = 0;
 geo.begin();
 
}
 
void loop() {
 geo.waitFrame();
 geo.clearDisplay();
 if(geo.framesElapsed(60)){
     segundos = segundos + 1;
 }
 
 //Control del jugador
 if (geo.pressedButton(BTN_LEFT) && jugadorX>0) {
   jugadorX = jugadorX - 1;
 }
 if (geo.pressedButton(BTN_RIGHT)  && jugadorX< (DISPLAY_WIDTH-ANCHO_BITMAP)) {
   jugadorX = jugadorX + 1;
 }
 if (geo.pressedButton(BTN_UP) && jugadorY>0) {
   jugadorY = jugadorY - 1;
 }
 if (geo.pressedButton(BTN_DOWN) && jugadorY< (DISPLAY_HEIGHT-ALTO_BITMAP)) {
   jugadorY = jugadorY + 1;
 }
  //Actualizo posición del enemigo
  enemigoX = enemigoX + velocidadX;
  enemigoY = enemigoY + velocidadY;
  if(enemigoX < 0){
     velocidadX = 1;
   }
  if(enemigoX > (DISPLAY_WIDTH - tamEnemigo)){
     velocidadX = -1;
   }
  if(enemigoY < 0){
    velocidadY = 1;
  }
  if(enemigoY > (DISPLAY_HEIGHT - tamEnemigo)){
    velocidadY = -1;
  }
 
   //muestro en pantalla al enemigo, al jugador y el segundero
  geo.fillRect(enemigoX, enemigoY, tamEnemigo,tamEnemigo);
  geo.drawBitmap(jugadorX, jugadorY, jugador);
  geo.setCursor(0,0);
  geo.print(segundos);
 
}
 

Sin embargo, si ejecutamos el código, vamos a ver que el jugador y el enemigo se pueden superponer uno con el otro.

Deberíamos detectar el choque entre ellos, frenar el contador, y dar por finalizado el juego.

Es por eso que usaremos la función collideRectRect(), para detectar la colisión entre ambos. Dicha función devuelve verdadero cuando los rectángulos enviados como parámetro se tocan en alguno de sus píxeles.

if (geo.collideRectRect(enemigoX,enemigoY, tamEnemigo,tamEnemigo,jugadorX, jugadorY,ANCHO_BITMAP,ALTO_BITMAP)) {
     //finalizar juego
}
 

Para finalizar el juego, vamos a declarar una variable de tipo booleana (puede tomar los valores true o false), la cual va tener el valor false mientras el jugador no haya perdido.

bool juegoTerminado; 

Este variable se inicializará en false en la función setup().

void setup() {
  segundos = 0;
  juegoTerminado=false;
  geo.begin();
}


 

Si ahora en la función loop() tomamos en cuenta el estado de la variable «juegoTerminado», tendríamos:

void loop() {
 geo.waitFrame();
 geo.clearDisplay();
 if(!juegoTerminado){
   //actualizo segundero
   if(geo.framesElapsed(60)){
       segundos = segundos + 1;
   }
 
   //Control del jugador
   if (geo.pressedButton(BTN_LEFT) && jugadorX>0) {
     jugadorX = jugadorX - 1;
   }
   if (geo.pressedButton(BTN_RIGHT)  && jugadorX< (DISPLAY_WIDTH-ANCHO_BITMAP)) {
     jugadorX = jugadorX + 1;
   }
   if (geo.pressedButton(BTN_UP) && jugadorY>0) {
     jugadorY = jugadorY - 1;
   }
   if (geo.pressedButton(BTN_DOWN) && jugadorY< (DISPLAY_HEIGHT-ALTO_BITMAP)) {
     jugadorY = jugadorY + 1;
   }
    //Actualizo posición del enemigo
   enemigoX = enemigoX + velocidadX;
   enemigoY = enemigoY + velocidadY;
 
   if(enemigoX < 0){
     velocidadX = 1;
   }
  
   if(enemigoX > (DISPLAY_WIDTH - tamEnemigo)){
     velocidadX = -1;
   }
   if(enemigoY < 0){
     velocidadY = 1;
   }
  
   if(enemigoY > (DISPLAY_HEIGHT - tamEnemigo)){
     velocidadY = -1;
   }
 
   //muestro en pantalla al enemigo, al jugador y el segundero
   geo.fillRect(enemigoX, enemigoY, tamEnemigo,tamEnemigo);
   geo.drawBitmap(jugadorX, jugadorY, jugador);
 
   //detecto colisión
   if(geo.collideRectRect(enemigoX,enemigoY, tamEnemigo,tamEnemigo,jugadorX, jugadorY,ANCHO_BITMAP,ALTO_BITMAP)){
     juegoTerminado=true;
   }
   geo.setCursor(0,0);
   geo.print(segundos);
  
 }
}
 

Ahora, cuando el jugador pierde, vemos que queda la pantalla en negro. Esto pasa, porque en la función loop() no se hace nada para el caso en que «juegoTerminado» tiene el valor true (verdadero).

Introducimos entonces una variante de la sentencia if:   if – else


if (una condición es verdadera) {

    //hacer algo 

}

else{

  //hacer algo si la condición es falsa

}


En este caso, el código correspondiente al else (si no), se ejecuta cuando no se cumple la condición del if.

Entonces, en nuestro sketch podemos agregar:

else{
   geo.setCursor(40, 20);
   geo.print("Game Over");
   geo.setCursor(60, 40);
   geo.print(segundos);
   
   if(geo.pressedButton(BTN_A)){
     segundos=0;
     juegoTerminado=false;
     enemigoX=0;
     enemigoY=0;
     jugadorX = 70;
     jugadorY = 20;
}
 

En este código estamos diciendo que en caso de perder, se muestre el mensaje “Game Over” junto con los segundos de supervivencia del jugador. Luego se da la posibilidad de reiniciar el juego presionando el botón A.

Se le puede dar un “condimiento” extra al juego, variando la posición inicial del enemigo en el eje y.  Para ello, en el setup(), agregamos;

enemigoY=geo.randomNumber(0,DISPLAY_HEIGHT); 

La función randomNumber() devuelve un número aleatorio mayor o igual al primer paŕametro y estrictamente menor al segundo parámetro. Es este caso, obtendríamos un valor entre 0 y 127 inclusive.

El código final quedaría:

#include <Geobuino.h>
Geobuino geo;
 
const int ANCHO_BITMAP = 24;
const int ALTO_BITMAP = 24;
 
const unsigned char PROGMEM jugador[] =
{
// ancho, alto,
ANCHO_BITMAP, ALTO_BITMAP,
0x00, 0xfe, 0xfe, 0x8e, 0x76, 0xfa, 0xaa, 0x8a, 0x8a, 0xf6, 0x0e, 0xfe, 0xfe, 0x0e, 0xf6, 0xaa, 0x8a, 0x8a, 0xfa, 0x76, 0x8e, 0xfe, 0xfe, 0x00,
0x00, 0xff, 0xff, 0xff, 0xff, 0xfe, 0xfe, 0xfe, 0xfe, 0xe6, 0xdb, 0xdf, 0xdf, 0xdb, 0xe6, 0xfe, 0xfe, 0xfe, 0xfe, 0xff, 0xff, 0xff, 0xff, 0x00,
0x00, 0x7f, 0x7f, 0x78, 0x7a, 0x70, 0x76, 0x76, 0x60, 0x6e, 0x6e, 0x60, 0x6e, 0x6e, 0x60, 0x76, 0x76, 0x76, 0x70, 0x7a, 0x78, 0x7f, 0x7f, 0x00,
};
 
int segundos;
int jugadorX = 70;
int jugadorY = 20;
 
int tamEnemigo = 15;
int enemigoX = 0;
int enemigoY = 0;
int velocidadX = 1;
int velocidadY = 1;
bool juegoTerminado;
 
void setup() {
 segundos = 0;
 juegoTerminado=false;
 geo.begin();
 enemigoY=geo.randomNumber(0,DISPLAY_HEIGHT);
}
 
void loop() {
 geo.waitFrame();
 geo.clearDisplay();
 if(!juegoTerminado){
   //actualizo segundero
   if(geo.framesElapsed(60)){
       segundos = segundos + 1;
   }
 
   //Control del jugador
   if (geo.pressedButton(BTN_LEFT) && jugadorX>0) {
     jugadorX = jugadorX - 1;
   }
   if (geo.pressedButton(BTN_RIGHT)  && jugadorX< (DISPLAY_WIDTH-ANCHO_BITMAP)) {
     jugadorX = jugadorX + 1;
   }
   if (geo.pressedButton(BTN_UP) && jugadorY>0) {
     jugadorY = jugadorY - 1;
   }
   if (geo.pressedButton(BTN_DOWN) && jugadorY< (DISPLAY_HEIGHT-ALTO_BITMAP)) {
     jugadorY = jugadorY + 1;
   }
    //Actualizo posición del enemigo
   enemigoX = enemigoX + velocidadX;
   enemigoY = enemigoY + velocidadY;
 
  
   if(enemigoX < 0){
     velocidadX = 1;
   }
  
   if(enemigoX > (DISPLAY_WIDTH - tamEnemigo)){
     velocidadX = -1;
   }
   if(enemigoY < 0){
     velocidadY = 1;
   }
  
   if(enemigoY > (DISPLAY_HEIGHT - tamEnemigo)){
     velocidadY = -1;
   }
 
   //muestro en pantalla al enemigo, al jugador y el segundero
   geo.fillRect(enemigoX, enemigoY, tamEnemigo,tamEnemigo);
   geo.drawBitmap(jugadorX, jugadorY, jugador);
 
   //detecto colisión
   if(geo.collideRectRect(enemigoX,enemigoY, tamEnemigo,tamEnemigo,jugadorX, jugadorY,ANCHO_BITMAP,ALTO_BITMAP)){
     juegoTerminado=true;
   }
   geo.setCursor(0,0);
   geo.print(segundos);
  
 }else{
   geo.setCursor(40, 20);
   geo.print("Game Over");
   geo.setCursor(60, 40);
   geo.print(segundos);
   if(geo.pressedButton(BTN_A)){
     segundos=0;
     juegoTerminado=false;
     enemigoX=0;
     enemigoY=0;
     jugadorX = 70;
     jugadorY = 20;
   }
 }
}
 

El juego se verá de la siguiente manera en la consola: