User Tag List

Resultados 1 al 2 de 2

Tema: Programación de un juego en C++ (5/11)

  1. #1

    Fecha de ingreso
    Jun 2004
    Ubicación
    Pinto (MADRID)
    Mensajes
    1,076
    Mencionado
    12 Post(s)
    Tagged
    0 Tema(s)
    Agradecer Thanks Given 
    5
    Agradecer Thanks Received 
    148
    Thanked in
    Agradecido 101 veces en [ARG:2 UNDEFINED] posts

    Programación de un juego en C++ (5/11)

    5. MOVIMIENTO DE LA NAVE.

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

    En este tutorial vamos a programar una de las partes más divertida y satisfactoria: hacer que la nave se mueva por el fondo del mar con inercia. Pero antes de nada tenemos que dar un repaso a todo lo que hemos hecho para comprender lo que estamos haciendo.

    Nuestro objetivo es hacer un juego de un submarino que tiene que bajar al fondo del mar, recoger uno de los 4 tesoros y subirlo a un barco mientras esquiva unos peligrosos peces globo. La cosa es sencilla, pero... ¿qué hemos hecho hasta ahora?
    - Primero, una plantilla que compile bien.
    - Segundo, una estructura para cada modo de programa, porque cada modo de programa (menu, juego, pausa, inventario, etc...) dibuja una cosa diferente en pantalla y reacciona a los botones de forma diferente.
    - Tercero, hemos arrancado el programa imprimiendo un texto por cada modo, para asegurarnos de que funciona bien y está listo para nuestro juego.
    - Cuarto, hemos diseñado un sencillo menú que nos permite jugar o salir del juego.

    Hemos establecido unos pequeños pasos que se pueden acabar en relativamente poco tiempo, y que nos acercan cada vez más al desarrollo final. En este juego (que hice en 8 horas) vemos los resultados rápidamente, pero si os metéis en un desarrollo más grande debéis tenerlos muy claros e ir completándolos uno a uno... por ejemplo: el sistema de mapas, el inventario, inteligencia de los enemigos, etc...

    Pero no os pongáis nerviosos, hoy lo que queremos son resultados rápidos y chulos. Si se puede hacer un juego así de divertido en 8 horas, ¿cuántos podríamos hacer en un mes?

    Código:
    // graficos
    SDL_Surface *ship;
    SDL_Surface *bug;
    SDL_Surface *gold;
    SDL_Surface *boat;
    SDL_Surface *bubble;
    Primero necesitamos unos gráficos para el juego. Tenemos una nave (ship) que es la que manejaremos nosotros, bichos (bug), tesoros (gold), el barco de rescate (boat) y burbujas (bubble). Así que creamos unas variables globales que son punteros a una superficie SDL que señalará a los gráficos.

    Código:
    ///////////////////////////////////
    /*  Variables del juego          */
    ///////////////////////////////////
    int ship_x=0;
    int ship_y=0;
    float ship_ah=0;
    float ship_av=0;
    Nuestra nave necesita unas variables, como su posición en X, posición en Y y aceleración horizontal y vertical. ¿Cómo vamos a conseguir darle inercia a la nave? Con un truco muy sencillo. ship_x y ship_y contienen la posición de la nave. Ya sabréis que para mover un sprite en pantalla hay que incrementar o decrementar su posición. Cuánto más la incrementemos, más rápido irá.

    Así que vamos a utilizar ship_ah y ship_av como las variables de aceleración. Mientras pulsamos los botones de dirección, las variables de aceleración irán aumentando con valores de 0.3, hasta un máximo de 8, mientras que si dejas de pulsarlos disminuirá en valores de 0.3 hasta 0. La aceleración será constante, y podremos comprobar cómo la velocidad de la nave aumenta desde movimientos de 1 pixel hasta un máximo de 8 pixels.

    Cuando lo veas en movimiento lo comprenderás, y simulará las físicas sin líos de fuerzas, rozamientos y esas cosas que muchos han olvidado del instituto (como yo).

    Código:
    void init_game()
    {
    ...
      SDL_Rect rect;
      SDL_Surface *tmpsurface;
    
      tmpsurface=SDL_LoadBMP("data/ship.bmp");
      if(tmpsurface)
      {
        ship=SDL_CreateRGBSurface(SDL_SRCCOLORKEY, tmpsurface->w, tmpsurface->h, 16, 0,0,0,0);
        rect.x=0;
        rect.y=0;
        rect.w=tmpsurface->w;
        rect.h=tmpsurface->h;
        SDL_BlitSurface(tmpsurface,&rect,ship,NULL);
        SDL_SetColorKey(ship,SDL_SRCCOLORKEY,SDL_MapRGB(screen->format,255,0,255));
        SDL_FreeSurface(tmpsurface);
      }
    Lo primero que vamos a hacer en la función init_game() es cargar los gráficos que hemos metido en la carpeta data. Así que creamos una superficie temporal tmpsurface* y cargamos el gráfico. Como el gráfico puede estar hecho con diferentes profundidades de color, vamos a asegurarnos de que usamos una versión de 16 bits, así que creamos la superficie SDL definitiva que le asignamos a ship con SDL_CreateRGBSurface(), del mismo tamaño que el gráfico que hemos cargado. Y con SDL_BlitSurface() copiamos la superficie que hemos cargado en nuestra superficie de 16 bits. Con SDL_SetColorKey() decimos que el color transparente de nuestra superficie es el morado (255,0,255). Por último liberamos la superficie temporal.

    Recordad que la Wiz no puede manejar una pantalla de 24 bits de color, así que vamos a convertir todos los gráficos a 16 bits por si acaso. Si queréis hacer un juego de 8 bits, no tenéis que hacer caso de esto... ¡por supuesto!

    Código:
      tmpsurface=SDL_LoadBMP("data/bug.bmp");
      if(tmpsurface)
      {
        bug=SDL_CreateRGBSurface(SDL_SRCCOLORKEY, tmpsurface->w, tmpsurface->h, 16, 0,0,0,0);
        rect.x=0;
        rect.y=0;
        rect.w=tmpsurface->w;
        rect.h=tmpsurface->h;
        SDL_BlitSurface(tmpsurface,&rect,bug,NULL);
        SDL_SetColorKey(bug,SDL_SRCCOLORKEY,SDL_MapRGB(screen->format,255,0,255));
        SDL_FreeSurface(tmpsurface);
      }
      tmpsurface=SDL_LoadBMP("data/gold.bmp");
      if(tmpsurface)
      {
        gold=SDL_CreateRGBSurface(SDL_SRCCOLORKEY, tmpsurface->w, tmpsurface->h, 16, 0,0,0,0);
        rect.x=0;
        rect.y=0;
        rect.w=tmpsurface->w;
        rect.h=tmpsurface->h;
        SDL_BlitSurface(tmpsurface,&rect,gold,NULL);
        SDL_SetColorKey(gold,SDL_SRCCOLORKEY,SDL_MapRGB(screen->format,255,0,255));
        SDL_FreeSurface(tmpsurface);
      }
      tmpsurface=SDL_LoadBMP("data/boat.bmp");
      if(tmpsurface)
      {
        boat=SDL_CreateRGBSurface(SDL_SRCCOLORKEY, tmpsurface->w, tmpsurface->h, 16, 0,0,0,0);
        rect.x=0;
        rect.y=0;
        rect.w=tmpsurface->w;
        rect.h=tmpsurface->h;
        SDL_BlitSurface(tmpsurface,&rect,boat,NULL);
        SDL_SetColorKey(boat,SDL_SRCCOLORKEY,SDL_MapRGB(screen->format,255,0,255));
        SDL_FreeSurface(tmpsurface);
      }
      tmpsurface=SDL_LoadBMP("data/bubble.bmp");
      if(tmpsurface)
      {
        bubble=SDL_CreateRGBSurface(SDL_SRCCOLORKEY, tmpsurface->w, tmpsurface->h, 16, 0,0,0,0);
        rect.x=0;
        rect.y=0;
        rect.w=tmpsurface->w;
        rect.h=tmpsurface->h;
        SDL_BlitSurface(tmpsurface,&rect,bubble,NULL);
        SDL_SetColorKey(bubble,SDL_SRCCOLORKEY,SDL_MapRGB(screen->format,255,0,255));
        SDL_FreeSurface(tmpsurface);
      }
    }
    Repetimos lo mismo con todos los gráficos para tenerlos en memoria.

    Código:
    void end_game()
    {
    ...
      if(ship)
        SDL_FreeSurface(ship);
      if(bug)
        SDL_FreeSurface(bug);
      if(gold)
        SDL_FreeSurface(gold);
      if(boat)
        SDL_FreeSurface(boat);
      if(bubble)
        SDL_FreeSurface(bubble);
    }
    En la función end_game() añadimos unas líneas para liberar la memoria que hemos ocupado con los gráficos. Cuanto más controlemos lo que hacemos, mejor.

    Código:
    void reset()
    {
      ship_x=148;
      ship_y=48;
      ship_ah=0;
      ship_av=0;
    }
    La función reset() se llama al principio de cada partida. En ella pondremos todo lo que creamos conveniente para que las partidas comiencen igual. Por ahora, vamos a añadir la posición inicial de la nave cuando empecemos a jugar, que será (148,48), con una aceleración de 0.

    Código:
    void read_game_keys()
    {
    ...
      if(SDL_JoystickGetButton(joystick, GP2X_BUTTON_B))
      {
        if(ship_av>-8)
          ship_av-=0.3;
      }
      else
      {
        if(ship_av<8)
          ship_av+=0.3;
      }
    En read_game_keys() leíamos los eventos de teclado, pero hay veces que lo que nos interesa es leer directamente el estado de los botones para saber si están apretados o no y actuar en consecuencia. El movimiento de la nave lo vamos a hacer de esta forma, para no depender de eventos.

    Con SDL_JoystickGetButton(joystick, GP2X_BUTTON_B) comprobamos el estado del botón B. Si está pulsado devuelve 1, si no está pulsado 0. Así que si pulsamos B (para que la nave suba) y la aceleración vertical es mayor que -8, la decrementamos 0.3. Si no estamos pulsando B, la aceleración vertical aumentará hasta un máximo de 8 y la nave caerá hasta el fondo del mar. No queremos que vaya más rápido de 8 pixels, pero podéis cambiarlo para hacer pruebas.

    Código:
      if(SDL_JoystickGetButton(joystick, GP2X_BUTTON_LEFT) || SDL_JoystickGetButton(joystick, GP2X_BUTTON_UPLEFT) || SDL_JoystickGetButton(joystick, GP2X_BUTTON_DOWNLEFT))
      {
        if(ship_ah>-8)
          ship_ah-=0.3;
      }
      if(SDL_JoystickGetButton(joystick, GP2X_BUTTON_RIGHT) || SDL_JoystickGetButton(joystick, GP2X_BUTTON_UPRIGHT) || SDL_JoystickGetButton(joystick, GP2X_BUTTON_DOWNRIGHT))
      {
        if(ship_ah<8)
          ship_ah+=0.3;
      }
    Y lo mismo para los lados. Aquí tenemos que comprobar si pulsamos izquierda (GP2X_BUTTON_LEFT), arriba-izquierda (GP2X_BUTTON_UPLEFT) y abajo-izquierda (GP2X_BUTTON_DOWNLEFT), ya que si sólo comprobamos izquierda y pulsamos la cruceta un poco por arriba, la nave no se movería.

    El resto es igual que con el botón de subir. Si apretamos hacia la izquierda decrementamos la aceleración horizontal hasta -8, y si pulsamos derecha la aumentamos hasta 8.

    Código:
      if(!SDL_JoystickGetButton(joystick, GP2X_BUTTON_LEFT) && !SDL_JoystickGetButton(joystick, GP2X_BUTTON_UPLEFT) && !SDL_JoystickGetButton(joystick, GP2X_BUTTON_DOWNLEFT) && ship_ah<0)
        ship_ah+=0.3;
    
      if(!SDL_JoystickGetButton(joystick, GP2X_BUTTON_RIGHT) && !SDL_JoystickGetButton(joystick, GP2X_BUTTON_UPRIGHT) && !SDL_JoystickGetButton(joystick, GP2X_BUTTON_DOWNRIGHT) && ship_ah>0)
        ship_ah-=0.3;
    }
    Con estas dos líneas, lo que hacemos es que si no pulsamos hacia la izquierda, pero nos estamos moviendo hacia la izquierda, aumentamos la aceleración horizontal hasta llegar a 0 (por lo que la nave se pararía en su movimiento horizontal). Y repetimos lo mismo hacia la derecha.

    [/CODE]void draw_game()
    {
    // dibujamos el fondo
    SDL_Rect dest;
    dest.x=0;
    dest.y=0;
    dest.w=320;
    dest.h=48;
    SDL_FillRect(screen,&dest,SDL_MapRGB(screen->format,0,255,255));
    dest.x=0;
    dest.y=48;
    dest.w=320;
    dest.h=179;
    SDL_FillRect(screen,&dest,SDL_MapRGB(screen->format,0,0,255));
    dest.x=0;
    dest.y=227;
    dest.w=320;
    dest.h=13;
    SDL_FillRect(screen,&dest,SDL_MapRGB(screen->format,220,140,80));[/CODE]

    Ahora tenemos que dibujar nuestro juego. Lo primero es dibujar 3 rectángulos de color que simularán el cielo, el agua y el fondo marino. SDL_FillRect() dibuja un rectángulo del tamaño y color que le indiquemos. No tiene más misterio.

    Código:
      // dibujamos el barco
      SDL_Rect rboat;
      rboat.x=136;
      rboat.y=24;
      if(boat)
        SDL_BlitSurface(boat,NULL,screen,&rboat);
    Creamos una variable SDL_Rect con las coordenadas de nuestro barco de rescate y lo dibujamos en pantalla con SDL_BlitSurface(). Os aconsejo que comprobéis siempre que la superficie existe, porque si no se crea bien el programa petaría de mala manera. Si simplemente no se dibuja, ya sabes que hay algún problema con el gráfico.

    Código:
      // dibujamos nuestra nave
      SDL_Rect rship;
      rship.x=ship_x;
      rship.y=ship_y;
      if(ship)
        SDL_BlitSurface(ship,NULL,screen,&rship);
    }
    Y por supuesto, dibujamos nuestro submarino.

    Código:
    void update_game()
    {
      ship_y+=(int)ship_av;
      ship_x+=(int)ship_ah;
    En update_game() es donde hacemos que nuestro batiscafo se mueva. Recordad que las comprobaciones, movimientos, etc... se hacen en update_game. Esto mismo se podría incluir en la función read_game_keys(), pero si quieres buscar dónde falla algo te podrías encontrar en el infierno. Además, así podremos hacer otras cosas chulas más adelante.

    Lo primero que hacemos es sumar a nuestra posición X y posición X, la aceleración horizontal y vertical respectivamente.

    Código:
      if(ship_x<0)
        ship_x=0;
      if(ship_x>296)
        ship_x=296;
    Establecemos los límites de movimiento. En horizontal, la nave se moverá entre 0 y 296. Así que si X es menor de 0, lo dejamos a 0 y si es mayor de 296 lo dejamos a 296.

    Código:
      if(ship_y<48)
      {
        ship_y=48;
        ship_av=0;
      }
      if(ship_y>204)
      {
        ship_y=204;
        ship_av=0;
      }
    Y hacemos lo mismo con los límites verticales. Nuestra nave se moverá entre 48 (el nivel del mar) y 204 (el fondo marino). Además, la aceleración vertical la dejamos a 0.

    Código:
        SDL_Delay(15);
        SDL_Flip(screen);
    Y para que el juego no se mueva demasiado rápido vamos a poner un delay justo antes de SDL_Flip(). Un SDL_Delay de 15 está bien. Si ponéis un valor menor, el juego irá más rápido, y si es mayor más lento.

    Este valor lo ponemos a pelo porque el juego es sólo para Wiz. Si este mismo código lo compiláis para PC, la nave sería incontrolable. Para hacer que el juego vaya a la misma velocidad en todas las plataformas habría que hacer algo un poco más complejo para que limite la velocidad a 30 o 60 frames (o el valor que consideres justo). Pero como el objetivo de este tutorial es tener unos resultados rápidos, vamos a dejarlo así.

    Si lo compiláis (o probáis la versión compilada), veréis cómo la nave se mueve a las órdenes de vuestras pulsaciones con una inercia bastante realista. La parte más difícil ya está hecha, así que nos quedan un par de menudeces para completar nuestro juegazo.

    Por cierto, si recordáis la primera lección dijimos que cuantas menos variables globales, mejor. ¿Por qué tenemos tantas aquí? ¿Realmente está bien hecho?

    En realidad, todo esto se podría ordenar mucho mejor con estructuras (y más aún con clases). Pero lo que interesa es que comprendáis cómo afrontar cada pasito que os lleve a completar el juego, y cómo afrontar de manera sencilla cosas que a priori parece muy difíciles. Programar unas físicas para un gráfico animado puede quitar las ganas de programar a cualquiera, pero si lo piensas con dos dedos de frente... ¡no es así!

    Como consejo: cuando algo te parezca muy difícil, ¡no te pongas a escribir! Piensa muchas veces de qué formas podrías programarlo de forma que sea sencillo de comprender. Y sobre todo, hazlo paso a paso.

    En los próximos capítulos utilizaremos algunas estructuras para que te vayas haciendo a la idea de cómo organizar mejor el código. Y si te crees capaz de hacerlo ya, prueba a cambiar este... ¡que está para eso!

  2. #2

    Fecha de ingreso
    Mar 2011
    Mensajes
    1
    Mencionado
    0 Post(s)
    Tagged
    0 Tema(s)
    Agradecer Thanks Given 
    0
    Agradecer Thanks Received 
    0
    Thanked in
    Agradecido 0 veces en [ARG:2 UNDEFINED] posts
    Muy interesante todo eso

Permisos de publicación

  • No puedes crear nuevos temas
  • No puedes responder temas
  • No puedes subir archivos adjuntos
  • No puedes editar tus mensajes
  •