简单的SFML游戏

我们将使用SFML库在C ++中将游戏制作为“标记”。 十五是一个著名的难题,看起来像这样:


在4x4的游戏环境中,随机放置15个骰子,每个骰子的数字从1到15,自由空间为1。 一次只能将一个骰子移动到一个空的位置。 游戏的目的是按照对应于其编号的顺序在游戏场上建立骰子。

因此,让我们开始吧。

启动Visual Studio并创建一个新的空项目。 您可以随意命名,我叫“ 15”。 在此项目中,创建一个新的main.cpp文件和一个空的main函数:

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

接下来,从sfml-dev.org下载SFML库并解压缩。 解压缩的库包含我们需要的文件夹: includelibbin 。 在其他包含目录的C / C ++部分的项目属性中,将路径添加到包含文件夹:


在“ 其他库目录”的“链接器”部分中将路径添加到lib文件夹:


bin目录中,您需要复制DLL文件,并将其与我们项目的exe文件放置在目录中:


此外,在“链接器”部分的“输入”部分,您需要添加“ 其他依赖项”中使用的库文件。 在我们的例子中,添加三个文件就足够了:sfml-system-d.lib,sfml-window-d.lib和sfml-graphics-d.lib:


文件名中的-d符号表示它是调试版本,应在Debug配置中使用。 在发行版本的设置中,您将需要指定名称中没有-d字符的文件。

网站上有关于将SFML库连接到Visual Studio项目的很好的说明。

现在,让我们尝试在我们的项目中使用该库。 创建一个窗口并启动事件循环:

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


结果将是一个黑色背景为600 x 600像素的方形窗口:


可以使用鼠标或通过Esc键以通常的方式关闭窗口。 键盘按键处理程序也包含在消息处理循环中。

开始工作之前,我们需要某种字体在屏幕上显示文本。 例如,我采用了TrueType Calibri字体。

现在我们可以开始制作游戏了。

创建一个新的游戏类:


该班将负责游戏的操作和渲染比赛场地。 为此,我们将从SFML库的Drawable和Transformable类继承我们的类。

因此,我们开始描述我们的课程

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


首先,我们连接图形库:

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

我们拥有的最重要的东西是一个元素数组,其中包含与比赛场地的状态相对应的整数值。 数组中的元素从左到右,从上到下与比赛场地的元素相对应,也就是说,数组的前4个元素与场地的第一行相对应,后4个元素与第二行等等。

接下来,每个动作将要计算的两个变量是Empty_index (数组中与空闲单元格相对应的索引)并已求解 (表明难题已解决)。

此外,在类中设置了font变量,该变量确定在窗口中显示文本时将使用的字体。

现在,我们编写类方法的实现。

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


类构造函数从外部文件加载字体并调用游戏初始化方法:

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


在渲染方法中,我们首先使用的是坐标变换,即乘以变换矩阵。 为了能够设置比赛场地的坐标,这是必需的。 接下来,使用SFML库的RectangleShape对象,绘制运动场的边界以及游戏中每个骰子的边界。 在骰子上,我们还绘制带有车牌号的文本。 另外,如果解决了难题,那么骰子的颜色也会不同。

现在该返回到主要功能了:

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


首先,加载字体并创建一个Text对象以显示带有键分配的文本行。 接下来,创建游戏对象并将字段的位置设置为具有坐标(50.50)的点-这就是我们从窗口边缘缩进的方式。

我决定通过键盘来控制游戏,因此每次按箭头键时,我们都会在游戏对象上调用Move方法,以沿相应的方向移动盘子。

按下F2键是新游戏的开始,因此在该事件的处理程序中,我们重新初始化游戏(这将导致骰子在其位置的放置),并将移动计数器的值设置为100。此计数器进一步用于在随机方向执行移动,直到不会重置,骰子也不会混合。 因此,我们肯定会获得解决难题的状态。

基本上就可以了,编译,组装,运行:



在本文中,我展示了如何使用SFML库快速创建一个简单的C ++游戏。 但是,程序本身的体系结构远非理想。 在下一篇文章中,我们将尝试对此做一些事情。

Source: https://habr.com/ru/post/zh-CN449596/


All Articles