SFML 2: Elaborando un juego completo II

SFML 2: Elaborando un juego completo II
Sin comentarios Facebook Twitter Flipboard E-mail

En la primera parte establecimos la clase base de nuestro así como definimos la clase Ball y la hicimos rebotar contra las paredes, en esta segunda parte terminaremos de elaborar nuestro juego implementando las palas, la inteligencia artificial, la entrada del usuario y un sistema de puntuaciones.

La clase Paddle

La clase Paddle va a representar las palas o raquetas de nuestro juego. Una será controlada por el jugador (la de la izquierda) y otra por una inteligencia artificial muy básica (derecha). Podríamos hacerla igual que la clase Ball heredando de Sprite, pero para ver más herramientas de SFML 2 esta vez usaremos la clase RectangleShape que a su vez hereda de Shape y nos permite dibujar rectángulos en pantalla.

como siempre empezaremos echando un vistazo a nuestro Paddle.hpp.

#ifndef PADDLE_HPP
#define PADDLE_HPP
#include <SFML/Graphics.hpp>
class Ball;
class Paddle : public sf::RectangleShape
{
public:
Paddle();
void updateHuman(sf::Time& delta);
void updateIA(sf::Time& delta, Ball& ball);
private:
float speed;
}; // class Paddle
#endif // PADDLE_HPP
view raw Paddle.hpp hosted with ❤ by GitHub

Vemos como hereda de RectangleShape y en esta ocasión tenemos dos métodos update, updateHuman y updateIA. Como habrás imaginado el primero controla la pala del jugador y solo necesitamos enviarle el tiempo para moverla. El segundo controla la pala de la IA y además del tiempo la pasamos una referencia a la bola parque necesita "ver" donde está para moverse en consecuencia.

Como atributos solo tenemos la velocidad, al contrario que la clase Ball no es un Vector sino un float ya que las palas solo se mueven arriba y abajo por lo que solo necesitamos la velocidad en el eje Y.

Sin más pasemos a ver el Paddle.cpp

#include "Paddle.hpp"
#include "config.hpp"
#include "Ball.hpp"
Paddle::Paddle()
: sf::RectangleShape()
{
// Definimos la velocidad de la pala (sólo y)
this->speed = 300.0f;
// Definimos el tamaño del rectángulo
this->setSize(sf::Vector2f(18, 66));
// Definimos el origen en el centro
this->setOrigin(9, 33);
// Establecemos el color de relleno en blanco
this->setFillColor(sf::Color(255, 255, 255));
// Establecemos el color del borde en negro
this->setOutlineColor(sf::Color(0, 0, 0));
// Establecemos el grosor del borde en 2 hacia dentro
this->setOutlineThickness(-2.0f);
}
void Paddle::updateHuman(sf::Time& delta)
{
// Obtenemos la posición superior e inferior
float top = this->getGlobalBounds().top;
float bottom = this->getGlobalBounds().top + this->getGlobalBounds().height;
// Movemos la pala en función de las teclas pulsada
// comprobando no salirnos de la pantalla
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Up) && top > 0)
this->move(0, -delta.asSeconds() * speed);
if (sf::Keyboard::isKeyPressed(sf::Keyboard::Down) && bottom < HEIGHT)
this->move(0, delta.asSeconds() * speed);
}
void Paddle::updateIA(sf::Time& delta, Ball& ball)
{
// La IA solo mueve la pala si la bola va hacia ella y está en su campo
if(ball.getSpeedX() >= 0 && ball.getPosition().x >= WIDTH/2.0f)
{
if(this->getPosition().y < ball.getPosition().y)
this->move(0, this->speed * delta.asSeconds());
if(this->getPosition().y > ball.getPosition().y)
this->move(0, -this->speed * delta.asSeconds());
}
}
view raw Paddle.cpp hosted with ❤ by GitHub

El constructor

Bueno en el constructor hacemos lo de siempre inicializar los valores de la pala, establecemos el tamaño, el origen en el centro, definimos un color, un borde y la velocidad.

Prestar atención a que no definimos la posición de la Pala ya que no es para las dos igual la pala del jugador estará en un sitio y la de la IA en otro, por lo que la definiremos más adelante.

El método updateHuman

Este método implementa el movimiento de la pala del jugador que respondo al teclado. Lo primero es obtener la parte superior e inferior de la pala.

A continuación vemos el estado del teclado en tiempo real con el método estático isKeyPressed de la clase Keyboard. Este método nos devuelve true si la tecla pasada por parámetro en ese mismo instante está siendo pulsada y false en caso contrario.

Aparte de esto comprobamos si ha chocado con los límites superior o inferior para que no se pueda salir de la pantalla.

El metodo updateIA

Este método contiene la "Inteligencia Artificial" de nuestro juego, la pongo entre comillas porque es algo muy básico. La idea es que la pala sepa la posición y de la bola si está más arriba que su posición se mueva hacia arriba y si está más abajo pues se mueve hacia abajo, así de simple.

Para que sea más realista solo moveremos la pala si la bola va hacia la pala (velocidad positiva, para esto necesitábamos el método getSpeedX de la clase Ball) y si esté en el campo de la IA (la posición es mayor a la mitad del WIDTH). Con esto evitamos que la pala se mueva cuando la bola está yendo hacia el otro lado.

Ahora hay que modificar el método update de la clase Ball para que reaccione al chocar contra las palas.

void Ball::update(sf::Time& delta, sf::FloatRect& p1, sf::FloatRect& p2)
{
// Obtenemos los cuatro laterales de la bola
float left = this->getPosition().x - this->getOrigin().x;
float right = this->getPosition().x + this->getOrigin().x;
float top = this->getPosition().y - this->getOrigin().y;
float bottom = this->getPosition().y + this->getOrigin().y;
// Comprobamos si choca contra las paredes
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();
}
// Por último comprobamos si ha chocado contra las palas
if (this->getGlobalBounds().intersects(p1) || this->getGlobalBounds().intersects(p2))
{
this->speed.x *= -1;
this->sound.play();
}
// Movemos la bola multiplicando la velocidad por el tiempo pasado
this->move(delta.asSeconds() * this->speed.x, delta.asSeconds() * this->speed.y);
}
view raw Ball.cpp hosted with ❤ by GitHub

Ahora recibe dos parámetros más que son dos FloatRect que seran los GlobalBounds de las palas. Con el método intersects de los rectángulos de la bola y la pala podemos ver si están chocando y en consecuencia cambiar la velocidad de la bola.

Hacemos los respectivos cambios en el Ball.hpp para que update reciba estos nuevos parámetros. Nuevamente modificamos Pong.hpp añadiendo dos objetos Paddle.

#ifndef PONG_HPP
#define PONG_HPP
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include "Ball.hpp"
#include "Paddle.hpp"
class Pong
{
public:
Pong();
void run();
private:
sf::RenderWindow window;
sf::Clock clock;
sf::Time time;
sf::Texture texture_back;
sf::Sprite background;
sf::Music music;
Ball ball;
Paddle pad_player;
Paddle pad_ia;
}; // class Pong
#endif // PONG_HPP
view raw Pong.hpp hosted with ❤ by GitHub

Y también modificamos el Pong.cpp.

#include "Pong.hpp"
#include "config.hpp"
Pong::Pong()
{
// Creamos la ventana
window.create(sf::VideoMode(WIDTH, HEIGHT, BPP), "Pong GenbetaDev", sf::Style::Close);
// Activa la sincronización vertical (60 fps)
window.setVerticalSyncEnabled(true);
// Cargamos la textura desde un archivo
texture_back.loadFromFile("data/background.png");
// Asignamos la textura al sprite de fondo
background.setTexture(texture_back);
// Se ajusta las posiciones de cada pala
pad_player.setPosition(40, HEIGHT/2);
pad_ia.setPosition(WIDTH - 40, HEIGHT/2);
// Se carga la música de fondo
music.openFromFile("data/back_music.ogg");
music.setLoop(true);
music.play();
}
void Pong::run()
{
// Game Loop mientras la ventana esté abierta
while (window.isOpen())
{
// Reiniciamos el reloj almacenando el tiempo pasado
time = clock.restart();
// Creamos un objeto evento
sf::Event event;
// Procesamos la pila de eventos
while (window.pollEvent(event))
{
// Si el evento es de tipo Closed cerramos la ventana
if (event.type == sf::Event::Closed)
window.close();
}
// Actualizamos los elementos del juego
pad_player.updateHuman(time);
pad_ia.updateIA(time, ball);
ball.update(time, pad_player.getGlobalBounds(), pad_ia.getGlobalBounds());
// Dibujamos los elementos del juego
window.draw(background);
window.draw(ball);
window.draw(pad_player);
window.draw(pad_ia);
score.show(window);
// Actualizamos la ventana
window.display();
}
}
view raw Pong.cpp hosted with ❤ by GitHub

Como vemos ahora en el constructor definimos la posición de cada una de las palas que no habíamos hecho antes. Llamamos a los métodos update de la bola y las dos pala pasando la información necesaria y a continuación dibujamos todos los objetos.

En este punto deberíamos tener un juego del Pong totalmente funcional con una pala controlada por el jugador y otra controlada por la IA.

Puntuaciones

Para ver el tratamiento de texto por parte de SFML 2 vamos a crear dos marcadores que lleven los puntos de cada jugador y los muestren en la parte superior.

La clase Score

Lo que haremos será una clase Score que llevará las puntuaciones del juego, este es el Score.hpp.

#ifndef SCORE_HPP
#define SCORE_HPP
#include <SFML/Graphics.hpp>
class Score
{
public:
Score();
void show(sf::RenderWindow& window);
void addPointPlayer();
void addPointIA();
private:
sf::Font font;
sf::Text text_player;
sf::Text text_ia;
unsigned int points_player;
unsigned int points_ia;
}; // class Score
#endif // SCORE_HPP
view raw Score.hpp hosted with ❤ by GitHub

Tenemos tres métodos show que recibe una referencia a la ventana y se encargará de dibujar en pantalla las puntuaciones. Luego tenemos addPointPlayer y addPointIA que lo que hacen es aumentar los puntos del jugador y la IA en uno respectivamente.

Como atributos tenemos un Objeto Font que será los que usen los textos, tenemos dos objetos Text que representan los textos de la puntuación del jugador y la IA. y por último tenemos dos enteros sin signo que son los puntos en sí de cada jugador.

Ahora vamos a ver el Score.cpp.

#include "Score.hpp"
#include "config.hpp"
#include <sstream>
Score::Score()
{
// Establecemos los puntos a 0
points_player = 0;
points_ia = 0;
// Cargamos la fuente
font.loadFromFile("data/OpenSans.ttf");
// Establecemos la fuente cargafa a los textos
text_player.setFont(font);
text_ia.setFont(font);
// Establecemos el tamaño de letra de los textos
text_player.setCharacterSize(30);
text_ia.setCharacterSize(30);
// Establecemos el color de los texto
text_player.setColor(sf::Color::White);
text_ia.setColor(sf::Color::White);
// Establecemos las posiciones en cada esquina superior
text_player.setPosition(20, 20);
text_ia.setPosition(WIDTH - 20 - text_ia.getLocalBounds().width, 20);
}
void Score::addPointPlayer()
{
points_player++;
}
void Score::addPointIA()
{
points_ia++;
}
void Score::show(sf::RenderWindow& window)
{
// Creamos 2 streams para pasar de int a string
std::stringstream ia;
std::stringstream ju;
// Obtenemos las cadenas desde los puntos
ia << points_ia;
ju << points_player;
// Establecemos las cadenas en los textos
text_ia.setString(ia.str());
text_player.setString(ju.str());
// Recalculamos la posición del texto derecho para que se mantenga alineado
text_ia.setPosition(WIDTH - 20 - text_ia.getLocalBounds().width, 20);
// dibujamos los textos en la ventana
window.draw(text_player);
window.draw(text_ia);
}
view raw Score.cpp hosted with ❤ by GitHub

Como siempre el código es casi autoexplicativo, la constructor inicializa los valores a 0 de las puntuaciones, cargamos la fuenta,se la asignamos a los textos, establecemos el tamaño, el color y la posición de los mismos.

Los metodos addPointPlayer y addPointIA que aumentan las variables en uno no tienen más consideración.

Por último el método show que crea dos objetos stringstream para poder convertir los objetos int en String y asignarlos a los textos con setString. Ojo, en realidad el objeto que reciben es un sf::String y lo que le estamos pasando es un std::string (de la STL), pero la clase sf::String tiene un constructor que resibe un std::String y lo convierte por lo que no debemos preocuparnos.

Por último antes de dibujarlos recalculamos la posición del texto de la derecha para que se mantenga alineado a la derecha si el contador crece a más de una cifra.

Ahora solo hay que crear un objeto Score en nuestro Pong y actualizarlo. Se marca un punto cuando conseguimos colarle la bola a la pala contraria, es decir, uno de los jugadores gana un punto cuando la bola "choca" contra el lado opuesto de la pantalla. Por tanto debe ser la bola la que asigne los puntos ya que es ella que sabe cuando choca contra el lado opuesto, así que modificamos el método update de Ball pasando una referencia al objeto Score y aumentando los puntos.

void Ball::update(sf::Time& delta, sf::FloatRect& p1, sf::FloatRect& p2, Score& score)
{
// Obtenemos los cuatro laterales de la bola
float left = this->getPosition().x - this->getOrigin().x;
float right = this->getPosition().x + this->getOrigin().x;
float top = this->getPosition().y - this->getOrigin().y;
float bottom = this->getPosition().y + this->getOrigin().y;
// Comprobamos si choca contra las paredes
if (left <= 0 && speed.x < 0)
{
this->speed.x *= -1;
this->sound.play();
score.addPointIA();
}
if (right >= WIDTH && speed.x > 0)
{
this->speed.x *= -1;
this->sound.play();
score.addPointPlayer();
}
if ((top <= 0 && speed.y < 0) || (bottom >= HEIGHT && speed.y > 0))
{
this->speed.y *= -1;
this->sound.play();
}
// Por último comprobamos si ha chocado contra las palas
if (this->getGlobalBounds().intersects(p1) || this->getGlobalBounds().intersects(p2))
{
this->speed.x *= -1;
this->sound.play();
}
// Movemos la bola multiplicando la velocidad por el tiempo pasado
this->move(delta.asSeconds() * this->speed.x, delta.asSeconds() * this->speed.y);
}
view raw ball.cpp hosted with ❤ by GitHub

Según en que lado demos anotamos un punto al jugador o a la IA. Ahora solo nos queda ver como queda nuestro Pong.hpp.

#ifndef PONG_HPP
#define PONG_HPP
#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include "Ball.hpp"
#include "Paddle.hpp"
#include "Score.hpp"
class Pong
{
public:
Pong();
void run();
private:
sf::RenderWindow window;
sf::Clock clock;
sf::Time time;
sf::Texture texture_back;
sf::Sprite background;
sf::Music music;
Ball ball;
Paddle pad_player;
Paddle pad_ia;
Score score;
}; // class Pong
#endif // PONG_HPP
view raw Pong.hpp hosted with ❤ by GitHub

Y nuestro Pong.cpp.

#include "Pong.hpp"
#include "config.hpp"
Pong::Pong()
{
// Creamos la ventana
window.create(sf::VideoMode(WIDTH, HEIGHT, BPP), "Pong GenbetaDev", sf::Style::Close);
// Activa la sincronización vertical (60 fps)
window.setVerticalSyncEnabled(true);
// Cargamos la textura desde un archivo
texture_back.loadFromFile("data/background.png");
// Asignamos la textura al sprite de fondo
background.setTexture(texture_back);
// Se ajusta las posiciones de cada pala
pad_player.setPosition(40, HEIGHT/2);
pad_ia.setPosition(WIDTH - 40, HEIGHT/2);
// Se carga la música de fondo
music.openFromFile("data/back_music.ogg");
music.setLoop(true);
music.play();
}
void Pong::run()
{
// Game Loop mientras la ventana esté abierta
while (window.isOpen())
{
// Reiniciamos el reloj almacenando el tiempo pasado
time = clock.restart();
// Creamos un objeto evento
sf::Event event;
// Procesamos la pila de eventos
while (window.pollEvent(event))
{
// Si el evento es de tipo Closed cerramos la ventana
if (event.type == sf::Event::Closed)
window.close();
}
// Actualizamos los elementos del juego
pad_player.updateHuman(time);
pad_ia.updateIA(time, ball);
ball.update(time, pad_player.getGlobalBounds(), pad_ia.getGlobalBounds(), score);
// Dibujamos los elementos del juego
window.draw(background);
window.draw(ball);
window.draw(pad_player);
window.draw(pad_ia);
score.show(window);
// Actualizamos la ventana
window.display();
}
}
view raw Pong.cpp hosted with ❤ by GitHub

Ya tenemos nuestro juego terminado, la Inteligencia Artificial es prácticamente invencible, pero podemos cambiar esto haciendo que su velocidad sea menor impidiéndole llegar a todas las bolas

Repositorio de código

He creado un repositorio del juego donde puede ver todo el código fuente final, así como el proyecto generado para Visual Studio 2010. Sería una buena iniciativa hacer forks para añadir dentro de la carpeta builds soluciones para otros entornos y/o sistemas y que se puede ejecutar el juego en el máximo número de plataformas. Si alguien se anima estaré encantado de añadirlos al proyecto principal.

Primera parte | SFML 2: Elaborando un juego completo I Github | Repositorio del juego

Comentarios cerrados
Inicio
×

Utilizamos cookies de terceros para generar estadísticas de audiencia y mostrar publicidad personalizada analizando tu navegación. Si sigues navegando estarás aceptando su uso. Más información