Einfaches SFML-Spiel

Wir werden das Spiel in C ++ mithilfe der SFML-Bibliothek "taggen". Fünfzehn ist ein bekanntes Puzzle, das so aussieht:


Auf einem 4x4-Spielfeld werden 15 Würfel mit Zahlen von 1 bis 15 und ein freier Platz zufällig platziert. Sie können die Würfel immer nur einzeln und nur an eine leere Stelle bewegen. Das Ziel des Spiels ist es, Würfel auf dem Spielfeld in der Reihenfolge zu bauen, die ihrer Anzahl entspricht.

Also fangen wir an.

Starten Sie Visual Studio und erstellen Sie ein neues leeres Projekt. Sie können es benennen, was Sie wollen, ich habe "15" genannt. Erstellen Sie in diesem Projekt eine neue main.cpp-Datei und eine leere Hauptfunktion:

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

Laden Sie als Nächstes die SFML-Bibliothek von sfml-dev.org herunter und entpacken Sie sie. Die entpackte Bibliothek enthält die Ordner, die wir benötigen: include , lib und bin . Fügen Sie in den Projekteigenschaften im Abschnitt C / C ++ unter Zusätzliche Include-Verzeichnisse den Pfad zum Include- Ordner hinzu:


Fügen Sie dort im Abschnitt Linker in Additional Library Directories den Pfad zum lib- Ordner hinzu:


Und aus dem bin- Verzeichnis müssen Sie die DLL-Dateien kopieren und in ein Verzeichnis mit der exe-Datei unseres Projekts einfügen:


Darüber hinaus müssen Sie im Abschnitt "Linker" und im Abschnitt "Eingabe" die in " Zusätzliche Abhängigkeiten" verwendeten Bibliotheksdateien hinzufügen. In unserem Fall reicht es aus, drei Dateien hinzuzufügen: sfml-system-d.lib, sfml-window-d.lib und sfml-graphics-d.lib:


Das Symbol -d im Dateinamen bedeutet, dass es sich um eine Debug-Version handelt und in der Debug-Konfiguration verwendet werden sollte. In den Einstellungen der Release-Version müssen Sie Dateien ohne das Zeichen -d im Namen angeben.

Eine gute Anleitung zum Verbinden der SFML-Bibliothek mit dem Visual Studio-Projekt finden Sie auf der Website der Bibliothek.

Versuchen wir nun, die Bibliothek in unserem Projekt zu verwenden. Erstellen Sie ein Fenster und starten Sie die Ereignisschleife:

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


Das Ergebnis ist ein quadratisches Fenster mit einer Größe von 600 x 600 Pixel und einem schwarzen Hintergrund:


Das Fenster kann wie gewohnt mit der Maus oder über die Esc-Taste geschlossen werden. Ein Tastatur-Tastendruck-Handler ist ebenfalls in der Nachrichtenverarbeitungsschleife enthalten.

Bevor wir zur Sache kommen, benötigen wir eine Schriftart, um Text auf dem Bildschirm anzuzeigen. Zum Beispiel habe ich die Schriftart TrueType Calibri verwendet.

Jetzt können wir anfangen, unser Spiel zu machen.

Erstelle eine neue Spielklasse:


Die Klasse ist für den Betrieb des Spiels und das Rendern des Spielfelds verantwortlich. Dazu erben wir unsere Klasse von den Klassen Drawable und Transformable der SFML-Bibliothek.

Also fangen wir an, unsere Klasse zu beschreiben

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


Zuerst verbinden wir die Grafikbibliothek:

 #include <SFML/Graphics.hpp> 

Hier deklarieren wir einige Konstanten, die für das Spiel erforderlich sind:

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

Wir deklarieren auch unseren Typ enum, der die Bewegungsrichtung der Platte bestimmt:

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

Und schließlich die Klasse selbst:

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

Das Wichtigste, was wir darin haben, ist ein Array von Elementen, die ganzzahlige Werte enthalten, die dem Zustand des Spielfelds entsprechen. Die Elemente im Array entsprechen den Elementen des Spielfelds von links nach rechts, von oben nach unten, dh die ersten 4 Elemente des Arrays entsprechen der ersten Zeile des Feldes, die zweiten 4 Elemente der zweiten Zeile usw.

Als nächstes werden zwei Variablen, die bei jeder Bewegung berechnet werden, leer_index (ein Index im Array, der einer freien Zelle entspricht) und gelöst (ein Zeichen dafür, dass das Rätsel gelöst ist).

Darüber hinaus wird die Schriftvariable in der Klasse festgelegt, die die Schriftart festlegt, die beim Anzeigen von Text im Fenster verwendet wird.

Jetzt werden wir die Implementierung der Methoden unserer Klasse schreiben.

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


Der Klassenkonstruktor lädt die Schriftart aus der externen Datei und ruft die Spielinitialisierungsmethode auf:

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

Die Spielinitialisierungsmethode füllt das Array mit Elementen in der richtigen Reihenfolge und setzt das Vorzeichen des gelösten Puzzles:

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

Ja, zunächst wird das Spiel als gelöst initialisiert, und vor Spielbeginn werden die Würfel mit zufälligen Zügen gemischt.

Die folgende Methode prüft, ob das Rätsel gelöst ist, und gibt das Ergebnis der Prüfung zurück:

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

Und schließlich eine Methode, die Plattenbewegungen in einem Spiel implementiert:

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

Die letzte Methode der Klasse ist die Methode, die das Spielfeld zeichnet:

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


Bei der Rendering-Methode verwenden wir als erstes die Koordinatentransformation durch Multiplikation mit der Transformationsmatrix. Dies ist notwendig, um die Koordinaten unseres Spielfeldes einstellen zu können. Zeichnen Sie als Nächstes mithilfe der RectangleShape-Objekte der SFML-Bibliothek die Ränder des Spielfelds und die Ränder jedes Würfels im Spiel. Auf die Würfel zeichnen wir auch den Text mit der Kennzeichen. Wenn das Rätsel gelöst ist, wird die Farbe der Würfel außerdem anders ausgeführt.

Es ist Zeit, zur Hauptfunktion zurückzukehren:

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


Laden Sie zunächst die Schriftart und erstellen Sie ein Textobjekt, um eine Textzeile mit der Zuweisung von Schlüsseln anzuzeigen. Als nächstes erstellen Sie unser Spielobjekt und setzen die Position des Feldes auf einen Punkt mit Koordinaten (50,50) - so rücken wir vom Rand des Fensters ein.

Ich habe beschlossen, das Spiel über die Tastatur zu steuern. Bei jedem Drücken der Pfeiltasten rufen wir die Move-Methode für das Spielobjekt auf, um die Platte in die entsprechende Richtung zu bewegen.

Das Drücken der Taste F2 ist der Beginn eines neuen Spiels. Im Handler dieses Ereignisses initialisieren wir das Spiel neu (was dazu führt, dass Würfel an ihren Stellen platziert werden) und setzen den Wert des Zählers für Züge auf 100. Dieser Zähler wird weiter verwendet, um Züge in zufällige Richtungen auszuführen, bis wird nicht zurückgesetzt und die Würfel mischen sich nicht. Somit werden wir definitiv den gelösten Zustand des Puzzles bekommen.

Das ist im Grunde alles, kompilieren, zusammenbauen, ausführen:



In diesem Artikel habe ich gezeigt, wie Sie mit der SFML-Bibliothek schnell ein einfaches C ++ - Spiel erstellen können. Die Architektur des Programms selbst ist jedoch alles andere als ideal. Im nächsten Artikel werden wir versuchen, etwas dagegen zu unternehmen.

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


All Articles