En este apartado puedes encontrar:

Algunas ideas sobre el esqueleto del juego

Para no necesitar conocer demasiado los detalles internos de SDL y de su adaptación como parte del "Tao Framework", el esqueleto incluye varias clases auxiliares, como:

  • El "Hardware", para poder mostrar cosas en pantalla y leer de teclado.
  • Un "Elemento Gráfico", que representa a una imagen en el juego (o incluso a un personaje que está representado por una secuencia de imágenes).
  • Una "Fuente" (un tipo de letra), que nos permitirá escribir textos en pantalla.

Para evitar parpadeos, las imágenes y los textos se dibujan en una pantalla oculta (un "doble buffer"), que se muestra cuando todos los elementos están preparados, por lo que tanto nuestros textos como nuestros tipos de letra se dibujarán en dicha pantalla oculta y se mostrarán posteriormente.

Aplicación a esta primera entrega

La pantalla de presentación simplemente muestra una imagen como ésta, escribe un poco de texto aclaratorio y espera a que se pulse la tecla Espacio para jugar, C para ver los créditos o S para salir:

Presentacion del esqueleto de juego

Y su fuente debería ser sencillo de seguir: apenas hay un método "Ejecutar", que se encarga de dibujar la imagen de ejemplo, escribir el texto, esperar a que se pulse una tecla (25 veces por segundo, para no saturar el sistema) y memorizar la tecla pulsada:

public  void Ejecutar()
{
    // Dibujo la imagen de la presentacion
    imagen.DibujarOculta(0,0);
 
    // Escribo avisos de las teclas utilizables
    Hardware.EscribirTextoOculta(
            "Pulsa Espacio para jugar",
            300, 550, 0xAA, 0xAA, 0xAA, fuenteSans18);
    Hardware.EscribirTextoOculta(
            "S para salir, C para créditos",
            290, 575, 0xAA, 0xAA, 0xAA, fuenteSans18);
 
    // Finalmente, muestro en pantalla
    Hardware.VisualizarOculta();  
 
    //hasta que se pulse espacio (sin saturar la CPU)
    do {        
      Hardware.Pausa(40);
    } while ((! Hardware.TeclaPulsada(Hardware.TECLA_ESP) )
             && (! Hardware.TeclaPulsada(Hardware.TECLA_S))
             && (! Hardware.TeclaPulsada(Hardware.TECLA_C)));
 
    opcionEscogida = OPC_PARTIDA;
    if (Hardware.TeclaPulsada(Hardware.TECLA_S))
        opcionEscogida = OPC_SALIR;
    if (Hardware.TeclaPulsada(Hardware.TECLA_C))
        opcionEscogida = OPC_CREDITOS;        
} 
 

(Si quieres ver más detalles, puedes ver el fuente completo en Google Code).


La pantalla de créditos solo escribe algo de texto:

Creditos del esqueleto de juego

Y su fuente es muy similar: poco más que un método "Ejecutar", que se encarga de escribir los textos de ejemplo y esperar a que se pulse la tecla ESC. Para que no sea totalmente igual a la presentación, en este caso se escribe el texto dentro de la parte repetitiva (algo que sserá totalmente necesario cuando queramos que el texto incluya alguna animación, pero que por ahora podríamos haber evitado, como hemos hecho con la presentación):

public  void Ejecutar()
{
  bool salir = false;  
  while (! salir )
  {
      Hardware.BorrarPantallaOculta(0,0,0); // Borro en negro
 
      Hardware.EscribirTextoOculta(
          "XXX (Remake) - Creditos", 110, 100,
            0x77, 0x77, color, fuenteSans18);
 
      [...]
 
      Hardware.EscribirTextoOculta(
            "Pulsa ESC para volver a la presentación...",
            110,550,0xAA, 0xAA, 0xAA, fuenteSans12);
 
      Hardware.VisualizarOculta();
      Hardware.Pausa(40); 
 
      salir = Hardware.TeclaPulsada (Hardware.TECLA_ESC);
  }
} 
 


La clase "Juego" (un poco más adelante tienes toda la estructura de clases) se limita a coordinar a las demás: lanza la presentación y, según lo que se escoja en ella, muestra la pantalla de créditos o comienza una nueva partida (una sesión real de juego):

public class Juego
{
    private Presentacion presentacion;
    private Partida partida;
    private Creditos creditos;
 
 
    // Inicialización al comenzar la sesión de juego
    public Juego()
    {
        // Inicializo modo grafico 800x600 puntos, 24 bits de color
        bool pantallaCompleta = false;
        Hardware.Inicializar(800, 600, 24, pantallaCompleta);
 
        // Inicializo componentes del juego
        presentacion = new Presentacion();
        partida = new Partida();
        creditos = new Creditos();
    }
 
 
    // --- Comienzo de un nueva partida: reiniciar variables ---
    public void Ejecutar()
    {    
        do
        {
            presentacion.Ejecutar();
            switch( presentacion.GetOpcionEscogida() )
            {
                    case Presentacion.OPC_CREDITOS: 
                        creditos.Ejecutar();
                        break;
                    case Presentacion.OPC_PARTIDA: 
                        partida.BuclePrincipal();
                        break;
            }
        }
        while (presentacion.GetOpcionEscogida() != Presentacion.OPC_SALIR );
    }
 
 
    // --- Cuerpo del programa -----
    public static void Main() 
    {
        Juego juego = new Juego();
        juego.Ejecutar();
    }
 
} /* fin de la clase Juego */
 
 


Finalmente, la clase "Partida" es la que contiene la lógica "real" de una sesión de juego. La parte más importante es el "bucle principal", que repite la secuencia de pasos habituales en este tipo de juegos:

  • Comprobar las teclas que pulse el usuario (o joystick, o ratón) y actuar en consecuencia (moviendo nuestro personaje a derecha e izquierda, por ejemplo).
  • Mover los elementos que deban moverse sólos (como el enemigo, más adelante, o incluso nuestro propio personaje si está saltando o cayendo).
  • Comprobar colisiones entre elementos (por ejemplo, si nuestro personaje toca un enemigo o recoge un objeto de la pantalla).
  • Dibujar todos los elementos del juego en su estado actual.
  • Hacer una pausa hasta el momento del siguiente fotograma, para que la velocidad de juego sea lo más estable posible, independientemente del ordenador en que se pruebe:
Pantalla del esqueleto de juego

public class Partida
{
    // Componentes del juego
    private Personaje miPersonaje;
 
    // Otros datos del juego
    int puntos;             // Puntuacion obtenida por el usuario
    bool partidaTerminada;  // Si ha terminado una partida   
 
    // Inicialización al comenzar la sesión de juego
    public Partida()
    {
        miPersonaje = new Personaje(this);        
        puntos = 0;
        partidaTerminada = false;        
    }
 
 
    // --- Comprobación de teclas, ratón y joystick -----
    void comprobarTeclas()
    {
          // Muevo si se pulsa alguna flecha del teclado
          if (Hardware.TeclaPulsada(Hardware.TECLA_DER) )
              miPersonaje.MoverDerecha();
          if (Hardware.TeclaPulsada(Hardware.TECLA_IZQ))
              miPersonaje.MoverIzquierda();
 
          if (Hardware.TeclaPulsada(Hardware.TECLA_ARR))
              miPersonaje.MoverArriba();
 
          if (Hardware.TeclaPulsada(Hardware.TECLA_ABA))
              miPersonaje.MoverAbajo();
 
          if (Hardware.TeclaPulsada(Hardware.TECLA_ESP))
              miPersonaje.Disparar();
 
          // Compruebo el Joystick
          if (Hardware.JoystickPulsado(0))
              miPersonaje.Disparar();
 
          int posXJoystick, posYJoystick;
          if (Hardware.JoystickMovido(out posXJoystick, out posYJoystick))
          {
              if (posXJoystick > 0) miPersonaje.MoverDerecha();
              else if (posXJoystick < 0) miPersonaje.MoverIzquierda();
              else if (posYJoystick > 0) miPersonaje.MoverAbajo();
              else if (posYJoystick < 0) miPersonaje.MoverArriba();
          }
 
          // Compruebo el raton
          int posXRaton = 0, posYRaton = 0;
          if (Hardware.RatonPulsado(out posXRaton, out posYRaton))
          {
              miPersonaje.MoverA(posXRaton, posYRaton);
          }
 
 
          // Si se pulsa ESC, por ahora termina la partida... y el juego
          if (Hardware.TeclaPulsada(Hardware.TECLA_ESC))
              partidaTerminada = true;
    }
 
 
    // --- Animación de los enemigos y demás objetos "que se muevan solos" -----
     void moverElementos()
    {
        // Nada por ahora
    }
 
 
    // --- Comprobar colisiones de enemigo con personaje, etc ---
     void comprobarColisiones()
    {
        // Nada por ahora
    }
 
 
    // --- Dibujar en pantalla todos los elementos visibles del juego ---
    void dibujarElementos()
    {
        // Borro pantalla      
        Hardware.BorrarPantallaOculta(0,0,0);
 
        // Dibujo el personaje
        miPersonaje.DibujarOculta();
 
        // Finalmente, muestro en pantalla
        Hardware.VisualizarOculta();        
    }
 
 
    // --- Pausa tras cada fotograma de juego, para velocidad de 25 fps -----
     void pausaFotograma() 
    {
        Hardware.Pausa( 40 );
    }
 
 
    // --- Bucle principal de juego -----
    public void BuclePrincipal() 
    {
 
        partidaTerminada = false;
        do {
            comprobarTeclas();
            moverElementos();
            comprobarColisiones();
            dibujarElementos();
            pausaFotograma();
        } while (! partidaTerminada);
    }
 
 
} /* fin de la clase Partida */
 

Estructura de clases empleada

Como clases "del juego" tenemos las ya comentadas: Juego, Presentacion, Creditos, Partida. Como clases auxiliares que ocultan el acceso al sistema: Hardware, Imagen, Fuente, Sonido y ElemGrafico (haz clic en la imagen para verla más grande):

Diagrama de clases

Compiladores y otras herramientas necesarias

El proyecto compila en Windows, pero me temo que no en Linux (o al menos yo no lo he conseguido, la DLL de Tao.SDL no parece funcionar correctamente en este sistema operativo). Se puede utilizar como entorno de desarrollo Visual Studio: incluyo un proyecto para la versión Visual Studio 2008 Express.

En equipos más antiguos o menos potentes, en los que Visual Studio no se mueva con soltura, se puede usar SharpDevelop: incluyo también un proyecto para SharpDevelop 2.2 (o Visual Studio 2005), que sólo necesita la plataforma .Net versión 2, por lo que se podría usar cualquier ordenador que tenga Windows XP SP2.

También se podría usar como compilador Mono (www.mono-project.com), pero al tratarse de varios fuentes habría que crearse un fichero BAT para compilarlos todos juntos. No incluyo dicho fichero BAT (al menos por ahora), así que quien quiera usar Mono tendrá que buscarse la vida un poco más... o preguntar.

Como siempre, si quieres saber más, sólo tienes que seguir leyendo...

¿Y si tengo dudas?

Si te surgen dudas y eres alumno mío, ya sabes dónde preguntar ;-) pero si no eres alumno mío, tampoco lo tienes especialmente difícil: puedes localizarme en un foro de AprendeAProgramar.com que está dedicado a este proyecto.

Y si quieres descargar el proyecto para verlo con más detenimiento o para usarlo como base para cualquier proyecto tuyo, puedes hacerlo desde la página del proyecto en Google Code (ésta es la versión 0.01, claro).

Siguiente entrega...