下载第三方库
- 以 SFML 库为例,到官网SFML (sfml-dev.org)下载对应编译器架构的(如 gcc 是 32 位的就安装 32 位的 SFML)对应压缩包
- 解压到指定目录下
添加环境配置
c_cpp_properties.json
一般你配置好 vscode 中的标准 c++环境之后,都会有这个文件,c_cpp_properties.json
是用来语法检查和代码提示等功能的,
所以我们要加上 SFML 库的头文件(我这里安装了mysql
和smfl
)
{
"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坐标也会改变,形成曲线的效果
*/