6. RECOGIDA DE TESOROS.

Tenéis el proyecto en: Tutorial de programación c++ (6/11)

Vamos a pensar de nuevo lo que hemos hecho hasta este momento. Siguiendo unos pasitos prefijados hemos creado una plantilla básica con una estructura, hemos comprobado que funcionan los modos de juego, hemos diseñado un sencillo menú y hemos dotado a la nave de movimiento.

Todo esto me ha costado 3 o 4 horas (mientras veía la tele), así que no parece complicado echar a andar un sencillo juego si tienes claro lo que necesitas, ¿no? Si en el próximo concurso de videojuegos de gp32spain te dan un mes de plazo, ya sabes que puedes entregar 30 juegos.

Lo que tenemos que hacer ahora es otra parte importante del juego: cuando nuestro batiscafo llegue al fondo del mar y se pose sobre un tesoro, éste se le pegará a las patas y podremos llevarlo de vuelta al barco nodriza. No empecéis a pensar que esto es difícil... si habéis llegado hasta aquí, lo que queda es cuesta abajo hasta dejar el juego a nuestro gusto.

¡Vamos allá!

Código:
struct gold_box
{
  int x;
  int y;
  int exist;
  int carried;
};
Lo que vamos a hacer justo después de los defines es crear una estructura para los cofres de oro. Esta estructura contiene 4 variables: posición X, posición Y, una para saber si existe (si existe se dibuja, si la dejamos en el barco deja de existir y no se dibuja), y otra para saber si está enganchada al submarino.

Más o menos debéis saber lo que es una estructura: es un tipo de datos (como int, solo que te permite almacenar varios datos. Así tenemos una única variable con todos los datos del tesoro.

Esto podríamos haberlo hecho con la nave, ¿no? Por ejem:

struct nave
{
int x;
int y;
float ah;
float av;
};

Pues sí... pero si queréis hacer vuestro código más bonito es cosa vuestra. Ya veremos en otros tutoriales más adelante cómo utilizar clases para hacer que los bichos se muevan solos. Y sobre todo, meternos a fondo en los estados. Pero sigamos con lo nuestro.

Código:
///////////////////////////////////
/*  Variables del juego          */
///////////////////////////////////
int level=0;
int score=0;
int ship_load=0;
gold_box gold_list[4];
En las variables globales vamos a añadir 4 datos más. level indica en qué nivel estamos. Empezaremos en el 1, y cada vez que recojamos los 4 tesoros, avanzaremos de nivel. score son los tesoros que hemos recuperado. ship_load indica si el batiscafo va cargado con un tesoro. Y gold_list es una lista de 4 variables de tipo gold_box, la estructura que hemos visto antes. Estas 4 variables son los 4 cofres que hay en el fondo.

Código:
void new_level()
{
  // creamos los tesoros
  for (int i=0; i<4; i++)
  {
    gold_list[i].x=28+80*i;
    gold_list[i].y=228;
    gold_list[i].exist=1;
    gold_list[i].carried=0;
  }
  // aumentamos el nivel
  level++;
}
Creamos una nueva función new_level() a la que llamaremos cada vez que iniciemos un nivel. Lo que hará esta función es incrementar la variable level, que indica el nivel en el que estamos y crear 4 nuevos tesoros en el fondo del mar. Así que hacemos un bucle para recorrer la lista gold_list y actualizamos los datos de cada tesoro. Los tesoros estarán en la posición y=228, que es el fondo marino. En su posición x empezarán desde 28 y tendrán una distancia de 80 pixels entre si. Ponemos exist=1 para que se dibuje y lo tengamos en cuenta en las acciones y carried=0, para indicar que está suelto en el fondo del mar.

Código:
void reset()
{
  level=0;
  score=0;
  ship_load=0;
En la función reset() añadimos estas líneas. Esta función se llama al iniciar la partida, así que pondremos los puntos a 0, el level a 0 (se pondrá a 1 porque justo después llamaremos a new_level()) y ship_load la ponemos a 0 para indicar que la nave no transporta ningún tesoro.

Código:
void read_menu_keys()
{
...
        switch (menu_selection)
        {
        case 0:
          reset();
          new_level();
          program_mode=PROGRAM_MODE_GAME;
          break;
Como os he dicho antes, cuando iniciamos una partida desde el menú, primero llamamos a reset() y justo después a new_level(), así que añadimos esta llamada en la función read_menu_keys().

Código:
void draw_game()
{
...
  // dibujamos los cofres de oro
  SDL_Rect rgold;
  for (int i=0; i<4; i++)
  {
    if (gold_list[i].exist)
    {
      rgold.x=gold_list[i].x;
      rgold.y=gold_list[i].y;
      if (gold)
        SDL_BlitSurface(gold,NULL,screen,&rgold);
    }
  }
En la función draw_game() vamos a dibujar los tesoros en las posiciones que indican las variables de cada estructura. Así que recorremos la lista gold_list (de longitud 4), y rellenamos la variable rgold con la posición de cada tesoro. Si el tesoro existe lo dibujamos en su posición x,y (almacenada ahora en rgold). No olvidaros de comprobar que la superficie gold existe para que la dibujar con SDL_BlitSurface() no de error.

Tampoco olvidéis que el orden de dibujado es importante. Lo último en dibujarse aparece encima de lo demás, así que primero dibujamos los tesoros y después el submarino. Aunque nunca deberían solaparse, pero es mejor tenerlo en cuenta.

Código:
  // dibujamos los textos
  char txt[20];
  sprintf(txt,"Nivel: %i",level);
  draw_text(screen,txt,10,5,0,0,0);
  sprintf(txt,"Puntos: %i",score);
  draw_text(screen,txt,250,5,0,0,0);
Y para acabar la función de dibujado, vamos a crear un bonito y minimalista marcador. En la parte superior de la pantalla, a la izquierda escribiremos el nivel y a la derecha los puntos. Lo que hace sprintf() es crear una cadena de texto en la variable char txt[20]; mezclando texto y números. draw_text() dibuja en pantalla esa variable.

Código:
void update_game()
{
...
  // comprueba si coge un tesoro y actualiza el que lleva encima
  for (int i=0; i<4; i++)
  {
    if (gold_list[i].exist)
    {
      if (gold_list[i].carried)
      {
        gold_list[i].x=ship_x;
        gold_list[i].y=ship_y+24;
      }
      if (!ship_load && ship_y>=204 && ship_x>gold_list[i].x-4 && ship_x<gold_list[i].x+4)
      {
        gold_list[i].carried=1;
        ship_load=1;
      }
    }
  }
Ahora tenemos que comprobar en update_game() si cojemos los tesoros y los llevamos al barco. Empezamos con la recogida. Recorremos la lista de 4 tesoros gold_list y comprobamos si existen. En caso de que exista comprobamos si lo llevamos en el submarino con if (gold_list[i].carried). Si es el caso, actualizamos la posición del tesoro y lo ponemos justo debajo del submarino, o sea, en la misma posición x, en la posición y+24 (porque el submarino mide 24 de alto).

Lo segundo es comprobar que el submarino no está cargado con otro tesoro, con if (!ship_load, y si además la nave está posada en el fondo (ship_y>=204) y la distancia entre el tesoro y el submarino es menor de 4 pixels. Entonces ponemos la variable carried de ese tesoro a 1, y ship_load a 1 (el tesoro es transportado y la nave está cargada).

Código:
  // comprueba si deja el tesoro en el barco
  if (ship_load && ship_y<=48 && ship_x>=136 && ship_x<160)
  {
    for (int i=0; i<4; i++)
    {
      if (gold_list[i].carried)
      {
        gold_list[i].carried=0;
        gold_list[i].exist=0;
        ship_load=0;
        score++;
      }
    }
  }
Ahora tenemos que comprobar si hemos dejado un tesoro en el barco. Si vamos cargados (ship_load) y nuestra posición vertical es a nivel del mar ship_y<=48 y la posición horizontal es justo debajo del barco (entre 136 y 160), entonces recorremos la lista gold_list para ver qué tesoro es el que llevamos. Cuando lo encontremos (el que tiene la variable carried a 1), ponemos sus variables carried y exist a 0 (para que no se dibuje), descargamos la nave (ship_load=0) y aumentamos en 1 los puntos (la variable score).

Código:
  // si hemos cogido todos los tesoros, pasamos a un nuevo nivel
  int boxes=0;
  for (int i=0; i<4; i++)
  {
    if (!gold_list[i].exist)
      boxes++;
  }
  if (boxes==4)
    new_level();
Ya sabéis que si cogemos todos los tesoros tenemos que pasar a un nuevo nivel y crear otros tesoros nuevos. Así que lo comprobamos a continuación. Vamos a sumar en la variable boxes cuántos tesoros no existen. Así que recorremos de nuevo la lista gold_list, y si la variable exist de cada tesoro está a 0, aumentamos en 1 la variable boxes.

Si al finalizar, boxes vale 4 significa que hemos cogido todos los tesoros, así que llamamos a la función new_level().

Código:
void draw_pause()
{
  draw_game();
  draw_text(screen,"Pausa",140,110,255,255,0);
}
Y para finalizar, en la función draw_pause() vamos a hacer un pequeño cambio muy chulo. Vamos a mejorar el aspecto de esta pantalla. Hasta ahora, si le dábamos a la pausa, la pantalla se quedaba en negro con el mensaje de pausa en mitad de la pantalla. Si justo antes del mensaje llamamos a la función draw_game(), se dibujará la pantalla del juego en el momento de darle al botón de pausa. La cosa es que se queda en pausa, ¿por qué? Porque los movimientos se actualizan en update_game() y nosotros no la estamos llamando. Esta es una de las ventajas de que cada función haga exclusivamente lo que tiene que hacer. Si hubiéseis metido en draw_game() alguna comprobación de movimiento, ahora mismo podríais tener un caos de pantalla.

Si el juego compila y lo probáis, podréis bajar hasta los tesoros y devolverlos de nuevo al barco nodriza. Aprovechad este momento, porque cuando aparezcan los bichos esto será un infierno marino.

Y no lo olvidéis, probad, cambiad y experimentad... ¿queréis más tesoros? ¿una nave más lenta? ¿tesoros con diferente valor? Todo eso y más podéis conseguirlos con unos ligeros retoques... ¡adelante!