[Tutorial] Snake en C++/SDL

Iniciado por kaltorak, 13 Agosto 2013, 04:49 AM

0 Miembros y 1 Visitante están viendo este tema.

kaltorak

SNAKE
EN C++/SDL




Hola a todos

He creado este manual, por petición de Dato000 miembro del grupo de desarrollo de videojuegos al que pertenezco,  en el voy a realizar una versión simple del videojuego Snake(o serpiente), usando C++ y la librería gráfica SDL, veréis que con apenas 280 lineas de código, contando espacios y comentarios se puede hacer el videojuego Snake(o serpiente) y que no es tan difícil como parece.

Espero que este manual le sea de ayuda a la gente que se apunto al reto y no pudo resolverlo, o para todo aquel que alguna vez a querido realizar el videojuego Snake(o serpiente) y no ha sido capaz.

Voy a empezar poniendo el código completo, para después ir explicándolo  linea a linea, doy por sentado que el lector de este manual tiene una mínima base de programación en C o C++.

Código (cpp) [Seleccionar]

//Juego Snake realizado por kaltorak para el manual Snake en SDL/C++

#include <cstdlib>
#include <vector>
#include <time.h>
#include <SDL/SDL.h>

//Prototipo de la funcion Colision.
int Colision(SDL_Rect,SDL_Rect);

//Prototipo de la funcion Iniciar.
void Iniciar(void);

/*definimos la direcciones que pude tomas la seriente con esto conseguimos que el codigo sea mucho mas
 mas comprensible*/
#define Arriba 1
#define Derecha 2
#define Abajo 3
#define Izquierda 4

/*Declaramos e inicializamos como constantes el ancho, el alto y la profundidad de color
 de la ventana principal.*/
const int ResolucionX = 640;
const int ResolucionY = 480;
const int PColor = 32;

const int DELAY = 85;

//Declaramos e inicializamos la varible LongitudSerpiente la cual contendra el tamaño de la serpiente.
int LongitudSerpiente = 0;

/*Declaramos e inicializamos la variable Direccion que contendra la direccion en la que se esta
 moviendo la Serpiente.*/
int Direccion = 0;

//Declaramos e inicializamos la variable control para controlar que la manzana no coincida con al cola.
int Control = 0;

/*Declaramos la variable memoria del tipoc SDL_Rect donde almacenaremos la posion anterior anterior
 de la cabeza de la serpiente para poder mover la cola*/
SDL_Rect Ultimo;

//Declaramos e inicializamos la variable Puntos que contendra los puntos del juego.
int Puntos = 0;

//Delcaramos e inicializamos la variable Titulo que contendra el titulo de la ventana y la puntuacion.
char Titulo[255];

using namespace std;

/*Declaramos la estructura Cuadro que sera la encargada de almacenar la posicion
 y el tamaño de cada una de las partes que formaran el cuerpo de la Serpiente*/
struct Cuadro
{
  SDL_Rect Posicion;
  Cuadro(){Posicion.x = 0;Posicion.y = 0;Posicion.w = 20;Posicion.h = 20;}
}Manzana;

//Declaramos el Vector Serpiente del tipo Cuadro.
vector <Cuadro> Serpiente;

int main ( int argc, char** argv )
{

   srand(time(NULL));

   //Inicializamos el modo de video de las SDL y comprobamos que se inicialize bien.
   if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
    {
     printf( "Imposible iniciar la libreria SDL: %s\n", SDL_GetError() );
     return 1;
    }

   //Itroducimos SDL_Quit en atexit para que se inice al finalizar el programa.
   atexit(SDL_Quit);

   /*Creamos la Surface principal del juego, la que se va a mostrar en pantalla.
     y comprobamos que se inicie correctamente.*/
   SDL_Surface* PantallaV = SDL_SetVideoMode(ResolucionX, ResolucionY, PColor,SDL_HWSURFACE|SDL_DOUBLEBUF);
   if ( !PantallaV )
    {
     printf("Imposible crear la ventana Principal: %s\n", SDL_GetError());
     return 1;
    }

   //Llamamos a la funcion Iniciar par poner todos los valores a cero.
   Iniciar();

   // program main loop
   bool done = false;
   while (!done)
   {
       // message processing loop
       SDL_Event event;
       while (SDL_PollEvent(&event))
       {
           // check for messages
           switch (event.type)
           {
               // exit if the window is closed
           case SDL_QUIT:
               done = true;
               break;

               // check for keypresses
           case SDL_KEYDOWN:
               {
                   // exit if ESCAPE is pressed
                   if (event.key.keysym.sym == SDLK_ESCAPE)
                    {
                     done = true;
                     break;
                    }
                   if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo)
                    {
                     Direccion = Arriba;
                     break;
                    }
                   if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba)
                    {
                     Direccion = Abajo;
                     break;
                    }
                   if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha)
                    {
                     Direccion = Izquierda;
                     break;
                    }
                   if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda)
                    {
                     Direccion = Derecha;
                     break;
                    }
               }
           } // end switch
       } // end of message processing

       // DRAWING STARTS HERE

       if(Direccion != 0)
        {
         //Almacenamos la posicion del ultimo de los elementos que forman la cola en la variable Ultimo.
         Ultimo.x = Serpiente[LongitudSerpiente].Posicion.x;
         Ultimo.y = Serpiente[LongitudSerpiente].Posicion.y;

         //Movemos la cola de la serpiente.
         for(int I = LongitudSerpiente; I >= 1; I--)
          {
           Serpiente[I].Posicion.x = Serpiente[I-1].Posicion.x;
           Serpiente[I].Posicion.y = Serpiente[I-1].Posicion.y;
          }
        }

       if(Direccion == Arriba)
        {
         Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h;
        }
       else if(Direccion == Abajo)
        {
         Serpiente[0].Posicion.y += Serpiente[0].Posicion.h;
        }
       else if(Direccion == Derecha)
        {
         Serpiente[0].Posicion.x += Serpiente[0].Posicion.w;
        }
       else if(Direccion == Izquierda)
        {
         Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w;
        }

       //Comprobamos la colision de la serpiente con el borde.
       if(((Serpiente[0].Posicion.x + Serpiente[0].Posicion.w)  > ResolucionX)
            || (Serpiente[0].Posicion.x < 0)
            || ((Serpiente[0].Posicion.y + Serpiente[0].Posicion.h)  > ResolucionY)
            || (Serpiente[0].Posicion.y < 0))
        {
         Iniciar();
        }

       //Comprobamos la colision de la serpiente con la cola.
       for(int I = 1; I <= LongitudSerpiente; I++)
        {
         if(Colision(Serpiente[0].Posicion,Serpiente[I].Posicion))
          {
           Iniciar();
          }
        }

       //Comprobamos la colision de la serpiente con la Manzana.
       if(Colision(Serpiente[0].Posicion,Manzana.Posicion))
        {
         LongitudSerpiente++;
         Puntos += 10;
         Serpiente.push_back(Cuadro());
         Serpiente[LongitudSerpiente].Posicion.x = Ultimo.x;
         Serpiente[LongitudSerpiente].Posicion.y = Ultimo.y;

         //Metemos en la varible Titulo el titulo de la ventana seguido de la puntuacion del juego.
         sprintf(Titulo,"Snake -- Puntos: %d",Puntos);

         /*Posicionamos denuevo la manazana en pantalla y comprobamos que la nueva ubicacion
           no este ocupada por la cola de la Serpiente.*/
         do
          {
           Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);
           Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
           Control = 0;
           for(int I = 1; I <= LongitudSerpiente; I++)
            {
             if(Colision(Manzana.Posicion,Serpiente[I].Posicion))
              {
               Control = 1;
               I = LongitudSerpiente;
              }
            }
          }
         while(Control == 1);
        }

       //Borramos la surface principal
       SDL_FillRect(PantallaV, 0, SDL_MapRGB(PantallaV->format, 0, 0, 0));

       //Pintamos la serpiente en la Surface principal
       for(int I = 0; I <= LongitudSerpiente; I++)
        {
         SDL_FillRect(PantallaV,&Serpiente[I].Posicion,SDL_MapRGB(PantallaV->format, 255, 255, 255));
        }

       //Pintamos la manzana en la Surface principal
       SDL_FillRect(PantallaV,&Manzana.Posicion,SDL_MapRGB(PantallaV->format, 0, 255, 0));

       //Cambiamos el titulo de la ventana por Snake.
       SDL_WM_SetCaption (Titulo, NULL);

       //Mostramos la Surface principal en pantalla.
       SDL_Flip(PantallaV);
       SDL_Delay(DELAY);
   } // end main loop


   return 0;
}

//Funcion que compruba las colisiones entre los elementos de juego.
int Colision(SDL_Rect H,SDL_Rect M)
{
   if (((H.x + H.w) > M.x) && ((H.y + H.h) > M.y) &&
           ((M.x + M.w) > H.x) && ((M.y + M.h) > H.y))
   {
       return 1;
   }
   else
   {
       return 0;
   }
}

//Funcion que reinicia el juego cuando la serpiente colisiona con ella misma o el borde.
void Iniciar(void)
{
 /*Ponemos el valor de la variable longitudSerpiente a 0 para que la serpiente solo este formada por
   la cabeza.*/
LongitudSerpiente = 0;
 //Ponemos el valor de la variable Direccion a 0 para que la serpiente aparezca parada al comenzar el juego.
 Direccion = 0;
 //Ponemos el valor de la variable Puntos a 0 para reiniciar el marcador de puntos.
 Puntos = 0;
 //Metemos en la varible Titulo el titulo de la ventana seguido de la puntuacion del juego.
 sprintf(Titulo,"Snake -- Puntos: %d",Puntos);
 //Borramos todos los miembros que forman el vector Serpiente
 Serpiente.clear();
 //Añadimos un miembro en el vector serpiente que contendra la cabeza de la serpiente.
 Serpiente.push_back(Cuadro());
 //Posicionamos aleatoriamente la manzana en la pantalla.
 Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);
 
 Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
 //Posicionamos la cabeza de la serpiente en el centro de la pantalla.
 Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w);
 Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h);
}


Para el que no lo conozca el videojuego Snake (o serpiente)  comentaros que fue lanzado a mediados de los 70 y ha mantenido su popularidad desde entonces, convirtiéndose en un clásico tras su salida en 1998 en todos los dispositivos móviles de Nokia.

Su argumento es muy sencillo pero enormemente adictivo, el jugador controla una larga y delgada criatura semejante a una serpiente, de hay el nombre del videojuego, que vaga por un plano delimitado por paredes, donde debe evitar a toda costa chocar contra las paredes o consigo misma mientras come manzanas las cuales la hacen crecer, lo cual complica el juego a medida que la serpiente va creciendo debido a la ingesta de las mencionadas manzanas, si esto no fuera suficiente la serpiente una vez que comienza a moverse no puede ser para por el jugador, este se tiene que limitar a cambiar el sentido de la marcha de la suso dicha serpiente mediante las flechas de dirección, para que se coma las manzanas y evite chocar.

Comencemos a analizar el código.

Comenzamos incluyendo las librerías que vamos a necesitar para el buen funcionamiento del código.

La librería estándar "cstdlib "

Código (cpp) [Seleccionar]
#include <cstdlib>

La librería "vector" para la creación de una lista de vectores, que controlen el tamaño de serpiente.

Código (cpp) [Seleccionar]
#include <vector>

La librería "time.h" para generar aleatoriamente la posición en la que va a aparecer la manzana en el juego, cada vez que esta sea devorada por la serpiente.

Código (cpp) [Seleccionar]
#include <time.h>

Y por ultimo la librería "SDL.h" la cual vamos usar para manejar el entorno gráfico y  las pulsaciones del teclado.

Código (cpp) [Seleccionar]
#include <SDL/SDL.h>


Las siguiente lineas de código son los prototipos de las funciones "Colision"  y "Iniciar", de las cuales explicare su funcionamiento mas adelante.

Código (cpp) [Seleccionar]
int Colision(SDL_Rect,SDL_Rect);

void Iniciar(void);


Ahora vamos a usar la directiva "#define" para crear unas macros, las cuales nos ayudaran a entender mejor el código.
Lo que estas macros hacen es que trabajemos con las direcciones reales en las que se mueve la serpiente y no números pues tras un tiempo sin usar el código no sabríamos que significaba cada numero.

Mirar a la dirección Arriba le adjudicamos el numero 1, lo que hace esta macro es que en el código podemos poner Arriba y sabremos que significa 1, después el preprocesador cambiara las palabra Arriba por un 1 antes de compilar el código, como bien he dicho esto es solo por limpieza y para mejorar la compresión del código para futuras modificaciones o para que sea comprensible para otra persona que no sea el programador del mismo.

Código (cpp) [Seleccionar]
#define Arriba 1
#define Derecha 2
#define Abajo 3
#define Izquierda 4


Tras estas lineas vamos a definir el ancho y alto de la pantalla principal así como la profundidad de color de la misma.

Código (cpp) [Seleccionar]
const int ResolucionX = 640;
const int ResolucionY = 480;
const int PColor = 32;


Ahora vamos a definir el retardo en milisegundos que usaremos para que el programa funcione mas o menos igual en todas las maquinas aunque sean las rápidas

Código (cpp) [Seleccionar]
const int DELAY = 85;

Declaramos e inicializamos la variable LongitudSerpiente la cual contendrá el tamaño de la serpiente, la iniciamos con valor 0 para que en un principio solo contenga la cabeza de la serpiente.

Código (cpp) [Seleccionar]
int LongitudSerpiente = 0;

Declaramos e inicializamos la variable Dirección, que contendrá la dirección en la que se esta
moviendo la Serpiente en cada momento, principalmente la inicializamos a 0 para que la serpiente aparezca parada al comenzar el juego.

Código (cpp) [Seleccionar]
int Direccion = 0;

Declaramos e inicializamos la variable control para controlar la permanecía o la salida del bucle que  controla el chequeo de la manzana al ser creada, para que no coincida en una posición de la pantalla en la cual se encuentre la serpiente.

Código (cpp) [Seleccionar]
int Control = 0;

Declaramos la variable Ultimo del tipo SDL_Rect donde almacenaremos la posición del ultimo cuadro que forma la cola de la serpiente, para cuando tengamos que añadir un nuevo cuadro a la cola tras la ingesta de una manzana, sepamos donde posicionarlo con respecto al resto de la serpiente.

Código (cpp) [Seleccionar]
SDL_Rect Ultimo;

Declaramos e inicializamos la variable Puntos que contendrá los puntos del juego.

Código (cpp) [Seleccionar]
int Puntos = 0;

Declaramos la variable Titulo que contendrá el titulo de la ventana y la puntuación.

Código (cpp) [Seleccionar]
char Titulo[255];

la siguiente linea es la encargada de usar el ámbito std como namespace, esto se traduce en que no tendremos que usar std:: delante de las funciones estándar de C++

Código (cpp) [Seleccionar]
using namespace std;

Declaramos la estructura Cuadro que sera la encargada de almacenar la posición y el tamaño, de cada una de las partes que formaran el cuerpo de la Serpiente y la Manzana.

Si os fijáis también he creado un constructor para que la primera vez que creemos uno de los cuadros que formaran la serpiente o la manzana, se inicialicen con un tamaño de 20 píxeles.

Código (cpp) [Seleccionar]
struct Cuadro
{
 SDL_Rect Posicion;
 Cuadro(){Posicion.x = 0;Posicion.y = 0;Posicion.w = 20;Posicion.h = 20;}
}Manzana;


Declaramos el vector Serpiente del tipo Cuadro, un vector es muy similar a un array o lista, lo único que tendremos control total cobre los miembros que forman el vector, podremos incluir miembros nuevos cuando queramos o eliminar algún miembro que no no interese, como no sabremos que tamaño va a alcanzar nuestra serpiente en cada momento, la mejor forma de manejarlo es usando vector pues como os he comentado nos genera una array o lista, que podemos modificar libremente,  otra manera de hacerlo seria dividir el ancho y el alto de la pantalla por el tamaño de un cuadro y multiplicar los resultados, después le restamos 1 para poder posicionar la manzana y tendremos el tamaño total que podrá tener nuestra serpiente.

Por ejemplo si la pantalla midiera 640x480 y cada cuadro que forma la serpiente midiera 20 píxeles, la forma de saber el tamaño máximo que podrá alcanzar nuestra serpiente sera así:

640/20 = 32
480/20 = 24

32 * 24 = 768
768 – 1 = 767


Nuestra serpiente podría tener un tamaño máximo de 767 cuadros y podríamos generar un array de 767 elementos y evitar usar vectores, pero de esta manera estaríamos desperdiciando mucha memoria que mas de un 90% de las veces no sera usada, por este motivo y como es una buena practica de programación ahorrar memoria vamos a usar vectores.

Código (cpp) [Seleccionar]
vector <Cuadro> Serpiente;

la función main no necesita presentación ;)

Código (cpp) [Seleccionar]
int main ( int argc, char** argv )
{


Usaremos la función srand con time como semilla para la generación de numeros aleatorios para posicionar la manzana en pantalla.

Código (cpp) [Seleccionar]
srand(time(NULL));

Inicializamos el modo de vídeo de las SDL y comprobamos que se inicialice bien.

Código (cpp) [Seleccionar]
if ( SDL_Init( SDL_INIT_VIDEO ) < 0 )
{
  printf( "Imposible iniciar la libreria SDL: %s\n", SDL_GetError() );
  return 1;
}


Introducimos SDL_Quit en atexit para que se inicie al finalizar el programa, saliendo así de la librería SDL siempre que el programa finalice.

Código (cpp) [Seleccionar]
atexit(SDL_Quit);

Creamos la Surface principal del juego, la que se va a mostrar en pantalla y comprobamos que se inicie correctamente.

Código (cpp) [Seleccionar]
SDL_Surface* PantallaV = SDL_SetVideoMode(ResolucionX, ResolucionY,PColor,SDL_HWSURFACE|SDL_DOUBLEBUF);
  if ( !PantallaV )
   {
    printf("Imposible crear la ventana Principal: %s\n", SDL_GetError());
    return 1;
   }


Llamamos a la función Iniciar para poner todos los valores a cero.

Código (cpp) [Seleccionar]
Iniciar();

A continuación voy a pasar a explicar la función Iniciar y después regresare al código en la linea siguiente a Iniciar();.

La función Iniciar es la encargada de poner todas las variables con los valores originales del juego para de esta manera poder reiniciarlo cuando colisionemos y la serpiente muera.

Código (cpp) [Seleccionar]
void Iniciar(void)
{
/*Ponemos el valor de la variable longitudSerpiente a 0 para que la serpiente solo este formada por
  la cabeza.*/
LongitudSerpiente = 0;
/*Ponemos el valor de la variable Direccion a 0 para que la serpiente aparezca parada al comenzar  el juego.*/
Direccion = 0;
//Ponemos el valor de la variable Puntos a 0 para reiniciar el marcador de puntos.
Puntos = 0;
//Metemos en la variable Titulo el titulo de la ventana seguido de la puntuación del juego.
sprintf(Titulo,"Snake -- Puntos: %d",Puntos);
//Borramos todos los miembros que forman el vector Serpiente
Serpiente.clear();
//Añadimos un miembro en el vector serpiente que contendrá la cabeza de la serpiente.
Serpiente.push_back(Cuadro());
//Posicionamos aleatoriamente la manzana en la pantalla.
Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);  
Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
//Posicionamos la cabeza de la serpiente en el centro de la pantalla.
Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w);
Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h);
}


Analicemos mas profundamente la manera de posicionar la manzana aleatoriamente en la pantalla

Código (cpp) [Seleccionar]
Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);  
Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);


Sabemos que las medidas de la pantalla están almacenadas dentro de las variables ResolucionX para el ancho y ResolucionY para el alto, las coordenadas que definen la posición de la manzana se posicionan en la esquina superior izquierda de la misma.


Así que si por un casual el ancho aleatorio que nos saliera fuera 640, la manzana se colocaría fuera de la pantalla por el lado derecho de la misma o si el valor para el alto generado aleatoriamente coincidiera con 480, pasaría lo mismo que en el caso anterior pero esta vez la manzana se dibujaría fuera de la pantalla por el lado inferior de la misma, para solucionar este problema debemos restarle el ancho y el alto de la manzana a las dimensiones de la pantalla para asegurarnos de que dicha manzana cuando sea dibujada no se muestre fuera de los limites de la pantalla, el ancho de la manzana se encuentran en Manzana.Posicion.w y el alto en Manzana.Posicion.h.

Código (cpp) [Seleccionar]
(ResolucionX-Manzana.Posicion.w)
(ResolucionY-Manzana.Posicion.h)


Para asegurarnos que el numero aleatorio que va a ser generado para colocar la manzana esta centrado con respecto a la pantalla y al movimiento de la serpiente, debemos dividir el resultado nuevamente por el ancho o el alto de la manzana de esta manera la manzana solo se podrá colocar en posiciones multiplicas de 20 que es el ancho y al alto de la manzana, quedando siempre centrada con el movimiento de la serpiente.

Código (cpp) [Seleccionar]
(ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w)
(ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h)


Y por ultimo ya solo nos queda multiplicar el resultado de la operación aleatoria por el ancho y el alto de la manzana para sacar las coordenadas reales de la pantalla en que va a ser dibujada la manzana.

Código (cpp) [Seleccionar]

Manzana.Posicion.x = ((rand() % ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);  
Manzana.Posicion.y = ((rand() % ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);


Para posicionar la serpiente en el centro de la pantalla usamos el mismo método que para posicionar la manzana, pero lógicamente sin usar números aleatorios, así nos aseguramos que tanto la manzana como la serpiente se encuentren centradas una con respecto a la otra, de esta manera cuando la serpiente se coma la manzana la posición de la cabeza y la manzana corresponderán perfectamente.

Código (cpp) [Seleccionar]

Serpiente[0].Posicion.x = (((ResolucionX/Serpiente[0].Posicion.w)/2)*Serpiente[0].Posicion.w);
Serpiente[0].Posicion.y = (((ResolucionY/Serpiente[0].Posicion.h)/2)*Serpiente[0].Posicion.h);


Una vez explicada la función Iniciar vamos a continuar con el código.

Ahora Declaramos e inicializamos la variable done del tipo bool la cual usaremos como bandera de control para abandonar el bucle principal del juego una vez que pulsemos la tecla Escape o la X que cierra la ventana, tras la declaración creamos el bucle principal del juego que se repetirá siempre que el valor de la variable done se a false (o 0).

Código (cpp) [Seleccionar]
bool done = false;
while (!done)
 {


Lo primero que vamos a encontrar dentro del bucle principal del juego es el método para leer los mensajes que el programa recibe del exterior, como pueden ser las teclas que han sido pulsadas o los mensajes recibidos de la ventana.

Código (cpp) [Seleccionar]
      SDL_Event event;
      while (SDL_PollEvent(&event))
      {
          switch (event.type)
          {
            case SDL_QUIT:
              done = true;
              break;

          case SDL_KEYDOWN:
              {
                  if (event.key.keysym.sym == SDLK_ESCAPE)
                   {
                    done = true;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo)
                   {
                    Direccion = Arriba;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba)
                   {
                    Direccion = Abajo;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha)
                   {
                    Direccion = Izquierda;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda)
                   {
                    Direccion = Derecha;
                    break;
                   }
              }
          }
      }


Declaramos event del tipo SDL_Event, que es donde almacenaremos los mensajes que recibamos del sistema.

Código (cpp) [Seleccionar]
SDL_Event event;

Después crearemos un bucle que se repetirá siempre que queden mensajes del sistema por procesar, al mismo tiempo almacenamos dichos mensajes en la variable event creada anteriormente.

Código (cpp) [Seleccionar]
while (SDL_PollEvent(&event))
      {


Tras lo cual procederemos a leer el mensaje del sistema almacenado en la variable event

Código (cpp) [Seleccionar]
switch (event.type)
          {


Si el mensaje almacenado en la variable event es  SDL_QUIT, esto nos informa de que la X de la ventana a sido pulsado y por tanto el programa debe finalizar, así que ponemos el valor de la variable done a true(o 1) para salir del bucle principal de programa y de este modo finalizar el mismo.

Código (cpp) [Seleccionar]
case SDL_QUIT:
              done = true;
              break;


Otro de los mensajes del sistema que vamos a monitorizar, sera si una tecla a sido pulsada para ello usaremos

Código (cpp) [Seleccionar]
case SDL_KEYDOWN:
              {


Y tras saber que una tecla a sido pulsada debemos ver cual y si nos interesa para que el programa reaccione en concordancia a la tecla pulsada, la primera tecla que vamos a comprobar si ha sido pulsada es Escape y lo haremos  mediante.

Código (cpp) [Seleccionar]
if (event.key.keysym.sym == SDLK_ESCAPE)
                   {


En caso de que el resultado sea 1 o mayor de 1 eso quiere decir que la tecla Escape a sido pulsada y se procederá a realizar lo que hay en el interior del bloque que no es otra cosa que poner el valor de la variable done a true (o 1) para salir del bucle principal de programa y de este modo finalizar el mismo.

Código (cpp) [Seleccionar]
done = true;
break;
}


Después realizaremos la comprobación con las teclas de dirección, empezando por la tecla de dirección Arriba o lo que es lo mismo SDLK_UP,  pero además de comprobar que la tecla haya sido pulsada podremos ver en el código que se realiza otra comprobación,Esta comprobación es que la variable Dirección no contenga el valor Abajo, esto lo hacemos por que la serpiente no puede andar hacia atrás y si la variable Dirección contiene el valor Abajo, la serpiente esta yendo hacia abajo en la pantalla, por lo tanto la tecla de dirección Arriba no tiene que tener efecto;  Para que entremos en el bloque del if ambas comprobaciones deben ser correctas, en caso de que las dos comprobaciones sean correctas cambiamos el valor de la variable Dirección por Arriba y de este modo le decimos a la serpiente la dirección que debe tomar a partir de ahora.

Código (cpp) [Seleccionar]

if(event.key.keysym.sym == SDLK_UP && Direccion != Abajo)
 {
   Direccion = Arriba;
   break;
 }


Explicado esto el resto de las comprobaciones que se realizan a las teclas de dirección son iguales a la comprobación de la tecla de dirección Arriba, pero obviamente cambiando la dirección del movimiento de la serpiente.

Código (cpp) [Seleccionar]

                  if(event.key.keysym.sym == SDLK_DOWN && Direccion != Arriba)
                   {
                    Direccion = Abajo;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_LEFT && Direccion != Derecha)
                   {
                    Direccion = Izquierda;
                    break;
                   }
                  if(event.key.keysym.sym == SDLK_RIGHT && Direccion != Izquierda)
                   {
                    Direccion = Derecha;
                    break;
                   }
              }
          }
      }


Una vez hemos comprobado todos los mensajes del sistema y realizados las acciones que mejor se ajustan a dichos mensajes, salimos del bucle que procesa los mensajes y continuamos.

Si la serpiente esta en movimiento quiere decir que la variable Dirección no vale 0 por tanto tenemos que empezar a mover dicha serpiente por la pantalla y para hacer esto lo primero que vamos a hacer es almacenar en la variable Ultimo del tipo SDL_Rect la posición del ultimo cuadro que forma la cola de la serpiente antes de que este sea movido de su posición actual esto lo hacemos para saber la posición en la que tendremos que colocar un nuevo cuadro si la serpiente se come la manzana

Código (cpp) [Seleccionar]
if(Direccion != 0)
{
 Ultimo.x = Serpiente[LongitudSerpiente].Posicion.x;
 Ultimo.y = Serpiente[LongitudSerpiente].Posicion.y;


Después de almacenar la posición del ultimo cuadro que forma la cola de la serpiente,  vamos a mover la posición de los cuadros que forman la cola de la serpiente mediante el siguiente for, empezando por el ultimo a la posición del cuadro que se encuentra una posición mas arriba en la lista de elementos del vector Serpiente, como podemos ver Declaramos la variable I la cual vamos a usar como contador y la Inicializamos con el valor de la variable LongitudSerpiente que contiene el tamaño actual de la serpiente y recorreremos los elementos que forman el vector Serpiente, hasta alcanzar el cuadro mas próximo a la cabeza de la serpiente que no es otro que el elemento del vector 1, pues como vimos antes la cabeza de la serpiente se encuentra en el elemento del vector 0.con esto conseguimos que la cola de la serpiente avance una posición o lo que es lo mismo 20 píxeles por la pantalla.

Código (cpp) [Seleccionar]
        //Movemos la cola de la serpiente.
        for(int I = LongitudSerpiente; I >= 1; I--)
         {
          Serpiente[I].Posicion.x = Serpiente[I-1].Posicion.x;
          Serpiente[I].Posicion.y = Serpiente[I-1].Posicion.y;
         }
       }


Una vez llegados a este punto nos tiene que surgir una duda, bien si movemos todos los cuadros que forman la cola de la serpiente a la posición inmediata siguiente que pasa con la cabeza de la serpiente?

Pues vamos a resolver esta duda ahora mismo, como la cabeza de la serpiente es la que dirige al resto de la serpiente es la que tiene que moverse en concordancia con la dirección que le digamos usando el teclado por ese motivo la movemos con los siguientes if,

Código (cpp) [Seleccionar]
      if(Direccion == Arriba)
       {
        Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h;
       }
      else if(Direccion == Abajo)
       {
       Serpiente[0].Posicion.y += Serpiente[0].Posicion.h;
       }
      else if(Direccion == Derecha)
       {
        Serpiente[0].Posicion.x += Serpiente[0].Posicion.w;
       }
      else if(Direccion == Izquierda)
       {
        Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w;
       }


Si la variable Dirección contiene Arriba eso significa por lo que vimos antes que hemos usado la tecla de dirección Arriba entonces debemos mover la serpiente por la pantalla hacia arriba esto lo hacemos modificando la variable que contiene la dirección y de la cabeza de la serpiente que no es otra que Serpiente[0].Posicion.y y para ello le restamos el alto del cuadro que forma la cabeza de la serpiente que se encuentra en la variable Serpiente[0].Posicion.h, que son 20 pixeles.

Código (cpp) [Seleccionar]
      if(Direccion == Arriba)
       {
        Serpiente[0].Posicion.y -= Serpiente[0].Posicion.h;
       }


El resto de los else if son exactamente iguales pero lógicamente alterando la los variables en concordancia en la dirección en que deba ser movida la serpiente

Código (cpp) [Seleccionar]
      else if(Direccion == Abajo)
       {
        Serpiente[0].Posicion.y += Serpiente[0].Posicion.h;
       }
      else if(Direccion == Derecha)
       {
        Serpiente[0].Posicion.x += Serpiente[0].Posicion.w;
       }
      else if(Direccion == Izquierda)
       {
        Serpiente[0].Posicion.x -= Serpiente[0].Posicion.w;
       }


Ahora vamos a comprobar la colisión de la cabeza de la serpiente con el borde, para ello tenemos que comprobar que la posición x e y de la serpiente se encuentre dentro de los limites de la pantalla, fijaros bien pues pasa lo mismo que cuando queríamos colocar la manzana dentro de los limites de la pantalla, como los puntos de posición del cuadro que forma la cabeza de la serpiente están en la esquina superior izquierda del cuadro, para comprobar que la colisión se realiza correctamente con la parte inferior y la parte derecha de la pantalla, tenemos que sumar el ancho y el alto del cuadro que forma la cabeza de la serpiente, a la variable respectiva, en caso de que se produzca la colisión reiniciaremos el juego llamando a la función Iniciar la cual os explique su funcionamiento anteriormente.

Código (cpp) [Seleccionar]
      if(((Serpiente[0].Posicion.x + Serpiente[0].Posicion.w)  > ResolucionX)
           || (Serpiente[0].Posicion.x < 0)
           || ((Serpiente[0].Posicion.y + Serpiente[0].Posicion.h)  > ResolucionY)
           || (Serpiente[0].Posicion.y < 0))
       {
        Iniciar();
       }


Una vez hemos realizado la comprobación de la colisión con el borde de la pantalla, vamos a realizar la comprobación de la colisión de la cabeza de la serpiente con la cola, para ello vamos a usar la función Colision la cual os explicare a continuación y después seguiremos viendo el código  

Como podéis ver la función Colision es muy simple lo que hace es comprobar si el cuadro que le pasamos como primer miembro de la función se encuentra dentro o en contacto con el cuadro que le pasamos como segundo miembro de la función, si están en contacto devuelve 1 y en caso contrario devuelve 0.

Código (cpp) [Seleccionar]

int Colision(SDL_Rect H,SDL_Rect M)
{
  if (((H.x + H.w) > M.x) && ((H.y + H.h) > M.y) &&
          ((M.x + M.w) > H.x) && ((M.y + M.h) > H.y))
  {
      return 1;
  }
  else
  {
      return 0;
  }
}


Vamos a recorrer los elementos que forman el vector Serpiente usando un for, empezaremos por el elemento 1 del vector Serpiente, de este modo nos saltamos la cabeza de la serpiente pues esta es la que colisiona con la cola, y lógicamente no puede colisionar consigo misma,  cuando Declaramos la variable I que vamos a usar como contador, la Inicializamos con el valor 1, de este modo  recorreremos todos los elementos que forman el vector Serpiente saltándonos la cabeza de la serpiente y comprobaremos mediante la función Colision si existe colisión con la cabeza de la serpiente que es el elemento 0 del vector, en caso de que la función Colision devuelva 1 quiere decir como vimos antes que la colisión se a producido y entramos dentro del bloque del if donde mediante la función Iniciar reiniciamos el juego .

Código (cpp) [Seleccionar]
      //Comprobamos la colision de la serpiente con la cola.
      for(int I = 1; I <= LongitudSerpiente; I++)
       {
        if(Colision(Serpiente[0].Posicion,Serpiente[I].Posicion))
         {
          Iniciar();
         }
       }


Ya solo nos queda comprobar si la serpiente se a comido la manzana lo cual haremos nuevamente con la función Colision y un if, esta vez comprobaremos la cabeza de la serpiente y la manzana como podemos ver , en caso de que la colisión se produzca entraremos en el bloque del if.

Código (cpp) [Seleccionar]
      if(Colision(Serpiente[0].Posicion,Manzana.Posicion))
       {


Como sabemos que cuando la serpiente se come un manzana su cola crece un cuadro debemos incrementar en uno el valor de la variable LongitudSerpiente y lo haremos así.

Código (cpp) [Seleccionar]
LongitudSerpiente++;

También sabemos que cuando nos comemos una manzana debemos aumentar los puntos, yo en este caso he decidido que cada manzana valga 10 puntos, así que aumento en 10 el valor de la variable Puntos.

Código (cpp) [Seleccionar]
Puntos += 10;

Al aumentar el tamaño de la serpiente, debemos incluir un nuevo elemento en el vector Serpiente y lo haremos con la siguiente linea.

Código (cpp) [Seleccionar]
Serpiente.push_back(Cuadro());

Una vez hemos creado el nuevo elemento en el vector Serpiente, debemos darle una posición en la pantalla y para eso usaremos la posición del ultimo elemento de la cola de la serpiente, que si recordáis habíamos almacenado en la variable Ultimo.

Código (cpp) [Seleccionar]
Serpiente[LongitudSerpiente].Posicion.x = Ultimo.x;
Serpiente[LongitudSerpiente].Posicion.y = Ultimo.y;


Como hemos modificado el valor de la variable Puntos debemos actualizar el titulo de ventana para que nos muestre la nueva puntuación.

Código (cpp) [Seleccionar]
sprintf(Titulo,"Snake -- Puntos: %d",Puntos);

Y finalmente ya solo nos queda posicionar la manzana nuevamente en una posición aleatoria de la pantalla,  como podemos ver aparte de colocar la manzana, debemos comprobar que no la ponemos sobre ninguno de los elementos que forman la serpiente, lo cual comprobaremos nuevamente con la función Colision, y en caso de que la colisión se produzca introduciremos 1 como valor de la Variable Control para que el bucle se vuelva a realizar y se genere otra posición aleatoria para la manzana.

Código (cpp) [Seleccionar]
do {
Manzana.Posicion.x = ((rand() %  ((ResolucionX-Manzana.Posicion.w)/Manzana.Posicion.w))*Manzana.Posicion.w);
Manzana.Posicion.y = ((rand() %  ((ResolucionY-Manzana.Posicion.h)/Manzana.Posicion.h))*Manzana.Posicion.h);
Control = 0;
for(int I = 1; I <= LongitudSerpiente; I++)
   {
     if(Colision(Manzana.Posicion,Serpiente[I].Posicion))
       {
         Control = 1;
         I = LongitudSerpiente;
       }
    }
  }
 while(Control == 1);
}


Ya casi hemos terminado solo nos queda el tema gráfico y lo primero que vamos a hacer es limpiar la surface PantallaV pintándola del color del fondo en este caso negro.

Código (cpp) [Seleccionar]
SDL_FillRect(PantallaV, 0, SDL_MapRGB(PantallaV->format, 0, 0, 0));

Después posicionamos el dibujo de cada uno de los elementos que forman la serpiente en la surface PantallaV.

Código (cpp) [Seleccionar]
for(int I = 0; I <= LongitudSerpiente; I++)
{
  SDL_FillRect(PantallaV,&Serpiente[I].Posicion,SDL_MapRGB(PantallaV->format, 255, 255,   255));
 }


Posicionamos el dibujo de la manzana en la surface PantallaV.

Código (cpp) [Seleccionar]
SDL_FillRect(PantallaV,&Manzana.Posicion,SDL_MapRGB(PantallaV->format, 0, 255, 0));

Refrescamos el titulo de la ventana, por si la puntuación a sido modificada.

Código (cpp) [Seleccionar]
SDL_WM_SetCaption (Titulo, NULL);

Ahora  mostramos la surface PantallaV en la pantalla y esperamos los milisegundos necesarios para que el programa funcione mas o menos igual en todas las maquinas aunque sean mas rápidas.

Código (cpp) [Seleccionar]
SDL_Flip(PantallaV);
SDL_Delay(DELAY);
}


Y por ultimo cuando el programa finalice devolvemos 0, esto hoy en día casi no se usa pero es una buena practica de programación y nos puede ayudar a mantener la compatibilidad del código con otros sistemas.

Código (cpp) [Seleccionar]
  return 0;
}


Bueno con esto terminamos el manual espero que os haya gustado y os sirva.

Para cualquier duda os podéis poner en contacto conmigo en el foro o en mi email.

kaltorak_@hotmail.com

La versión en pdf del manual la tenis en el siguiente enlace, junto con el archivo compilado para windows y linux, así como el código fuente:

http://ultrashare.net/hosting/fl/6a05538c64

Un saludo
Kaltorak.



dato000

#1
TOP 10 es un regalo de kaltorak para el mundo, si quieren hacer buenos juegos, ya saben a quien acudir, amchacon y kaltorak son unas eminencias en allegro y SDL, sean parte de nuestro grupo de diseño y programación de juegos.



EI: SIN SPAM



Esperamos que la comunidad del hacker.net nos apoye, somos un grupo de emprendedores que gusta de la programación (bueno, yo al menos lo intento) y dejenme decirles, este post es sin lugar a dudas, de los mejores que pueden encontrar en toda la red, en cualquier idioma, es aún mejor que un videotutorial (y eso que soy fan incondicionable de mi sensei paueky) ya que encuentran absolutamente todo el material disponible para la causa, be water my friends!!!



crksergio

No sé cómo hay tan pocos (por no decir nada) comentarios en este tema. Felicitaciones por este tremendo tutorial, está buenísimo.

kaltorak

Me alegra mucho saber que les a gustado  ;-)

Muchas gracias por el apoyo.

Un saludo
Kaltorak.



noalg

Cita de: cracksergio en 14 Agosto 2013, 19:03 PM
No sé cómo hay tan pocos (por no decir nada) comentarios en este tema. Felicitaciones por este tremendo tutorial, está buenísimo.

+1 Yo creo que simplemente el tutorial habla por si solo, excelente.
::¿*¿---//&$#\\---▶{}◀---//#$&\\---?*?::


¡¡¡NO PINCHES AQUI SI NO QUIERES INFECTARTE CON UN VIRUS!!!

Danyfirex

Gracias kaltorak muy interesante y útil.

saludos

vangodp

Joder muy bueno sii!! Gracias   :o
Quiere ser mi amigo??? jajajaj ;-)


:-*

kaltorak

Gracias chicos

Me gusta saber que os esta sirviendo  ;)

vangodp  claro que si que podemos ser amigos, es mas todo el que quiera hacer algún desarrollo conmigo que me lo diga.

Busco a alguien con un buen juego en mente para hacer, que tenga una buena historia y una buena mecánica y a alguien para hacer los gráficos de ese juego.

todo el que quiera que se ponga en contacto conmigo por aquí  ;)

Un saludo
kaltorak.



vangodp

Hombre juego en mente....  :rolleyes: as veces se me va explotar la cabeza y as veces po me quedo en blanco jajaj.
Tengo pensado hacer algo como Lord of Ultima, pero con algo mas de emoción jajaj.
Aun que no se que hacer y soy novato extremo y me queda un camino largo.XD
Me gusta los 2D ya que el 3D se necesita un batallón de programadores jeje.
Algo pequeño seria mas guapo creo, algo que se puede hacer a nivel entre compaeros y no tán profissional.  ::)

kaltorak

Si la idea seria hacer algo no muy complicado, pero que tenga estilo propio que no se haya echo ya, la programación en 3d no seria un problema no se me da mal programar en opengl y siempre podemos usar un motor gráfico gratuito para aliviar la carga de programación, lo que necesito es una historia buena y una mecánica de juego fresca que no este muy trillada y si ya consigo a alguien que se encargue del apartado gráfico mejor que mejor :-\ , se que pido casi un imposible pues la gente normalmente no quiere hacer nada quieren que se lo demos todo echo, pero bueno soñar no cuesta nada.