bilibili

下载第三方库

  • 以 SFML 库为例,到官网SFML (sfml-dev.org)下载对应编译器架构的(如 gcc 是 32 位的就安装 32 位的 SFML)对应压缩包
  • 解压到指定目录下

添加环境配置

c_cpp_properties.json

一般你配置好 vscode 中的标准 c++环境之后,都会有这个文件,c_cpp_properties.json是用来语法检查和代码提示等功能的,

所以我们要加上 SFML 库的头文件(我这里安装了mysqlsmfl)

{
  "configurations": [
    {
      "name": "Win32",
      "includePath": [
        "${workspaceFolder}/**",
        "C:\\Program Files\\MySQL\\MySQL Server 8.0\\include", //(添加)此处为mysql安装路径下的include文件夹路径,
        "D:/Develop software/code/c++Code/SFML-2.6.1-windows-gcc-13.1.0-mingw-64-bit/SFML-2.6.1/include"
      ],
      "defines": ["_DEBUG", "UNICODE", "_UNICODE"],
      "windowsSdkVersion": "10.0.22621.0",
      "compilerPath": "D:\\Develop software\\visualStudio\\VC\\Tools\\MSVC\\14.39.33519\\bin\\Hostx64\\x64\\cl.exe",
      // "compilerPath": "D://Develop software//development environment//mingw64//bin//g++.exe",
      "cStandard": "c17",
      "cppStandard": "c++17",
      "intelliSenseMode": "windows-msvc-x64"
      // "intelliSenseMode": "gcc-x64"
    }
  ],
  "version": 4
}

task.json

这个文件中包含需要的头文件路径链接指令,以及所需要的动态或者静态库,少一步骤就会出错,需要什么库的名字可以去查找安装的sfml/bin目录下有什么库的名称

{
  "tasks": [
    {
      "type": "cppbuild",
      "label": "C/C++: g++.exe 生成活动文件",
      "command": "D:\\Develop software\\development environment\\mingw64\\bin\\g++.exe",
      "args": [
        "-fdiagnostics-color=always",
        "-g",
        // "${file}", //当前文件  // "*",
        "*.cpp", //当前文件夹所有的.cpp文件都编译
        "-o",
        // "${fileDirname}\\${fileBasenameNoExtension}.exe"
        "${fileDirname}\\output.exe", //生成的可执行程序名字
        "-I",
        "C:\\Program Files\\MySQL\\MySQL Server 8.0\\include", // mysql的头文件路径
        "-I",
        "D:/Develop software/code/c++Code/SFML-2.6.1-windows-gcc-13.1.0-mingw-64-bit/SFML-2.6.1/include", // SFML的头文件路径
        "-L",
        "D:/Develop software/code/c++Code/SFML-2.6.1-windows-gcc-13.1.0-mingw-64-bit/SFML-2.6.1/lib", // SFML的lib路径
        "-L",
        "C:\\Program Files\\MySQL\\MySQL Server 8.0\\lib", // lib
        "-llibmysql",
        "-lsfml-graphics", // SFML图形库
        "-lsfml-window", // SFML窗口库
        "-lsfml-system", // SFML系统库
        "-lsfml-network" // SFML网络库
      ],
      "options": {
        "cwd": "${fileDirname}"
      },
      "problemMatcher": ["$gcc"],
      "group": {
        "kind": "build",
        "isDefault": true
      },
      "detail": "调试器生成的任务。"
    }
  ],
  "version": "2.0.0"
}

拷贝 dll 文件

bin目录下的所有(需要用到的)dll文件拷贝到你的项目下,使它能被找到(也可以修改环境变量,或者放到system32目录下)

在 VisualStudio 中配置

类似步骤,都是添加include头文件,链接指令,动态库文件

  • 解决方案管理器 -> 右键属性 -> C/C++ -> 附加包含目录 -> 添加 include 路径
  • 链接器 -> 常规 -> 附加库目录 -> 添加 lib 路径
  • 链接器 -> 输入 -> 附加依赖项 -> 编辑 -> 添加如下代码
sfml-graphics-d.lib
sfml-window-d.lib
sfml-system-d.lib
sfml-audio-d.lib
opengl32.lib
freetype.lib
winmm.lib
gdi32.lib
  • 最后把sfml/bin目录下的动态链接库复制到项目路径(和 main.cpp 同处一个目录)下

赛车游戏代码

#include <SFML/Graphics.hpp>
#include <SFML/Audio.hpp>
#include <vector>
#include <iostream>
#include <cmath>
#include <string.h>

using namespace sf;

const int WIN_WIDTH = 1024;
const int WIN_HEIGHT = 768;
const int roadWidth = 1800;
const int roadHeight = 180;
const int roadSegLength = 180;
const int roadCount = 1885; // 因为y是一个三角函数的变量,轨道长度要设为3.14的倍数1884
const int FPS = 60;
const int ItemSize = 450; // 路障大小
int socre = 0;            // 得分
const char charItem[] = "1234567890+*/-%";
struct Road
{
    // 轨道中心的3d世界坐标
    float x, y, z;
    // 轨道在屏幕中的坐标以及长度
    float X, Y, W;
    float scale;
    float curve;   // 曲线因子
    Sprite numspr; // 路障数字
    // 路障数字索引
    int operatorIndex;
    int numIndex;

    // 3d坐标转2d屏幕坐标
    void project(int cameraX, int cameraY, int cameraZ) // 转换坐标的规则,而非物体,宽度是由scale计算得来的
    {
        // 离摄像机越远,缩放数值越小(在z轴上)
        /*
                z
                ^
                |
                |
                |
                |
                ----------------->x

            y轴垂直于你,向上为正,向下为负
            所以,在摄像机前方的物体,它的z坐标值越大,它的缩放值越小
            反之,在摄像机后方的物体,它的z坐标值越小,它的缩放值越大
         */
        scale = 1.0f / (z - cameraZ); // 反比例函数,取值范围是(0,+∞)(最靠近摄像机的物体的缩放值是+∞,最远的物体的缩放值是0)
        // 3d坐标转2d屏幕坐标
        X = (1 + (x - cameraX) * scale) * WIN_WIDTH / 2; // z不变,x轴数值越大,物品在视野中的距离越远(x越大)
        Y = (1 - (y - cameraY) * scale) * WIN_HEIGHT / 2;
        W = roadWidth * scale * WIN_WIDTH / 2;
    }
    void drawItem(RenderWindow &window, int index, int xPlacement)
    {
        Sprite s = numspr;
        int left = (index % 5) * ItemSize;
        int top = (index / 5) * ItemSize;
        s.setTextureRect(IntRect(left, top, ItemSize, ItemSize)); // 裁剪到对应数字的位置
        s.setScale(W / ItemSize, W / ItemSize);
        s.setPosition(X + xPlacement * W, Y - W);
        window.draw(s);
    }

    // 重载版本,画路障
    void drawItem(RenderWindow &win)
    {
        if (operatorIndex == -1)
        {
            return;
        }
        drawItem(win, operatorIndex, -1);
        drawItem(win, numIndex, 0);
    }
    void generateItem(bool bAlwaysGen)
    {
        if (bAlwaysGen || (rand() % 200 == 0))
        {
            operatorIndex = (rand() % 5) + 10;
            numIndex = (rand() % 10);
            if (numIndex == 9) // 处理数字为0的情况
            {
                numIndex = 0;
            }
        }
        else
        {
            operatorIndex = -1;
        }
    }

    Road(int _x, int _y, int _z, float _c, Sprite _spr) : x(_x), y(_y), z(_z), curve(_c), numspr(_spr)
    {
        generateItem(false); // 随机生成路障
    }
};

int calculateScore(int socre, int operatorIndex, int numIndex)
{
    char op = charItem[operatorIndex];
    int num = charItem[numIndex] - '0'; // 将数字字符转成数字
    switch (op)
    {
    case '+':
        socre += num;
        break;
    case '-':
        socre -= num;
        break;
    case '*':
        socre *= num;
        break;
    case '/':
        if (num != 0)
        {
            socre /= num;
        }
        break;
    case '%':
        if (num != 0)
        {
            socre %= num;
        }
        break;
    default:
        break;
    }
    return socre;
}
void drawScore(RenderWindow &window, Sprite sItem, int score, int x, int y)
{
    char ch[100] = {'\0'};
    _itoa_s(score, ch, 10); // 把数字转成10进制字符串
    int len = strlen(ch);
    for (int i = 0; i < len; i++)
    {
        Sprite s = sItem;
        int index = -1;
        for (int j = 0; charItem[j] != '\0'; ++j)
        {
            if (charItem[j] == ch[i])
            {
                index = j;
                break;
            }
        }
        int left = (index % 5) * ItemSize;
        int top = (index / 5) * ItemSize;
        s.setTextureRect(IntRect(left, top, ItemSize, ItemSize));
        s.setScale(0.18f, 0.18f);
        s.setPosition(x + 0.13f * i * ItemSize, y);
        window.draw(s);
    }
}
// x1,y1,w1分别是下底的中心坐标x,y,以及一半的下底宽度w,x2,y2,w2分别是上底的中心坐标x,y,以及一半的上底宽度w
void drawTrape(RenderWindow &window, Color c, int x1, int y1, int w1, int x2, int y2, int w2)
{
    // 定义一个多边形
    ConvexShape polygon(4);
    polygon.setFillColor(c);
    // 设置多边形的点,Vector2f(x,y)表示点的坐标二维向量
    polygon.setPoint(0, Vector2f(x1 - w1, y1));
    polygon.setPoint(1, Vector2f(x2 - w2, y2));
    polygon.setPoint(2, Vector2f(x2 + w2, y2));
    polygon.setPoint(3, Vector2f(x1 + w1, y1));
    // 绘制多边形
    window.draw(polygon);
}
int main()
{
    sf::RenderWindow window(sf::VideoMode(WIN_WIDTH, WIN_HEIGHT), "Racing Game!");
    window.setFramerateLimit(FPS);

    Texture bg;
    bg.loadFromFile("cloud.png");
    Sprite bgSprite(bg, IntRect(0, 0, WIN_WIDTH, WIN_HEIGHT / 2));

    // 加载路障数字
    Texture item;
    item.loadFromFile("item.png");
    Sprite s(item);

    // 加载音乐
    Sound sound, bgm;
    sf::SoundBuffer buffer[5];
    buffer[0].loadFromFile("get.mp3");
    buffer[1].loadFromFile("jump.mp3");
    buffer[2].loadFromFile("falldown.mp3");
    buffer[3].loadFromFile("tianfuyue.mp3");
    buffer[4].loadFromFile("liumaishenjian.mp3");

    std::vector<Road> roads;
    for (int i = 0; i < roadCount; i++)
    {
        // 随机生成曲线因子
        float curve = (i > 0 && i < 300) ? 0.5 : -0.5;
        // 每一格轨道沿Z轴正方向叠放(3D坐标系)
        // Road road(0, 1600 * std::sin(i / 30.0), (i + 1) * roadSegLength, curve, s);
        Road road(0, 0, (i + 1) * roadSegLength, curve, s);
        roads.push_back(road);
    }

    int cameraZ = 0;    // 摄像机的z坐标(3d),可能超出int范围
    int cameraX = 0;    // 摄像机的x坐标(3d)
    int cameraY = 1600; // 摄像机的y坐标(3d)

    int speed = 100;        // 速度,每帧移动的距离
    bool isJumping = false; // 是否跳跃
    float dy = 0, y = 0;    // 竖直向上的速度,跳跃产生的位移

    bgm.setBuffer(buffer[3]);
    bgm.setLoop(true);
    bgm.play();

    while (window.isOpen())
    {
        sf::Event event;
        // 从事件队列中获取事件
        while (window.pollEvent(event))
        {
            if (event.type == sf::Event::Closed)
            {
                window.close();
            }
            if (Keyboard::isKeyPressed(Keyboard::Up))
            {
                speed += 2;
                if (speed > 1000)
                {
                    speed = 1000;
                }
            }
            if (Keyboard::isKeyPressed(Keyboard::Down))
            {
                speed -= 2;
                if (speed < 100)
                {
                    speed = 100;
                }
            }
        }
        cameraZ += speed; // 每帧累加速度
        if (Keyboard::isKeyPressed(Keyboard::Up))
            cameraZ += roadSegLength;
        if (Keyboard::isKeyPressed(Keyboard::Down))
            cameraZ -= roadSegLength;
        if (Keyboard::isKeyPressed(Keyboard::Left))
            cameraX -= 100;
        if (Keyboard::isKeyPressed(Keyboard::Right))
            cameraX += 100;
        if (Keyboard::isKeyPressed(Keyboard::Space) && !isJumping)
        {
            sound.setBuffer(buffer[1]);
            sound.play();
            isJumping = true;
            dy = 150;
        }
        if (isJumping)
        {
            dy -= 5;
            y += dy;
            if (y <= 0)
            {
                isJumping = false;
                y = 0;
                sound.setBuffer(buffer[2]);
                sound.play();
            }
        }

        if (Keyboard::isKeyPressed(Keyboard::LControl) && Keyboard::isKeyPressed(Keyboard::Q))
        {
            window.close();
            return 0;
        }

        // 循环使用轨道
        int totalLength = roadCount * roadSegLength;
        if (cameraZ >= totalLength)
            cameraZ -= totalLength;
        if (cameraZ < 0)
            cameraZ += totalLength;

        // 渲染窗口内容
        window.clear();
        // drawTrape(window, sf::Color::White, WIN_WIDTH / 2, 500, 200, WIN_WIDTH / 2, 300, 100);

        int roadIndex = cameraZ / roadSegLength;
        float x = 0, dx = 0;
        cameraY = 1600 + roads[roadIndex].y; // 相机随着y变化而变化会有带入感,可以试着注释掉看看效果,(如果y在顶点处,会出现相机最近的砖块过大或者看到贴图下面)
        // 此处300行是视野范围
        int minY = WIN_HEIGHT; // 可以看到的最高点y坐标(2d屏幕)

        for (int i = roadIndex; i < roadIndex + 300; ++i)
        {
            Road &now = roads[i % roadCount];
            // 每一块轨道在被转换成2d界面后,由于摄像机的x变化,转换后的形状变成斜梯形所以产生(拼接)曲线的效果
            now.project(cameraX - x, cameraY + y, cameraZ - (i >= roadCount ? totalLength : 0)); // 如果当前要绘制的方块大于总共的方块数,i>roadCount,则需要将摄像头放回起点在转换,但是实际上的cameraZ值还没有超出totalLength
            // 进行循环处理(从0开始,又因为总共的个数是3.14倍数,所以循环处理后会回到原点且没有高低差)
            dx += now.curve;
            x += dx;
            if (!i)
            {
                continue;
            }
            if (now.Y >= WIN_HEIGHT) // 你所撞击的方块(屏幕最下方的)
            {
                if (!isJumping && now.operatorIndex != -1)
                {
                    socre = calculateScore(socre, now.operatorIndex, now.numIndex);
                    now.operatorIndex = -1;
                    roads[(i + 1500) % roadCount].generateItem(true); // 重新生成道路障碍物
                    sound.setBuffer(buffer[0]);
                    sound.play();
                }
            }
            if (now.Y < minY)
            {
                minY = now.Y;
            }
            else if (now.Y >= minY)
            {
                continue; // 被遮挡住超出视野范围的方块,不绘制
            }

            bgSprite.setTextureRect(IntRect(0, 0, WIN_WIDTH, minY)); // 设置背景图片的显示范围
            window.draw(bgSprite);                                   // 绘制背景图片

            drawScore(window, s, socre, 10, 10); // 显示得分

            Road &prev = roads[(i - 1) % roadCount];

            Color grass = i % 2 ? Color(16, 210, 16) : Color(0, 199, 0); // 偶数行用白
            Color edge = i % 2 ? Color(0, 0, 0) : Color(255, 255, 255);  // 偶数行用白色,奇数行用黑色
            Color road = i % 2 ? Color(105, 105, 105) : Color(101, 101, 101);

            drawTrape(window, grass, prev.X, prev.Y, WIN_WIDTH * 10, now.X, now.Y, WIN_WIDTH * 10);
            drawTrape(window, edge, prev.X, prev.Y, prev.W * 1.3, now.X, now.Y, now.W * 1.3);
            // 先画边框,再画道路覆盖
            drawTrape(window, road, prev.X, prev.Y, prev.W, now.X, now.Y, now.W);
        }
        for (int i = roadIndex + 300; i > roadIndex; --i)
        {
            roads[i % roadCount].drawItem(window);
        }
        window.display(); // 显示渲染窗口
    }

    return 0;
}

/*
project只负责处理一个方块的3d坐标到2d屏幕坐标的转换

drawTrape因为把上一个方块的中心(2d)坐标作为绘制new轨道的下底中心坐标,宽与prev的宽一样,所以不会超出范围

curse是曲线因子,用来控制斜率,值越大,斜率越大,曲线越陡峭,值越小,斜率越小,曲线越平滑,
每一帧要绘制300块,每一块绘制完后,cameraX会改变,所以相应的新的上底的x坐标也会改变,形成曲线的效果
*/