Juego SFML simple

Haremos que el juego "etiquete" en C ++ usando la biblioteca SFML. Quince es un rompecabezas bien conocido que se ve así:


En un campo de juego 4x4, 15 dados con números del 1 al 15 y un espacio libre se ubican al azar. Solo puedes mover los dados uno a la vez y solo a un lugar vacío. El objetivo del juego es construir dados en el campo de juego en el orden correspondiente a sus números.

Entonces comencemos.

Inicie Visual Studio y cree un nuevo proyecto vacío. Puedes nombrarlo como quieras, lo llamé "15". En este proyecto, cree un nuevo archivo main.cpp y una función principal vacía:

// main.cpp int main() { return 0; } 

A continuación, descargue la biblioteca SFML de sfml-dev.org y descomprímalo. La biblioteca desempaquetada contiene las carpetas que necesitamos: include , lib y bin . En las propiedades del proyecto en la sección C / C ++ en Directorios de inclusión adicionales, agregue la ruta a la carpeta de inclusión :


Allí, en la sección Linker en Directorios de bibliotecas adicionales, agregue la ruta a la carpeta lib :


Y desde el directorio bin , debe copiar los archivos DLL y colocarlos en un directorio con el archivo exe de nuestro proyecto:


Además, en la sección Enlace, en la sección Entrada, debe agregar los archivos de biblioteca utilizados en Dependencias adicionales . En nuestro caso, es suficiente agregar tres archivos: sfml-system-d.lib, sfml-window-d.lib y sfml-graphics-d.lib:


El símbolo -d en el nombre del archivo significa que es una versión de depuración y debe usarse en la configuración de depuración. En la configuración de la versión de lanzamiento, deberá especificar archivos sin el carácter -d en el nombre.

Una buena instrucción sobre cómo conectar la biblioteca SFML al proyecto de Visual Studio se encuentra en el sitio web de la biblioteca.

Ahora intentemos usar la biblioteca en nuestro proyecto. Cree una ventana e inicie el bucle de eventos:

main.cpp
 // main.cpp #include <SFML/Graphics.hpp> int main() { //    600  600    60    sf::RenderWindow window(sf::VideoMode(600, 600), "15"); window.setFramerateLimit(60); sf::Event event; while (window.isOpen()) { while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); if (event.type == sf::Event::KeyPressed) { //    -    if (event.key.code == sf::Keyboard::Escape) window.close(); } } //      window.clear(); window.display(); } return 0; } 


El resultado será una ventana cuadrada que mide 600 por 600 píxeles con un fondo negro:


La ventana se puede cerrar de la manera habitual con el mouse o con la tecla Esc. Un controlador de teclado también se incluye en el bucle de procesamiento de mensajes.

Antes de comenzar a trabajar, necesitamos algún tipo de fuente para mostrar el texto en la pantalla. Por ejemplo, tomé la fuente TrueType Calibri.

Ahora podemos comenzar a hacer nuestro juego.

Crea una nueva clase de juego:


La clase será responsable del funcionamiento del juego y de representar el campo de juego. Para hacer esto, heredaremos nuestra clase de las clases Drawable y Transformable de la biblioteca SFML.

Entonces, comenzamos a describir nuestra clase

Game.h
 #pragma once #include <SFML/Graphics.hpp> const int SIZE = 4; //      const int ARRAY_SIZE = SIZE * SIZE; //   const int FIELD_SIZE = 500; //      const int CELL_SIZE = 120; //     enum class Direction { Left = 0, Right = 1, Up = 2, Down = 3 }; class Game : public sf::Drawable, public sf::Transformable { protected: int elements[ARRAY_SIZE]; int empty_index; bool solved; sf::Font font; public: Game(); void Init(); bool Check(); void Move(Direction direction); public: virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; }; 


Primero, conectamos la biblioteca de gráficos:

 #include <SFML/Graphics.hpp> 

Aquí declaramos algunas constantes necesarias para el juego:

 const int SIZE = 4; //      const int ARRAY_SIZE = SIZE * SIZE; //    const int FIELD_SIZE = 500; //      const int CELL_SIZE = 120; //     

También declaramos nuestro tipo enum, que determina la dirección del movimiento de la placa:

 enum class Direction { Left = 0, Right = 1, Up = 2, Down = 3 }; 

Y finalmente, la clase misma:

 class Game : public sf::Drawable, public sf::Transformable { protected: int elements[ARRAY_SIZE]; int empty_index; bool solved; sf::Font font; public: Game(); void Init(); bool Check(); void Move(Direction direction); public: virtual void draw(sf::RenderTarget& target, sf::RenderStates states) const; }; 

Lo más importante que tenemos es un conjunto de elementos que contienen valores enteros correspondientes al estado del campo de juego. Los elementos en la matriz corresponden a los elementos del campo de juego de izquierda a derecha, de arriba a abajo, es decir, los primeros 4 elementos de la matriz corresponden a la primera línea del campo, los segundos 4 elementos a la segunda línea, etc.

A continuación, dos variables que se calcularán en cada movimiento son empty_index (un índice en la matriz correspondiente a una celda libre) y resueltas (una señal de que el rompecabezas está resuelto).

Además, la variable de fuente se establece en la clase, que determina la fuente que se utilizará al mostrar texto en la ventana.

Ahora escribiremos la implementación de los métodos de nuestra clase.

Game.cpp
 #include "Game.h" Game::Game() { //      font.loadFromFile("calibri.ttf"); Init(); } void Game::Init() { //    for (int i = 0; i < ARRAY_SIZE - 1; i++) elements[i] = i + 1; //        empty_index = ARRAY_SIZE - 1; elements[empty_index] = 0; //     = 0 solved = true; } bool Game::Check() { //    for (unsigned int i = 0; i < ARRAY_SIZE; i++) { if (elements[i] > 0 && elements[i] != i + 1) return false; } return true; } void Game::Move(Direction direction) { //       int col = empty_index % SIZE; int row = empty_index / SIZE; //          int move_index = -1; if (direction == Direction::Left && col < (SIZE - 1)) move_index = empty_index + 1; if (direction == Direction::Right && col > 0) move_index = empty_index - 1; if (direction == Direction::Up && row < (SIZE - 1)) move_index = empty_index + SIZE; if (direction == Direction::Down && row > 0) move_index = empty_index - SIZE; //      if (empty_index >= 0 && move_index >= 0) { int tmp = elements[empty_index]; elements[empty_index] = elements[move_index]; elements[move_index] = tmp; empty_index = move_index; } solved = Check(); } void Game::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform *= getTransform(); sf::Color color = sf::Color(200, 100, 200); //     sf::RectangleShape shape(sf::Vector2f(FIELD_SIZE, FIELD_SIZE)); shape.setOutlineThickness(2.f); shape.setOutlineColor(color); shape.setFillColor(sf::Color::Transparent); target.draw(shape, states); //       shape.setSize(sf::Vector2f(CELL_SIZE - 2, CELL_SIZE - 2)); shape.setOutlineThickness(2.f); shape.setOutlineColor(color); shape.setFillColor(sf::Color::Transparent); //        sf::Text text("", font, 52); for (unsigned int i = 0; i < ARRAY_SIZE; i++) { shape.setOutlineColor(color); text.setFillColor(color); text.setString(std::to_string(elements[i])); if (solved) { //      shape.setOutlineColor(sf::Color::Cyan); text.setFillColor(sf::Color::Cyan); } else if (elements[i] == i + 1) { //        text.setFillColor(sf::Color::Green); } //   ,   if (elements[i] > 0) { //      sf::Vector2f position(i % SIZE * CELL_SIZE + 10.f, i / SIZE * CELL_SIZE + 10.f); shape.setPosition(position); //     text.setPosition(position.x + 30.f + (elements[i] < 10 ? 15.f : 0.f), position.y + 25.f); target.draw(shape, states); target.draw(text, states); } } } 


El constructor de la clase carga la fuente del archivo externo y llama al método de inicialización del juego:

 Game::Game() { //      font.loadFromFile("calibri.ttf"); Init(); } 

El método de inicialización del juego llena la matriz con elementos en el orden correcto y establece el signo del rompecabezas resuelto:

 void Game::Init() { //    for (int i = 0; i < ARRAY_SIZE - 1; i++) elements[i] = i + 1; //   -     empty_index = ARRAY_SIZE - 1; elements[empty_index] = 0; //     = 0 solved = true; } 

Sí, inicialmente el juego se inicializará como resuelto, y antes del comienzo del juego mezclaremos los dados usando movimientos aleatorios.

El siguiente método verifica si el rompecabezas está resuelto y devuelve el resultado de la verificación:

 bool Game::Check() { //    for (unsigned int i = 0; i < ARRAY_SIZE; i++) { if (elements[i] > 0 && elements[i] != i + 1) return false; } return true; } 

Y finalmente, un método que implementa el movimiento de placas en un juego:

 void Game::Move(Direction direction) { //       int col = empty_index % SIZE; int row = empty_index / SIZE; //          int move_index = -1; if (direction == Direction::Left && col < (SIZE - 1)) move_index = empty_index + 1; if (direction == Direction::Right && col > 0) move_index = empty_index - 1; if (direction == Direction::Up && row < (SIZE - 1)) move_index = empty_index + SIZE; if (direction == Direction::Down && row > 0) move_index = empty_index - SIZE; //      if (empty_index >= 0 && move_index >= 0) { int tmp = elements[empty_index]; elements[empty_index] = elements[move_index]; elements[move_index] = tmp; empty_index = move_index; } solved = Check(); } 

El último método de la clase es el método que dibuja el campo de juego:

dibujar
 void Game::draw(sf::RenderTarget& target, sf::RenderStates states) const { states.transform *= getTransform(); sf::Color color = sf::Color(200, 100, 200); //     sf::RectangleShape shape(sf::Vector2f(FIELD_SIZE, FIELD_SIZE)); shape.setOutlineThickness(2.f); shape.setOutlineColor(color); shape.setFillColor(sf::Color::Transparent); target.draw(shape, states); //       shape.setSize(sf::Vector2f(CELL_SIZE - 2, CELL_SIZE - 2)); shape.setOutlineThickness(2.f); shape.setOutlineColor(color); shape.setFillColor(sf::Color::Transparent); //        sf::Text text("", font, 52); for (unsigned int i = 0; i < ARRAY_SIZE; i++) { shape.setOutlineColor(color); text.setFillColor(color); text.setString(std::to_string(elements[i])); if (solved) { //      shape.setOutlineColor(sf::Color::Cyan); text.setFillColor(sf::Color::Cyan); } else if (elements[i] == i + 1) { //        text.setFillColor(sf::Color::Green); } //   ,   if (elements[i] > 0) { //      sf::Vector2f position(i % SIZE * CELL_SIZE + 10.f, i / SIZE * CELL_SIZE + 10.f); shape.setPosition(position); //     text.setPosition(position.x + 30.f + (elements[i] < 10 ? 15.f : 0.f), position.y + 25.f); //    target.draw(shape, states); //    target.draw(text, states); } } } 


En el método de representación, lo primero que usamos es la transformación de coordenadas, multiplicando por la matriz de transformación. Esto es necesario para poder establecer las coordenadas de nuestro campo de juego. Luego, usando los objetos RectangleShape de la biblioteca SFML, dibuja los bordes del campo de juego y los bordes de cada dado en el juego. En los dados también dibujamos el texto con el número de la placa. Además, si se resuelve el rompecabezas, entonces el color de los dados se hace de manera diferente.

Es hora de volver a la función principal:

main.cpp
 // main.cpp #include <SFML/Graphics.hpp> #include "Game.h" int main() { //    600  600    60    sf::RenderWindow window(sf::VideoMode(600, 600), "15"); window.setFramerateLimit(60); sf::Font font; font.loadFromFile("calibri.ttf"); //     sf::Text text("F2 - New Game / Esc - Exit / Arrow Keys - Move Tile", font, 20); text.setFillColor(sf::Color::Cyan); text.setPosition(5.f, 5.f); //    Game game; game.setPosition(50.f, 50.f); sf::Event event; int move_counter = 0; //       while (window.isOpen()) { while (window.pollEvent(event)) { if (event.type == sf::Event::Closed) window.close(); if (event.type == sf::Event::KeyPressed) { //    -    if (event.key.code == sf::Keyboard::Escape) window.close(); if (event.key.code == sf::Keyboard::Left) game.Move(Direction::Left); if (event.key.code == sf::Keyboard::Right) game.Move(Direction::Right); if (event.key.code == sf::Keyboard::Up) game.Move(Direction::Up); if (event.key.code == sf::Keyboard::Down) game.Move(Direction::Down); //   if (event.key.code == sf::Keyboard::F2) { game.Init(); move_counter = 100; } } } //     ,    if (move_counter-- > 0) game.Move((Direction)(rand() % 4)); //      window.clear(); window.draw(game); window.draw(text); window.display(); } return 0; } 


Primero, cargue la fuente y cree un objeto de Texto para mostrar una línea de texto con la asignación de teclas. A continuación, cree nuestro objeto de juego y establezca la posición del campo en un punto con coordenadas (50.50); ​​así es como sangramos desde el borde de la ventana.

Decidí controlar el juego a través del teclado, por lo que cada vez que presionamos las teclas de flecha llamamos al método Move en el objeto del juego para mover la placa en la dirección correspondiente.

Presionar la tecla F2 es el comienzo de un nuevo juego, por lo que en el controlador de este evento reiniciamos el juego (lo que conducirá a la colocación de dados en sus lugares), y también establecemos el valor del contador de movimientos en 100. Este contador se usa más para ejecutar movimientos en direcciones aleatorias, hasta no se reiniciará y los dados no se mezclarán. Por lo tanto, definitivamente obtendremos el estado resuelto del rompecabezas.

Eso es básicamente todo, compilar, ensamblar, ejecutar:



En este artículo, mostré cómo puedes crear rápidamente un juego simple de C ++ usando la biblioteca SFML. Sin embargo, la arquitectura del programa en sí está lejos de ser ideal. En el próximo artículo, intentaremos hacer algo al respecto.

Source: https://habr.com/ru/post/449596/


All Articles