基础项目实战——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++)
目录 前言一、渲染引擎二、关闭事件三、梯形绘制四、轨道绘制五、边缘绘制六、草坪绘制七、前后移动八、左右移动九、曲线轨道十、课山坡轨道十一、循环轨道十二、背景展示十三、引入速度十四、物品绘制十五、课数字路障十六、分数展示十七、重新生成十八、…...
AboutDialog组件的功能和用法
文章目录 1 概念介绍2 使用方法3 示例代码 我们在上一章回中介绍了AlertDialog Widget相关的内容,本章回中将介绍AboutDialog Widget.闲话休提,让我们一起Talk Flutter吧。 1 概念介绍 我们在这里说的AboutDialog是一种弹出式窗口,和上一章回中介绍的Al…...
hdfs之读写流程
写入流程: 客户端Client想将文件a.txt上传至hdfs,首先向Namenode发送请求进行权限校验,Namenode通过后会计算出来三个节点,并将这三个节点告知客户端,客户端将输入进行切割成块,一个一个的块进行传输&…...
链表的介绍
目录 引言优缺点与链表相似的数据结构注意事项单向链表的实现基础实现创建类创建成员变量创建特殊方法 增加数据push_back方法insert方法 删除数据del_back方法del_index方法 clear方法查询数据at方法与重载的中括号运算符toArray方法indexOf方法 修改数据获取链表大小测试方法…...
背着开发板回家过年~
大家好,我是bug菌~ 小明,过年了, 别再死磕什么STM32底层驱动、linux平台总线驱动框架、嵌入式实时操作系统源码这些了。 你背着电脑和开发板回家,压根没一点实际用处。 发小们潇洒地出入各种娱乐场所,唱着歌࿰…...
Kafka 日志存储 — 磁盘存储
Kafka 依赖与磁盘来存储和缓存消息,采用文件追加的方式来写入消息。顺序写盘的速度快于随机写内存。 1 磁盘存储 除顺序写入外,Kafka中大量使用了页缓存、零拷贝等技术来进一步提升吞吐性能。 1.1 页缓存 页缓存是操作系统实现的一种磁盘缓存&#x…...
【机器学习】自定义数据集 使用tensorflow框架实现逻辑回归并保存模型,然后保存模型后再加载模型进行预测
一、使用tensorflow框架实现逻辑回归 1. 数据部分: 首先自定义了一个简单的数据集,特征 X 是 100 个随机样本,每个样本一个特征,目标值 y 基于线性关系并添加了噪声。tensorflow框架不需要numpy 数组转换为相应的张量࿰…...
RK3568中使用QT opencv(显示基础图像)
文章目录 一、查看对应的开发环境是否有opencv的库二、QT使用opencv 一、查看对应的开发环境是否有opencv的库 在开发板中的/usr/lib目录下查看是否有opencv的库: 这里使用的是正点原子的ubuntu虚拟机,在他的虚拟机里面已经安装好了opencv的库。 二、…...
Brave132 编译指南 Windows 篇:获取源码(六)
1. 引言 在 Brave 浏览器 132 版本的编译过程中,获取源代码是至关重要的第一步。源代码包含了 Brave 浏览器的所有核心功能、特性和组件的实现细节,是深入理解、定制和优化 Brave 的基础。通过获取和管理源代码,开发者能够深入探索 Brave 的…...
解决 pip install 出现 error: subprocess-exited-with-error 错误的方法
解决 pip install 出现 error: subprocess-exited-with-error 错误的方法_pip安装报错 subprocess-CSDN博客文章浏览阅读10w次,点赞62次,收藏86次。通过上述步骤,我们成功解决了 pip install 时出现的 error: subprocess-exited-with-error 错…...
网络安全攻防实战:从基础防护到高级对抗
📝个人主页🌹:一ge科研小菜鸡-CSDN博客 🌹🌹期待您的关注 🌹🌹 引言 在信息化时代,网络安全已经成为企业、政府和个人必须重视的问题。从数据泄露到勒索软件攻击,每一次…...
DeepSeek大模型技术解析:从架构到应用的全面探索
一、引言 在人工智能领域,大模型的发展日新月异,其中DeepSeek大模型凭借其卓越的性能和广泛的应用场景,迅速成为业界的焦点。本文旨在深入剖析DeepSeek大模型的技术细节,从架构到应用进行全面探索,以期为读者提供一个…...
Deepseek的api调用报错乱码问题
最近的deepseek也是很火,但是在调用api的过程中也会出现一些大大小小的问题,所以这里也给出一种问题和他的解决方案,报错的类型如下图所示 API Streaming Failed Command failed with exit code 1: powershell (Get-CimInstance -ClassName W…...
.NET Core 中依赖注入的使用
ASP.NET Core中服务注入的地方 在ASP.NET Core项目中一般不需要自己创建ServiceCollection、IServiceProvider。在Program.cs的builder.Build()之前向builder.Services中注入。在Controller中可以通过构造方法注入服务。 低使用频率的服务 把Action用到的服务通过Action的参…...
Mysql Resultset 解析记录
Mysql Resultset 解析记录 结果集消息头字段定义结果数据完整spicy文件 结果集消息头 消息头由消息体长度消息序列号消息体组成;消息头长度为3字节,消息序列号长度为1字节。 结果集的消息头消息体内容为结果集的列数。 结果集消息头的spicy1格式如下&a…...
ThinkPhp伪静态设置后,访问静态资源也提示找不到Controller
ThinkPhp没有配置伪静态时,除了默认的IndexController能访问,其他路由Controller都访问不到,提示404错误。配置了伪静态后就解决了这个问题。 但是当我的ThinkPhp后台项目中有静态资源放在public目录(或子目录)中需要…...
【回溯+剪枝】找出所有子集的异或总和再求和 全排列Ⅱ
文章目录 1863. 找出所有子集的异或总和再求和解题思路:子集问题解法(回溯 剪枝)47. 全排列 II解题思路:排序 回溯 剪枝 1863. 找出所有子集的异或总和再求和 1863. 找出所有子集的异或总和再求和 一个数组的 异或总和 定义为…...
单细胞-第五节 多样本数据分析,打分R包AUCell
文件在单细胞\5_GC_py\1_single_cell\3.AUCell.Rmd 1.基因 rm(list = ls()) load("g.Rdata")2.AUCell https://www.ncbi.nlm.nih.gov/pmc/articles/PMC9897923 IF: NA NA NA用这个文章里的方法,将单细胞亚群的marker基因与ros相关基因取交集,用作AUCell的基因集…...
锁升级过程与优化操作
前文我们学习了CAS自旋锁知道CAS对应的就是一条指令操作,属于一种轻量级锁,那么有轻必有重,从无锁到轻量级锁到重量级锁是一个升级过程,此文我们对锁升级的过程以及一些优化锁的操作一探究竟。 1. 锁升级 从前文 《程序员不可能不…...
android主题设置为..DarkActionBar.Bridge时自定义DatePicker选中日期颜色
安卓自定义DatePicker选中日期颜色 背景:解决方案:方案一:方案二:实践效果: 背景: 最近在尝试用原生安卓实现仿element-ui表单校验功能,其中的的选择日期涉及到安卓DatePicker组件的使用&#…...
Kafka常见问题之 `javax.management.InstanceAlreadyExistsException`
文章目录 Kafka常见问题之 javax.management.InstanceAlreadyExistsException1. 概述2. 常见原因3. 具体异常示例4. 解决方案4.1 确保单一 Kafka Producer 实例4.2 配置 Kafka Broker 和 Producer 使用唯一的 JMX 名称(对于Producer重点检查 client.id)4…...
数据分析系列--③RapidMiner算子说明及数据预处理
一、算子说明 1.新建过程 2.算子状态灯 状态灯说明: (1)状态指示灯: 红色:指示灯说明有参数未被设置或输入端口未被连接等问题; 黄色:指示灯说明还未执行算子,不管配置是否基本齐全; 绿色:指示灯说明一切正常,已成功执行算子。 (2)三角…...
Gradle配置指南:深入解析settings.gradle.kts(Kotlin DSL版)
文章目录 Gradle配置指南:深入解析settings.gradle.kts(Kotlin DSL版)settings.gradle.kts 基础配置选项单项目配置多项目配置 高级配置选项插件管理(Plugin Management)基础配置模板案例:Android项目标准配…...
专为课堂打造:宏碁推出三款全新耐用型 Chromebook
IT之家 1 月 25 日消息,宏碁(Acer)昨日(1 月 24 日)发布公告,针对教育市场,推出 Chromebook Spin 512 (R857T)、Chromebook Spin 511 (R757T) 和 Chromebook 511 (C737) 三款产品,兼…...
电商系统-用户认证(三)基于公钥解析JWT令牌
一、 基于私钥生成jwt令牌 步骤: 导入认证服务 将shangcheng_user_auth工程导入到项目中去,如下图 启动eureka,再启动认证服务 3) 认证服务中创建测试类 public class CreateJwtTest { /**** 创建令牌测试*/Testpublic voi…...
验证回文串
hello 大家好!今天开写一个新章节,每一天一道算法题。让我们一起来学习算法思维吧! function isPalindrome(s) {// 第一步:将字符串中的所有大写字符转换为小写字符s s.toLowerCase();// 第二步:使用正则表达式移除所…...
Java定时任务实现方案(四)——Spring Task
Spring Task 这篇笔记,我们要来介绍实现Java定时任务的第四个方案,使用Spring Task,以及该方案的优点和缺点。 Spring Task是Spring框架提供的一个轻量级任务调度框架,用于简化任务调度的开放,通过注解或XML配置的…...
Python 数据分析 - Matplotlib 绘图
Python 数据分析 - Matplotlib 绘图 简介绘图折线图单线多线子图 散点图直方图条形图纵置横置多条 饼图 简介 Matplotlib 是 Python 提供的一个绘图库,通过该库我们可以很容易的绘制出折线图、直方图、散点图、饼图等丰富的统计图,安装使用 pip install…...
深入探讨数据库索引类型:B-tree、Hash、GIN与GiST的对比与应用
title: 深入探讨数据库索引类型:B-tree、Hash、GIN与GiST的对比与应用 date: 2025/1/26 updated: 2025/1/26 author: cmdragon excerpt: 在现代数据库管理系统中,索引技术是提高查询性能的重要手段。当数据量不断增长时,如何快速、有效地访问这些数据成为了数据库设计的核…...
【Redis】hash 类型的介绍和常用命令
1. 介绍 Redis 中存储的 key-value 本身就是哈希表的结构,存储的 value 也可以是一个哈希表的结构 这里每一个 key 对应的一个 哈希类型用 field-value 来表示 2. 常用命令 命令 介绍 时间复杂度 hset key field value 用于设置哈希表 key 中字段 field 的值为…...
World Creator地形导入UE
修改导出分辨率1009x1009, 虚幻默认参数的整体分辨率是1009 导出预设选择高度图(heigh map)格式选择PNG 16位,或者RAW 16位,需要反转y轴(与虚幻不同),命名格式会自动带一个 , 将改成_ 或者删掉自己命名 &am…...
mybatis(104/134)
动态sql标签,用于选择查询 if标签 where标签 :自动生成where,取决于后面有没有条件,会自动去除条件前面的and和or,不会去除语句后面的 trim标签:自动生成where,在语句后自动去除后缀and和or for…...
制造企业的成本核算
一、生产成本与制造费用的区别 (1)生产成本,是直接用于产品生产,构成产品实体的材料成本。 包括企业在生产经营过程中实际消耗的原材料、辅助材料、备品备件、外购半成品、燃料、动力包装物以及其它直接材料,和直接参加产品生产的工人工资,以及按生产工人的工资总额和规…...
Windows中本地组策略编辑器gpedit.msc打不开/微软远程桌面无法复制粘贴
目录 背景 解决gpedit.msc打不开 解决复制粘贴 剪贴板的问题 启用远程桌面剪贴板与驱动器 重启RDP剪贴板监视程序 以上都不行?可能是操作被Win11系统阻止 最后 背景 远程桌面无法复制粘贴,需要查看下主机策略组设置,结果按WinR输入…...
详解排序算法
文章目录 1. 排序算法分类2. 比较排序算法介绍2.1 插入排序2.1.1 直接插入排序2.1.2 希尔排序 2.2 选择排序2.2.1 直接选择排序2.2.2 堆排序2.2.2.1 向下调整算法建堆2.2.2.2 向上调整算法建堆2.2.2.3 进行堆排序2.2.2.4 堆排序时间、空间复杂度分析2.2.2.5 利用堆排序解决TOP-…...
练习题 - Django 4.x File 文件上传使用示例和配置方法
在现代的 web 应用开发中,文件上传是一个常见的功能,无论是用户上传头像、上传文档,还是其他类型的文件,处理文件上传都是开发者必须掌握的技能之一。Django 作为一个流行的 Python web 框架,提供了便捷的文件上传功能和配置方法。学习如何在 Django 中实现文件上传,不仅…...
Vue 响应式渲染 - 待办事项简单实现
Vue 渐进式JavaScript 框架 基于Vue2的学习笔记 - Vue 响应式渲染 - 待办事项简单实现 目录 待办事项简单实现 页面初始化 双向绑定的指令 增加留言列表设置 增加删除按钮 最后优化 总结 待办事项简单实现 页面初始化 对页面进行vue的引入、创建输入框和按钮及实例化V…...
【福州市AOI小区面】shp数据学校大厦商场等占地范围面数据内容测评
AOI城区小区面样图和数据范围查看: — 字段里面有name字段。分类比较多tpye:每个值代表一个类型。比如字段type中1549代表小区住宅,1563代表学校。小区、学校等占地面积范围数据 —— 小区范围占地面积面数据shp格式 无偏移坐标,只…...
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2
llama.cpp LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK2 1. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK22. LLM_ARCH_DEEPSEEK and LLM_ARCH_DEEPSEEK23. struct ggml_cgraph * build_deepseek() and struct ggml_cgraph * build_deepseek2()References 不宜吹捧中国大语言模型的同…...
k8s简介,k8s环境搭建
目录 K8s简介环境搭建和准备工作修改主机名(所有节点)配置静态IP(所有节点)关闭防火墙和seLinux,清除iptables规则(所有节点)关闭交换分区(所有节点)修改/etc/hosts文件&…...
2024年个人总结
序 照例,每年都有的个人年度总结来了,看了很多其他大佬的总结,感觉自己的2024过于单薄,故事也不太丰满,自己就回去比较,自己哪里做的不好 ?但后来发现已经进入了一个思维误区。 年度总结年度总结…...
【落羽的落羽 数据结构篇】顺序表
文章目录 一、线性表二、顺序表1. 概念与分类2. 准备工作3. 静态顺序表4. 动态顺序表4.1 定义顺序表结构4.2 顺序表的初始化4.3 检查空间是否足够4.3 尾部插入数据4.4 头部插入数据4.5 尾部删除数据4.6 头部删除数据4.7 在指定位置插入数据4.8 在指定位置删除数据4.9 顺序表的销…...
麒麟操作系统服务架构保姆级教程(十四)iptables防火墙四表五链和防火墙应用案例
如果你想拥有你从未拥有过的东西,那么你必须去做你从未做过的事情 防火墙在运维工作中有着不可或缺的重要性。首先,它是保障网络安全的关键防线,通过设置访问控制规则,可精准过滤非法网络流量,有效阻挡外部黑客攻击、恶…...
Linux之详谈——权限管理
目录 小 峰 编 程 编辑 一、权限概述 1、什么是权限 2、为什么要设置权限 3、Linux中的权限类别- 4、Linux中文件所有者 1)所有者分类(谁) 2)所有者的表示方法 ① u(the user who owns it)(属主权限&…...
第05章 13 椭球体张量可视化应用一则-神经束追踪
在神经束追踪(Tractography)中,椭球体张量(Ellipsoid Tensor)通常用于描述神经纤维的方向和扩散特性。这种技术广泛应用于磁共振成像(MRI)的扩散张量成像(DTI)数据中。VT…...
Celery
https://www.bilibili.com/video/BV1RGDEY5ERB 架构 简单任务 执行 包结构 本示例: app 添加任务 获取结果 配置延时任务 任务配置 beat 提交定时任务...
JavaScript系列(48)-- 3D渲染引擎实现详解
JavaScript 3D渲染引擎实现详解 🎮 今天,让我们深入探讨JavaScript的3D渲染引擎实现。通过WebGL和现代JavaScript技术,我们可以构建一个功能完整的3D渲染系统。 3D渲染基础概念 🌟 💡 小知识:3D渲染引擎的…...
Golang并发机制及CSP并发模型
Golang 并发机制及 CSP 并发模型 Golang 是一门为并发而生的语言,其并发机制基于 CSP(Communicating Sequential Processes,通信顺序过程) 模型。CSP 是一种描述并发系统中交互模式的正式语言,强调通过通信来共享内存…...
使用 Docker + Nginx + Certbot 实现自动化管理 SSL 证书
使用 Docker Nginx Certbot 实现自动化管理 SSL 证书 在互联网安全环境日益重要的今天,为站点或应用部署 HTTPS 已经成为一种常态。然而,手动申请并续期证书既繁琐又容易出错。本文将以 Nginx Certbot 为示例,基于 Docker 容器来搭建一个…...
游戏策划的分类
P3游戏策划分类 1.程序2.美术3.策划 程序:一般分为客户端程序和服务器程序 客户端程序一般负责游戏的前端画面表现 服务器程序负责游戏的后端运算 美术:角色原画,角色模型动作,场景原画,场景模型,UI设计&a…...