Después de hacer un recorrido por los principales elementos de la biblioteca SFML 2 ya solo nos queda poner en práctica los conceptos explicados y ver como se unen todos elaborando un juego completo.
Como no podría ser de otra manera vamos a abordar el desarrollo del Hola Mundo de los videojuegos, un clon del Pong. Su sencillez hacen que elaborar el juego sea sencillo y nos permite ver las características más comunes de un videojuegos como el manejo de sprites, la lógica o la entrada del usuario.
Partiremos del código base para visual studio 2010 que utilizamos para los demás tutoriales. En esta ocasión vamos a usar un pack de recursos que he preparado para nuestro juego. Hay que descomprimir la carpeta data dentro de la carpeta bin de nuestro proyecto. La carpeta contiene todos los elementos necesarios para elaborar nuestro pong.
Una vez hecho esto abrimos nuestro proyecto con visual studio a través de la solución GameSolution2010. Una vez abierto veremos el archivo main.cpp que viene creado por defecto con el código de prueba ejecutamos una vez para ver que todo funciona correctamente (deberíamos ver una ventana con un fondo celeste) y a continuación borramos todo el contenido del archivo main.cpp para empezar nuestro juego desde cero.
Definiendo nuestro juego
Antes de abordar la elaboración de cualquier proyecto, primero hay que diseñar y ver en que consiste. En nuestro caso es sencillo haremos un clon del clásico pong. Solo habrá una pantalla en la que controlaremos a una pala que moveremos arriba y abajo con la flecha de dirección. La Inteligencia Artificial moverá la otra pala. Si conseguimos colar la bola detrás de la pala de la IA se nos sumará un punto, si se cuela detrás de la nuestras se le sumará un punto a la IA. Veámos una captura del juego.
Archivo de configuración
Todos las clases y objetos de nuestro juego necesitarán acceder constantemente al ancho y alto de la ventana, así que lo que vamos a hacer es crear unas constantes estáticas con esta información y guardarlas en un archivo config.hpp que los demás podrán incluir para acceder a ellas, no es muy elegante, pero para este tutorial nos vale.
Muy importante que todo los archivos que creemos sean dentro de la carpeta src del directorio principal del proyecto, aquí debe estar todos los archivos de código fuente del juego. Este sería el archivo config.hpp
La clase Pong
Vamos a intentar seguir el paradigma de la programación orientada a objetos lo máximo posible. Así que lo primero que vamos a hacer es crear un objeto principal que es el que lanza el juego, será el encargado de crear los objetos necesarios así como de controlar el bucle del juego.
Yo he decidido llamar a la clase Pong así que he creado un archivo Pong.hpp y Pong.cpp que solo tiene el constructor y un método run. Veamos como queda.
Como vemos hacemos uso de los módulos Graphics y Audio de SFML 2. Como atributos de la clase por ahora tenemos un objeto RenderWindow que representa la ventana del juego, un objeto Clock que es una especie de cronómetro que lleva el tiempo pasado desde la última vez que se reinició (o desde que se creó), un objeto Time que almacena tiempo y lo permite devolver en segundos, milisegundos y microsegundos (lo usaremos para almacenar el tiempo de nuestro Reloj). Luego tenemos un objeto Texture que cargará la textura del fondo y un Sprite que usará la textura para mostrarlo. Por último tenemos un objeto Music que será el que tenga la música de fondo de nuestro juego.
A continuación vamos a definir nuestro archivo Pong.cpp.
El constructor
Lo primero es añadir los archivos de cabecera necesario en este caso el header de la clase y el archivo de configuración. A continuación en el constructor de la clase llamamos al método create de window. Éste recibe tres parámetros el primero es un objeto del tipo VideoMode que a su vez recibe tres parámetros con el mododo de video, obtenemos los valores del archivo config.hpp. El siguiente parámetro es el título que tendrá la ventana. Por último recibe un tercer parámetro que es el estilo de la ventana que en este caso definimos que solo se puede cerrar, si quieres más información del as banderas que se pueden usar en este parámetro mira este tutorial.
La siguiente sentencia activa la sincronización vertical haciendo que el juego vaya a unos 60 frames por segundo. A continuación cargamos la textura de fondo que se encuentra en la carpeta data y se la asignamos a nuestro Sprite. background.
Lo último que haremos por ahora en nuestro constructor es abrir el archivo de música que sonará de fondo. Activamos el loop para que se repita indefinidamente cada vez que acabe y por último le damos al play.
El método run
El método run será el encargado de llevar el bucle de nuestro juego, será el que se estará ejecutando continuamente y donde debemos meter toda la lógica de nuestro juego. Como vemos contiene un while que se ejecuta mientras la ventana esté abierta, la verdad es que los métodos de SFML son autoexplicativos.
Lo primero que hacemos dentro del bucle es reiniciar el reloj que aparte de reiniciarse devuelve el tiempo pasado desde el anterior reinicio. Con esto lo que hacemos es tener en el objeto time el tiempo pasado en cada ciclo del juego muy importante para mover objetos.
Luego tenemos nuestro ciclo de eventos explicado en los tutoriales anteriores en este caso solo controlamos un eventos que es el de cierre que lo que hace es cerrar la ventana y por tanto salir de la aplicación.
Por último dibujamos en la ventana nuestro fondo con draw y actualizamos la escena con display. Si ejecutamos nuestro juego tendremos una ventana con un bonito fondo de pong verde y una música que sueba de fondo.
Cabe decir que habrá que crear un archivo main.cpp donde se cree un objeto Pong y se lance un mensaje a run. Algo así.
La clase Ball
Ahora vamos a darle algo de vida a nuestro juego vamos a crear la clase Ball. La bola de momento solo se encargará de moverse y rebotar contra los marcos de la ventana. La clase Ball será un Sprite de SFML por lo que heredaremos de ella y le añadiremos algunas cosas más. Nuestro Ball.hpp sería el siguiente
Como vemos sólo tiene el constructor y dos métodos más que son update y getSpeedX. El primero recibe como parámetro el tiempo pasado en cada ciclo del juego y será el encargado de actualizar la bola. El segundo devuelve la velocidad en el eje X de la bola. Solo definimos este y no la velocidad en el eje Y porque en nuestro juego solo nos hará falta que otros objetos usen su velocidad X.
Como atributos de la clase tenemos una textura que será la imagen de la bola, un vector de tipo float 2D que representa la velocidad de la bola y por último tenemos un SoundBuffer y un Sound que contendrán el sonido que hace la bola al chocar contra las paredes o las palas.
Ahora pasemos a ver fichero Ball.cpp.
El constructor de ball
Lo primero que hacemos es cargar la textura desde un archivo y asignarla al objeto, nada nuevo. A continuación lo mismo con el sonido cargamo el SoundBuffer y se lo asignamos al Sound.
El la siguiente línea cambiamos el origen del Sprite, el origen es el punto a partir del cual se dibuja el objeto, si no se cambia se toma la esquina superior izquierda del mismo para dibujarlo y obtener si posición. También es el punto a partir del cual se aplican las rotaciones y se escala el objeto. Nosotros vamos a cambiarlo para que ese punto sea el centro del objeto. Lo hacemos a través del objeto getLocalBounds() que nos devuelve un réctangulo que representa el objeto.
Rectángulos en SFML
Antes de seguir vamos a explicar la clase la clase Rect que no es una clase en sí, sino una plantilla que crea rectángulos del tipo pasado, en SFML vienen ya definidos dos typedef de los más comunes.
sf::Rectes sf::IntRect sf::Rect es sf::FloatRect
Un rectángulo se define en SFML con cuatro valores una coordenada top y otra left que representan la esquina superior izquierda del rectángulo y otros dos valores llamados width y height que representa el ancho y alto del rectángulo respectivamente.
Estos cuatro atributos son públicos. Además cuenta con algunas funciones para saber si un punto está contenido dentro del rectángulo o para saber si intersecta con otro rectángulo, puedes ver más información en la documentación.
Bien pues todos los objetos que heredan de la clase Transformable que son Shape, Text y Sprite tienen dos métodos comunes getLocalBounds y getGlobalBounds.
El primero devuelve siempre un rectángulo local del objeto es decir siempre nos devolverá su posición en 0,0 (top y left valen 0) y width y height con el tamaño del mismo sin modificaciones como escalados o rotaciones.
Por el contrario getGlobalBounds nos devolverá un rectángulo con la posición global del objeto una vez aplicadas todas las tranformaciones, lo mismo con su tamaño.
Lo mejor para verlo es probar los valores que van dando ambos con diferentes objetos y transformaciones.
Una vez hecho este paréntesis explicativo continuemos.
Después de definir el origen en el centro de la pala ahora si podemos situar la bola en el centro de nuestra ventana con setPosition. Por último definimos la velocidad x e y de la bola según los valores dados aquí irá más rápida o más despacio.
El método getSpeedX
Este método solo devuelve la velocidad en el eje x ya que será necesaria para definir la Inteligencia Artificial de la pala que maneja la CPU.
El método update
Como ya hemos dicho por ahora solo recibe como parámetro el tiempo pasado en cada ciclo, más adelante recibirá otros más. Lo primero que hacemos es obtener las cuatro límites de la bola. Como hemos definido el origen en el centro de la misma, el método getPosition() nos devolverá un vector con el punto donde se encuentra el centro de la bola como el origen también se encuentra en el centro restandoselo o sumándolo obtendremos los puntos de los límites de la bola.
Lo siguiente es comprobar si choca contra las paredes para cambiar el sentido de la velocidad, si choca contra la izquierda o la derecha cambiamos el signo de la velocidad en x y si chocamos con la parte superior o inferior cambiamos la velocidad en el eje y.
Fíjate en el siguiente código.
if (left <= 0 && speed.x < 0) { this->speed.x *= -1; this->sound.play(); } if (right >= WIDTH && speed.x > 0) { this->speed.x *= -1; this->sound.play(); } if ((top <= 0 && speed.y < 0) || (bottom >= HEIGHT && speed.y > 0)) { this->speed.y *= -1; this->sound.play(); }
Como vemos el cambio de velocidad en el eje y lo controlamos en un solo if mientras que el cambio de velocidad en el eje x lo tenemos en dos if separados aunque sea el mismo código. Lo hacemos así porque más adelante será la bola la encargada de mandar el mensaje de a quien anotar un punto según que pared toque por lo que tenemos que diferenciar en que pared toca.
También comprobamos que la velocidad vaya en el sentido de la pared que toca para cambiar la velocidad, con esto evitamos que se quede "pillada" en una de las paredes.
Por último ya habiendo calculado en que dirección debe ir la bola solo queda moverla para ello recurrimos a la física que nos dice que:
espacio = velocidad * tiempo
Pues multiplicamos la respectivas velocidades en los dos ejes por el tiempo pasado y en consecuencia movemos la bola.
Añadiendo la bola al juego
Ahora solo nos queda crear una bola en nuestro juego vamos a nuestro Pong.hpp que nos queda así.
y el Pong.cpp así.
Si ejecutamos nuestro juego deberíamos tener una bola que rebota contra las paredes produciendo un sonido en cada choque.
Hasta aquí la primera parte en la segunda implementaremos las palas y el sistema de puntuación.