Jogo SFML simples

Faremos o "tag" do jogo em C ++ usando a biblioteca SFML. Quinze é um quebra-cabeça conhecido que se parece com isso:


Em um campo de jogo 4x4, 15 dados com números de 1 a 15 e um espaço livre são localizados aleatoriamente. Você só pode mover os dados um de cada vez e apenas para um local vazio. O objetivo do jogo é construir dados no campo de jogo na ordem correspondente aos seus números.

Então, vamos começar.

Inicie o Visual Studio e crie um novo projeto vazio. Você pode nomear o que quiser, eu chamei de "15". Neste projeto, crie um novo arquivo main.cpp e uma função principal vazia:

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

Em seguida, baixe a biblioteca SFML em sfml-dev.org e descompacte-a. A biblioteca descompactada contém as pastas que precisamos: include , lib e bin . Nas propriedades do projeto na seção C / C ++ em Diretórios de inclusão adicionais , adicione o caminho para a pasta de inclusão :


Lá, na seção Vinculador em Diretórios Adicionais da Biblioteca, adicione o caminho para a pasta lib :


E do diretório bin , você precisa copiar os arquivos DLL e colocá-los em um diretório com o arquivo exe do nosso projeto:


Além disso, na seção Vinculador, na seção Entrada, você precisa adicionar os arquivos de biblioteca usados ​​em Dependências adicionais . No nosso caso, basta adicionar três arquivos: sfml-system-d.lib, sfml-window-d.lib e sfml-graphics-d.lib:


O símbolo -d no nome do arquivo significa que é uma versão de depuração e deve ser usada na configuração de depuração. Nas configurações da versão de lançamento, você precisará especificar arquivos sem o caractere -d no nome.

Uma boa instrução sobre como conectar a biblioteca SFML ao projeto Visual Studio está no site da biblioteca.

Agora vamos tentar usar a biblioteca em nosso projeto. Crie uma janela e inicie o loop 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; } 


O resultado será uma janela quadrada medindo 600 por 600 pixels com fundo preto:


A janela pode ser fechada da maneira usual com o mouse ou através da tecla Esc. Um manipulador de pressionamento de tecla do teclado também está incluído no loop de processamento de mensagens.

Antes de começarmos a trabalhar, precisamos de algum tipo de fonte para exibir o texto na tela. Por exemplo, peguei a fonte TrueType Calibri.

Agora podemos começar a fazer o nosso jogo.

Crie uma nova classe de jogo:


A classe será responsável pela operação do jogo e pela renderização do campo de jogo. Para fazer isso, herdaremos nossa classe das classes Drawable e Transformable da biblioteca SFML.

Então, começamos a descrever nossa classe

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; }; 


Primeiro, conectamos a biblioteca de gráficos:

 #include <SFML/Graphics.hpp> 

Aqui declaramos algumas constantes necessárias para o jogo:

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

Também declaramos nosso tipo enum, que determina a direção do movimento da placa:

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

E, finalmente, a própria classe:

 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; }; 

A coisa mais importante que temos nele é uma matriz de elementos contendo valores inteiros correspondentes ao estado do campo de jogo. Os elementos na matriz correspondem aos elementos do campo de jogo da esquerda para a direita, de cima para baixo, ou seja, os 4 primeiros elementos da matriz correspondem à primeira linha do campo, os segundos 4 elementos à segunda linha, etc.

A seguir, duas variáveis ​​que serão calculadas a cada movimento são empty_index (um índice na matriz correspondente a uma célula livre) e resolvidas (um sinal de que o quebra-cabeça foi resolvido).

Além disso, a variável de fonte é definida na classe, que determina a fonte que será usada ao exibir texto na janela.

Agora vamos escrever a implementação dos métodos da nossa classe.

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); } } } 


O construtor da classe carrega a fonte do arquivo externo e chama o método de inicialização do jogo:

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

O método de inicialização do jogo preenche a matriz com elementos na ordem correta e define o sinal do quebra-cabeça resolvido:

 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; } 

Sim, inicialmente o jogo será inicializado como resolvido e, antes do início do jogo, misturaremos os dados usando movimentos aleatórios.

O método a seguir verifica se o quebra-cabeça foi resolvido e retorna o resultado da verificação:

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

E, finalmente, um método que implementa o movimento da placa em um jogo:

 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(); } 

O último método da classe é o método que desenha o campo de jogo:

desenhar
 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); } } } 


No método de renderização, a primeira coisa que usamos é a transformação de coordenadas, multiplicando pela matriz de transformação. Isso é necessário para poder definir as coordenadas do nosso campo de jogo. Em seguida, usando os objetos RectangleShape da biblioteca SFML, desenhe as bordas do campo de jogo e as bordas de cada dado no jogo. Nos dados, também desenhamos o texto com o número da placa. Além disso, se o quebra-cabeça for resolvido, a cor dos dados será feita de maneira diferente.

É hora de retornar à função 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; } 


Primeiro, carregue a fonte e crie um objeto Texto para exibir uma linha de texto com a atribuição de chaves. Em seguida, crie nosso objeto de jogo e defina a posição do campo em um ponto com coordenadas (50.50) - é assim que recuamos da borda da janela.

Decidi controlar o jogo pelo teclado, então, a cada pressionamento das teclas de seta, chamamos o método Move no objeto do jogo para mover a placa na direção correspondente.

Pressionar a tecla F2 é o início de um novo jogo; portanto, no manipulador deste evento, reinicializamos o jogo (o que levará à colocação dos dados em seus lugares) e também definimos o valor do contador de movimentos como 100. Esse contador é usado ainda mais para executar movimentos em direções aleatórias, até não será redefinido e os dados não serão misturados. Assim, definitivamente obteremos o estado resolvido do quebra-cabeça.

Isso é basicamente tudo, compilar, montar, executar:



Neste artigo, mostrei como você pode criar rapidamente um jogo C ++ simples usando a biblioteca SFML. No entanto, a arquitetura do próprio programa está longe de ser ideal. No próximo artigo, tentaremos fazer algo sobre isso.

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


All Articles