基础项目实战——3D赛车(c++)
目录
- 前言
- 一、渲染引擎
- 二、关闭事件
- 三、梯形绘制
- 四、轨道绘制
- 五、边缘绘制
- 六、草坪绘制
- 七、前后移动
- 八、左右移动
- 九、曲线轨道
- 十、课山坡轨道
- 十一、循环轨道
- 十二、背景展示
- 十三、引入速度
- 十四、物品绘制
- 十五、课数字路障
- 十六、分数展示
- 十七、重新生成
- 十八、跳跃功能
- 十九、课音效播放
- 二十、音乐播放
- 二十一、音乐切换
前言
这一期我们一起学期3d赛车这个项目实战,这个项目比之前的那几个难度高了很多,所以做起来会比较复杂,里面会涉及一些数学思想。这个项目做完了之后非常有意思还是值得一做的。
完整工程文件,提取码为1111
(蓝色字体可以点进去)
一、渲染引擎
做这个项目我们要自己到网上下载一些库,要配置环境。所以配置环境可以看我上一期扫雷(蓝色字体可以点进去)
#include <SFML/Graphics.hpp>
using namespace sf;const int WinWidth = 1024;
const int WinHeight = 768;int main() {
RenderWindow win(VideoMode(WinWidth, WinHeight), "Racing");win.setFramerateLimit(60); // 设置帧率为 60 帧
while (win.isOpen()) {
}
return 0;
}
运行之后我不得到一个不可以移动的窗口。
二、关闭事件
这样就可以关闭和移动窗口了。
while (win.isOpen()) {Event e;
while (win.pollEvent(e)) {
if (e.type == Event::Closed) {win.close();}}}
三、梯形绘制
这个梯形就代表每一块道路的图案
void DrawTrape(RenderWindow& window, Color c, int x1, int y1, int w1, int x2, int y2, int w2) {ConvexShape polygon(4);//梯形的四个定点polygon.setFillColor(c);//设置梯形填充的颜色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);
}
win.clear();
DrawTrape(win, Color::White, WinWidth / 2, 500, 200, WinWidth / 2, 300, 100);win.display();
四、轨道绘制
const int roadWidth = 1800;
const int roadSegLength = 180;
const int roadCount = 1884;
对于每个轨道,用一个结构体来实现,小写的 x,y,z 代表,每个轨道的中心,在 3D 世界中的坐标,大写的 X 和 Y,则是 3D 映射到 2D 时的屏幕坐标,大写的 W 代表这条轨道映射到屏幕后,它的宽度。
实现一个带参构造函数,利用初始化列表进行初始化。
然后实现一个 project 函数,代表 3D 到 2D 的映射,camX,camY,camZ 代表的是 3D 相机的位置,接下来我会讲一些数学的概念。听不懂没有关系,只要你能够自己跟着把代码写出来,并且通过修改每个参数,运行后得到你想要的效果就可以了。不用太计较这里的数学计算。
首先,当相机位置 camZ 确定时,z 越大,那么这条轨道就应该越小,所以实现这么一个反比例函数,计算缩放比例 scale。
利用这个缩放比例,可以计算出 X 和 Y,像这样。
最后计算 W,也就是每个轨道的实际屏幕宽度,和 scale 强相关,所以作为系数乘上去。
struct Road {
float x, y, z;
float X, Y, W;
float scale;
Road(int _x, int _y, int _z): x(_x), y(_y), z(_z) {}
void project(int camX, int camY, int camZ) {scale = 1.0 / (z - camZ);X = (1 + (x - camX) * scale) * WinWidth / 2;Y = (1 - (y - camY) * scale) * WinHeight / 2;W = scale * roadWidth * WinWidth / 2;}
};
定义一个轨道的 vector,每个轨道的 x 和 y 都是 0,z 坐标按照 i 进行平铺。
vector<Road> roads;
for (int i = 0; i < roadCount; ++i) {
Road r(0, 0, (i+1) * roadSegLength);roads.push_back(r);}
因为近大远小的关系,所以屏幕上只显示 300 个轨道,对于每个轨道,调用 project 计算投影,传入的是相机的位置,其中 y 代表竖直方向,设定为 1600。
获取当前轨道和前一个轨道,分别为 now 和 pre,利用这两个轨道,可以计算出一个梯形,并且设置好颜色,稍微做点差异化,产生相间的效果。运行看下效果。
for (int i = 0; i < 300; ++i) {Road& now = roads[i % roadCount];now.project(0, 1600, 0);
if (!i) {
continue;}Road& pre = roads[(i - 1) % roadCount];Color road = i % 2 ? Color(105, 105, 105) :
DrawTrape(win, road, pre.X, pre.Y, pre.W, now.X, now.Y, now.W);}
当然,可以在 i 这里除上一个数,使得两个颜色区分的时候,不会那么密集。看下效果。这里如果你不理解,怎么办呢?你就多试几个数字,试着试着感觉就有了。
Color road = (i/3) % 2 ? Color(105, 105, 105) : Color(101, 101, 101);
五、边缘绘制
Color edge = (i/3) % 2 ? Color(0, 0, 0) : Color(255, 255, 255);
DrawTrape(win, edge, pre.X, pre.Y, pre.W*1.3, now.X, now.Y, now.W*1.3);
六、草坪绘制
Color grass = i / 3 % 2 ? Color(16, 210, 16) : Color(0, 199, 0);
DrawTrape(win, grass, pre.X, pre.Y, WinWidth, now.X, now.Y, WinWidth);
七、前后移动
for (int i = 0; i < roadCount; ++i) {
Road r(0, 0, (i+1) * roadSegLength);roads.push_back(r);}
int cameraZ = 0;
while (win.isOpen()) {
if (Keyboard::isKeyPressed(Keyboard::Up)) cameraZ += roadSegLength;
if (Keyboard::isKeyPressed(Keyboard::Down)) cameraZ -= roadSegLength;win.clear();
int roadIndex = cameraZ / roadSegLength;for (int i = roadIndex; i < roadIndex + 300; ++i) {Road& now = roads[i % roadCount];now.project(0, 1600, cameraZ );
八、左右移动
除了前后移动,左右也可以移动。同样是控制相机的坐标,定义 cameraX 代表相机的 x坐标。
int cameraZ = 0;
int cameraX = 0;
分别按下左键和右键时,更改 cameraX 的值。
if (Keyboard::isKeyPressed(Keyboard::Left)) cameraX -= 100;
if (Keyboard::isKeyPressed(Keyboard::Right)) cameraX += 100;
然后在这里把 cameraX 代替原来 0 的值。
now.project(cameraX, 1600, cameraZ);
运行,发现露馅了。没事,宽度乘10就好了。
DrawTrape(win, grass, pre.X, pre.Y, 10*WinWidth, now.X, now.Y, 10*WinWidth);
九、曲线轨道
这节课我们想办法让这条路,能够变成弯的,看起来更加的真实,在原来的路上,加上一个曲线因子。
float scale, curve;Road(int _x, int _y, int _z, float _c): x(_x), y(_y), z(_z), curve(_c) {}
然后在实例化 Road 的时候,当 i 在 0 到 300 之间,曲线因子就是 0.5,否则就是负的 0.5。构造的时候,传参传进去。
float curve = (i > 0 && i < 300) ? 0.5 : -0.5;Road r(0, 0, (i+1) * roadSegLength, curve);
然后定义一个 x 和 dx,初始化都为 0,这段代码怎么去理解呢?这里的 now.curve 就理解成加速度,那么 dx 就是速度,而 x 就是位移,这样这条路弯曲起来就看起来非常的连续了,看下效果。
int roadIndex = cameraZ / roadSegLength;
float x = 0, dx = 0;
for (int i = roadIndex; i < roadIndex + 300; ++i) {Road& now = roads[i % roadCount];now.project(cameraX - x, 1600, cameraZ);x += dx;dx += now.curve;
十、课山坡轨道
然后我希望这个轨道,有一种跌宕起伏的感觉,三角函数可以做到这一点,所以每个轨道的中心的 y 坐标,用一个三角函数 sin 来实现,振幅 1600,像这样:
相机的位置从 1600 作为基准,再加上起始轨道的 y 坐标,像这样。
int cameraY = 1600 + roads[roadIndex].y;
int minY = WinHeight;
这里的 1600 就可以改成这个 cameraY 了。所以这个 cameraY 的范围,就根据正弦函数的值,变成了 0 到 3200。
now.project(cameraX - x, cameraY, cameraZ);
这时候我们发现,效果好像不对,问题出在哪里呢?
原因是这样的。
需要注意的是,一旦出现跌宕起伏,那么如果出现一个 Y 值最小的轨道,后面比 Y 值大的轨道,就不应该被绘制出来,注意这里用的屏幕坐标系,所以越往上值越小。
int roadIndex = cameraZ / roadSegLength;
float x = 0, dx = 0;
int cameraY = 1600 + roads[roadIndex].y;
int minY = WinHeight;
那么判断当前的 Y 是否比最小的 Y 小,如果小,那么记录这个最小值;否则直接 continue,代表这条轨道不用绘制,来看看效果。
if (now.Y < minY) {minY = now.Y;else {
continue;}
十一、循环轨道
然后我们希望整条路,能够产生循环,这样就可以,用有限的 Road 对象来展现无限的路。
计算这条路的总长度 totalLength,如果发现 cameraZ 的位置已经大于总长度了,就减去总长度,如果小于总长度,就变成负数了,那么加上总长度。
其实就是让 cameraZ 的值,始终保持在 [0, totalLength) 之间。
int totalLength = roadCount * roadSegLength;
while (cameraZ >= totalLength) cameraZ -= totalLength;
while (cameraZ < 0) cameraZ += totalLength;
now.project(cameraX - x, cameraY, cameraZ - (i >= roadCount ? totalLength : 0) );
然后你会发现,这里出现了一些断层,这个不是很合理。
根据 PI 计算一下,roadCount 让它是 3.14 的倍数就好了,那就 3.14 乘上 600,变成 1884 ,看下效果。
const int roadCount = 1884;
十二、背景展示
准备一张云的图片,加载一个纹理,并且生成一个精灵。宽度是窗口宽度,高度是窗口高度的一半。
Texture bg;bg.loadFromFile("cloud.png");
Sprite s(bg, IntRect(0, 0, WinWidth, WinHeight/2));
然后在每次绘制轨道的时候,把它绘制出来。
if (now.Y < minY) {minY = now.Y;}
else {
continue;}
win.draw(s);
十三、引入速度
引入一个赛车前进的速度,speed 初始化为 0。
int cameraZ = 0;
int cameraX = 0;
int speed = 100;
当键盘按下 UP 和 DOWN 的时候,不再修改 cameraZ 的值,而是修改速度的值,然后最终,cameraZ 把这个速度累加上就好了。
这样一来,往前往后,修改的就是速度,不再是位移。
if (Keyboard::isKeyPressed(Keyboard::Up)) {
if (speed > 1000) speed = 1000;
}
if (Keyboard::isKeyPressed(Keyboard::Down)) {
speed -= 2;
if (speed < 100) speed = 100;
}
cameraZ += speed;
十四、物品绘制
接下来在这条路上,间隔的绘制物品,每个物品在图片的宽高是 450,这个作为常量,根据你图片的大小,进行修改即可。
const int itemSize = 450;
Road 的结构体中,定义一个精灵 spr。
struct Road {
float x, y, z;
float scale, curve;Sprite spr;
实现一个 drawItem 的函数,设置好纹理的矩形,缩放比例,以及位置,位置的计算方式比较简单,X 坐标和这条路保持一致,Y 坐标减去 W。然后给它绘制出来。
s.setScale(W / itemSize, W / itemSize);s.setPosition(X, Y - W);win.draw(s);
}
然后定义一个物品的纹理对象,加载对应的图片,生成一个精灵对象。
Texture item;item.loadFromFile("item.png");
Sprite sitem(item);
把精灵对象通过传参,传到 Road 对象中。
Road r(0, sin(i/30.0)*1600, (i + 1) * roadSegLength, curve, sitem);
然后一个 for 循环,注意从后往前绘制,否则遮挡关系会不对。为了不产生密集恐惧症,我们可以 % 上一个 20,每 20 个轨道,出现一个物品。
for (int i = roadIndex + 300; i > roadIndex; --i) {
if (i % 20 == 0)roads[i % roadCount].drawItem(win);}
Road r(0, 0, (i + 1) * roadSegLength, curve, sitem);
十五、课数字路障
我们把路上的这些物品,显示成一些不同的数字,有数字,有符号。
首先定义这么一个字符串,和这张图片中的每个图片一一对应。
const char charItem[] = "1234567890+*/-%";
然后在 Road 中定义两个变量,operatorIndex 代表符号在 charItem 中的下标,numberIndex 代表数字 charItem 中的下标。
int operatorIndex;
int numberIndex;
接下来就可以开始初始化了,为了数字不会太密集,采用随机数的方式,如果是 200 的倍数,才出现物品。同样是通过随机的方式,尽量不出现 0,因为 除 0 和 模 0 都没意义。
operatorIndex 等于 -1 代表这个 Road 上没有东西。
Road(int _x, int _y, int _z, float _c, Sprite _spr): x(_x), y(_y), z(_z), curve(_c), spr(_spr){
if (rand() % 200 == 0) {operatorIndex = (rand() % 5) + 10;numberIndex = rand() % 10;
if (numberIndex == 9) {numberIndex = 0;}}
else {operatorIndex = -1;}}
然后我们重载原先的 drawItem,并且加入一个新的参数 ,index 代表的是 charItem 中的下标,xPlacement 则表示 x 方向的偏移。然后 left 和 top 则代表的是在这张图上的(对应数字或者符号)的左上角坐标。
void drawItem(RenderWindow& win, int index, int xPlacement) {Sprite s = spr;
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);win.draw(s);}
void drawItem(RenderWindow& win) {
if (operatorIndex == -1) {
return;}
drawItem(win, operatorIndex, -1);
drawItem(win, numberIndex, 0);}
这里的取模就可以去掉了。
for (int i = roadIndex + 300; i > roadIndex; --i) {roads[i % roadCount].drawItem(win);}
十六、分数展示
我们在窗口的左上角,展示一个分数,并且还是这张图片。先定义一个分数 score,尽量考虑到所有情况,所以初始化 - 的 1234567890。
然后实现一个 DrawNumber 函数,sItem 代表数字对应那个精灵,x 和 y 代表显示在屏幕的左上角坐标。然后把 number 转换成字符串,存储到 ch 中。
遍历这个字符串,并且去 charItem 中找到对应的下标,利用相同的绘制方式,把它绘制在屏幕上。
void DrawNumber(RenderWindow& win, Sprite sItem, int number, int x, int y) {
char ch[100] = {'\0'};_itoa_s(number, ch, 10);
int len = strlen(ch);
for (int i = 0; i < len; ++i) {Sprite s = sItem;
int index = -1;
for (int j = 0; charItem[j]; ++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.18, 0.18);s.setPosition(x + 0.13 * itemSize * i, y);win.draw(s);}
}
然后调用即可。
s.setTextureRect(IntRect(0, 0, WinWidth, minY));win.draw(s);
DrawNumber(win, sitem, score, 10, 10);
十七、重新生成
如果每次路过都把数字清空,那么迟早有一天,路上就没有数字了,于是实现一个重新生成的接口,这里做一点小改动,这个 Road 一开始,是通过随机计算的。
当然,如果 generateItem 直接传一个 true,那么必定产生数字。
Road(int _x, int _y, int _z, float _c, Sprite _spr): x(_x), y(_y), z(_z), curve(_c), spr(_spr){
generateItem(false);}
void generateItem(bool bAlwaysGen) {
if (bAlwaysGen || rand() % 200 == 0) {operatorIndex = (rand() % 5) + 10;numberIndex = rand() % 10;
if (numberIndex == 9) {numberIndex = 0;}}
else {operatorIndex = -1;}}
所以当前这条路如果正好出屏幕外,那么就把 i + 1500 的轨道,生成一个新的数字。
now.operatorIndex = -1;
roads[(i + 1500) % roadCount].generateItem(
十八、跳跃功能
为了增加趣味性,可以引入跳跃功能,从而可以跳过这个数字。加入一个状态叫 isJumping ,一开始是 false,代表没有跳跃。
跳跃会改变 z 的坐标,所以 z 代表实际偏移的 z ,dz 代表跳跃时的速度。
bool isJumping = false;
float z = 0, dz = 0;
如果按下空格,当非跳跃状态,则切换到跳跃状态,并且把起跳速度设置为 150。
if (Keyboard::isKeyPressed(Keyboard::Space)) {
if (isJumping == false) {isJumping = true;dz = 150;}}
然后如果一直在跳跃中,则减去 5,这里就是在模拟自由落体,这里的 5 就是加速度,然后 z 累加 dz 的过程,就是模拟的 位移 和 速度,当 z 小于等于 0,说明落地了,isJumping 置为 false。
if (isJumping) {dz -= 5;z += dz;
if (z <= 0) {z = 0;isJumping = false;}}
最后,当遇到数字的时候,如果非跳跃状态,才会生效,否则没有任何效果。
if (now.Y >= WinHeight) {
if (!isJumping && now.operatorIndex != -1) {score = caculateScore(score, now.operatorIndex, now.numberIndex);now.operatorIndex = -1;roads[(i + 1500) % roadCount].generateItem(true);}}
十九、课音效播放
接下来,我们加入一些音效,让游戏更加的带感。引入一个头文件。
初始化三个音效缓存,分别代表 碰到数字、起跳、落地 音效。
buffer[1].loadFromFile("jump.mp3");
buffer[2].loadFromFile("falldown.mp3");
起跳可以这么写。
if (isJumping == false) {sound.setBuffer(buffer[1]);sound.play();isJumping = true;dz = 150;}
落地可以这么写。
if (z <= 0) {z = 0;isJumping = false;sound.setBuffer(buffer[2]);sound.play();}
碰到数字可以这么写。
if (!isJumping && now.operatorIndex != -1) {score = caculateScore(score, now.operatorIndex, now.numberIndex);now.operatorIndex = -1;roads[(i + 1500) % roadCount].generateItem(true);sound.setBuffer(buffer[0]);sound.play();}
二十、音乐播放
再加上一个 bgm。
SoundBuffer buffer[5];Sound sound, bgm;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");
在窗口 while 之前,调用 play 接口。
bgm.setBuffer(buffer[3]);
bgm.setLoop(true);
bgm.play();
二十一、音乐切换
音乐可以随着赛车的速度,进行切换,当开的快的时候,播放 4 号 音乐;否则,播放 5 号音乐。
if (Keyboard::isKeyPressed(Keyboard::Up)) {speed += 2;
if (speed > 1000) speed = 1000;
if (speed == 500) {speed = 502;bgm.setBuffer(buffer[4]);bgm.play();}}
if (Keyboard::isKeyPressed(Keyboard::Down)) {speed -= 2;
if (speed < 100) speed = 100;
if (speed == 500) {speed = 498;bgm.setBuffer(buffer[3]);bgm.play();}}
相关文章:

基础项目实战——3D赛车(c++)
目录 前言一、渲染引擎二、关闭事件三、梯形绘制四、轨道绘制五、边缘绘制六、草坪绘制七、前后移动八、左右移动九、曲线轨道十、课山坡轨道十一、循环轨道十二、背景展示十三、引入速度十四、物品绘制十五、课数字路障十六、分数展示十七、重新生成十八、…...

ODP(OBProxy)路由初探
OBProxy路由策略 Primary Zone 路由 官方声明默认情况,会将租户请求发送到租户的 primary zone 所在的机器上,通过 Primary Zone 路由可以尽量发往主副本,方便快速寻找 Leader 副本。另外,设置primary zone 也会在一定成都上减少…...

从零推导线性回归:最小二乘法与梯度下降的数学原理
欢迎来到我的主页:【Echo-Nie】 本篇文章收录于专栏【机器学习】 本文所有内容相关代码都可在以下仓库中找到: Github-MachineLearning 1 线性回归 1.1 什么是线性回归 线性回归是一种用来预测和分析数据之间关系的工具。它的核心思想是找到一条直…...
计算机网络__基础知识问答
Question: 1)在计算机网络的5层结构中,每一层的功能大概是什么? 2)交换机的功能?https://www.bilibili.com/video/BV1na4y1L7Ev 3)路由器的功能?https://www.bilibili.com/video/BV1hv411k7n…...
第 5 章:声音与音乐系统
5.1 声音效果的应用 在游戏中,声音效果是增强游戏沉浸感和趣味性的重要元素。Pygame 提供了强大的音频处理功能,使得添加各种声音效果变得相对简单。声音效果可以包括角色的动作音效,如跳跃、攻击、受伤时的声音;环境音效&#x…...

C语言编译过程全面解析
今天是2025年1月26日,农历腊月二十七,一个距离新春佳节仅一步之遥的日子。城市的喧嚣中,年味已悄然弥漫——能在这个时候坚持上班的人,真可称为“牛人”了吧,哈哈。。。。 此刻,我在重新审视那些曾被遗忘的…...

算法每日双题精讲 —— 前缀和(【模板】一维前缀和,【模板】二维前缀和)
在算法竞赛与日常编程中,前缀和是一种极为实用的预处理技巧,能显著提升处理区间和问题的效率。今天,我们就来深入剖析一维前缀和与二维前缀和这两个经典模板。 一、【模板】一维前缀和 题目描述 给定一个长度为 n n n 的整数数组 a a a&…...

Maui学习笔记- SQLite简单使用案例02添加详情页
我们继续上一个案例,实现一个可以修改当前用户信息功能。 当用户点击某个信息时,跳转到信息详情页,然后可以点击编辑按钮导航到编辑页面。 创建项目 我们首先在ViewModels目录下创建UserDetailViewModel。 实现从详情信息页面导航到编辑页面…...

VMware 中Ubuntu无网络连接/无网络标识解决方法【已解决】
参考文档 Ubuntu无网络连接/无网络标识解决方法_ubuntu没网-CSDN博客 再我们正常使用VMware时,就以Ubuntu举例可能有时候出现无网络连接,甚至出现无网络标识的情况,那么废话不多说直接上教程 环境:无网络 解决方案&#…...
完美世界前端面试题及参考答案
如何设置事件捕获和事件冒泡? 在 JavaScript 中,可以通过addEventListener方法来设置事件捕获和事件冒泡。该方法接收三个参数,第一个参数是事件类型,如click、mousedown等;第二个参数是事件处理函数;第三个参数是一个布尔值,用于指定是否使用事件捕获机制。当这个布尔值…...

新时代架构SpringBoot+Vue的理解(含axios/ajax)
文章目录 引言SpringBootThymeleafVueSpringBootSpringBootVue(前端)axios/ajaxVue作用响应式动态绑定单页面应用SPA前端路由 前端路由URL和后端API URL的区别前端路由的数据从哪里来的 Vue和只用三件套axios区别 引言 我是一个喜欢知其然又知其所以然的…...

代理模式 -- 学习笔记
代理模式学习笔记 什么是代理? 代理是一种设计模式,用户可以通过代理操作,而真正去进行处理的是我们的目标对象,代理可以在方法增强(如:记录日志,添加事务,监控等) 拿一…...
gif动画图像优化,相同的图在第2,4,6帧中重复出现,会增加图像体积吗?
对于 GIF 图像,情况与 Git 文件存储有所不同。GIF 是一种图像格式,其体积主要取决于图像的内容、颜色数量、优化设置等因素。如果在 GIF 动画中,相同的图像在第 2、4、6 帧中重复出现,是否会增加图像体积,取决于以下几…...

Harmony Next 跨平台开发入门
ArkUI-X 官方介绍 官方文档:https://gitee.com/arkui-x/docs/tree/master/zh-cn ArkUI跨平台框架(ArkUI-X)进一步将ArkUI开发框架扩展到了多个OS平台:目前支持OpenHarmony、Android、 iOS,后续会逐步增加更多平台支持。开发者基于一套主代码…...

阿里巴巴Qwen团队发布AI模型,可操控PC和手机
每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗?订阅我们的简报,深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同,从行业内部的深度分析和实用指南中受益。不要错过这个机会,成为AI领…...
android 音视频系列引导
音视频这块的知识点自己工作中有用到,一直没有好好做一个总结,原因有客观和主观的。 客观是工作太忙,没有成段时间做总结。 主观自己懒。 趁着这次主动离职拿了n1的钱,休息一下,对自己的人生做一下总结,…...

STM32调试手段:重定向printf串口
引言 C语言中经常使用printf来输出调试信息,打印到屏幕。由于在单片机中没有屏幕,但是我们可以重定向printf,把数据打印到串口,从而在电脑端接收调试信息。这是除了debug外,另外一个非常有效的调试手段。 一、什么是pr…...
基于 Jenkins 的测试报告获取与处理并写入 Jira Wiki 的技术总结
title: 基于 Jenkins 的测试报告获取与处理并写入 Jira Wiki 的技术总结 tags: - jenkins - python categories: - jenkins在软件开发的持续集成与持续交付(CI/CD)流程里,及时、准确地获取并分析测试报告对保障软件质量至关重要。本文将详细…...
Vue.js组件开发-实现导出PDF文件可自定义添加水印及水印样式方向
使用 Vue 实现导出 PDF 文件并添加水印,同时支持设置水印样式、方向和自定义水印内容。 步骤 安装依赖:使用 html2canvas 将 HTML 内容转换为 canvas,使用 jspdf 生成 PDF 文件。创建 Vue 组件:在组件中实现水印生成、HTML 转 c…...
css中的animation
css的animation animation是一个综合属性,是animation-name, animation-duration, animation-timing-function, animation-delay, animation-iteration-count, animation-direction, animation-fill-mode, animation-play-state, and animation-timeline这些属性的简写 不过在…...

DAY 47
三、通道注意力 3.1 通道注意力的定义 # 新增:通道注意力模块(SE模块) class ChannelAttention(nn.Module):"""通道注意力模块(Squeeze-and-Excitation)"""def __init__(self, in_channels, reduction_rat…...
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数
高效线程安全的单例模式:Python 中的懒加载与自定义初始化参数 在软件开发中,单例模式(Singleton Pattern)是一种常见的设计模式,确保一个类仅有一个实例,并提供一个全局访问点。在多线程环境下,实现单例模式时需要注意线程安全问题,以防止多个线程同时创建实例,导致…...
如何配置一个sql server使得其它用户可以通过excel odbc获取数据
要让其他用户通过 Excel 使用 ODBC 连接到 SQL Server 获取数据,你需要完成以下配置步骤: ✅ 一、在 SQL Server 端配置(服务器设置) 1. 启用 TCP/IP 协议 打开 “SQL Server 配置管理器”。导航到:SQL Server 网络配…...
[USACO23FEB] Bakery S
题目描述 Bessie 开了一家面包店! 在她的面包店里,Bessie 有一个烤箱,可以在 t C t_C tC 的时间内生产一块饼干或在 t M t_M tM 单位时间内生产一块松糕。 ( 1 ≤ t C , t M ≤ 10 9 ) (1 \le t_C,t_M \le 10^9) (1≤tC,tM≤109)。由于空间…...
TJCTF 2025
还以为是天津的。这个比较容易,虽然绕了点弯,可还是把CP AK了,不过我会的别人也会,还是没啥名次。记录一下吧。 Crypto bacon-bits with open(flag.txt) as f: flag f.read().strip() with open(text.txt) as t: text t.read…...

海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...

算法—栈系列
一:删除字符串中的所有相邻重复项 class Solution { public:string removeDuplicates(string s) {stack<char> st;for(int i 0; i < s.size(); i){char target s[i];if(!st.empty() && target st.top())st.pop();elsest.push(s[i]);}string ret…...
电脑桌面太单调,用Python写一个桌面小宠物应用。
下面是一个使用Python创建的简单桌面小宠物应用。这个小宠物会在桌面上游荡,可以响应鼠标点击,并且有简单的动画效果。 import tkinter as tk import random import time from PIL import Image, ImageTk import os import sysclass DesktopPet:def __i…...
React父子组件通信:Props怎么用?如何从父组件向子组件传递数据?
系列回顾: 在上一篇《React核心概念:State是什么?》中,我们学习了如何使用useState让一个组件拥有自己的内部数据(State),并通过一个计数器案例,实现了组件的自我更新。这很棒&#…...

Linux操作系统共享Windows操作系统的文件
目录 一、共享文件 二、挂载 一、共享文件 点击虚拟机选项-设置 点击选项,设置文件夹共享为总是启用,点击添加,可添加需要共享的文件夹 查询是否共享成功 ls /mnt/hgfs 如果显示Download(这是我共享的文件夹)&…...