【C++游戏开发-01】推箱子
C++游戏开发
文章目录
- C++游戏开发
- @[TOC](文章目录)
- 前言
- 一、逻辑分析
- 1.1地图实现
- 1.2人物的移动
- 1.2.1小人移动
- 1.2.2其他移动
- 1.3墙壁的碰撞
- 1.4箱子的推动
- 1.4.1什么时候推箱子
- 1.4.2什么情况可以推箱子
- 1.5胜利的判断
- 1.6卡关的处理
- 1.7关卡的切换
- 二、DEMO代码
- 2.1游戏框架
- 2.2各功能函数的实现
- Init()
- Paint()
- Run()
- Close()
- 2.3额外添加的函数
- Move(char key)
- Check()
- main()
- 三、完整源代码
- 四、总结
文章目录
- C++游戏开发
- @[TOC](文章目录)
- 前言
- 一、逻辑分析
- 1.1地图实现
- 1.2人物的移动
- 1.2.1小人移动
- 1.2.2其他移动
- 1.3墙壁的碰撞
- 1.4箱子的推动
- 1.4.1什么时候推箱子
- 1.4.2什么情况可以推箱子
- 1.5胜利的判断
- 1.6卡关的处理
- 1.7关卡的切换
- 二、DEMO代码
- 2.1游戏框架
- 2.2各功能函数的实现
- Init()
- Paint()
- Run()
- Close()
- 2.3额外添加的函数
- Move(char key)
- Check()
- main()
- 三、完整源代码
- 四、总结
前言
推箱子为本系列的第一篇,我个人认为这是游戏开发中最基础最简单的部分,所以放在开篇,程序有些入门适合初学者阅读。对于程序方面如果大家有更好的方案欢迎在评论区留言。那么首先为大家介绍一下推箱子的游戏规则:
如上图所示,为某一关推箱子的地图画面;
其中黄色的圆点为目标点,带有叉的方块是箱子,玩家通过方向键控制小人移动,在移动过程中如果遇到箱子可以推动箱子,但不能拉。如下情况则不可以推动箱子:
1.箱子遇到墙壁;
2.两个箱子重叠(推动两个箱子);
如果将所有的箱子都放置在了目标点处,则游戏获得胜利(过关)。
一、逻辑分析
所有的游戏开发其逻辑都基于游戏的规则,因此完整的游戏规则是必不可少的。其中最重要的规则无外乎:获胜条件、失败条件、积分规则等。
对于推箱子而言,获胜规则显而易见是将所有的箱子都放置在目标点处。而本游戏并没有失败条件,但是考虑到箱子可能会被推到墙壁处而不能通关出现卡关情况,因此对于卡关也要做出处理。
本游戏中没有积分规则,但是会有不同的关卡设计,但其底层玩法,包括人物的移动,箱子的推动,胜利的判断是没有区别的,因此我们可以在不同的关卡中只更换游戏地图来实现关卡切换。
综上,要实现的部分有:
- 地图的实现
- 人物的移动
- 墙壁的碰撞
- 箱子的推动
- 胜利的判断
- 卡关的处理
- 关卡的切换
1.1地图实现
对于推箱子地图的实现,我们可以用字符简单的绘制一下,如下:
### # # # # #### ####### ###### ###### # # # ###
在上图中使用了字符#来模拟地图的墙壁,当然只有地图是远远不够的,还要有小人,箱子,目标点,那么使用字符H来模拟小人,使用字符O来模拟箱子,字符*来模拟目标点得到如下的图:
### #*# #O# #### #######*O H O *######O###### # #*# ###
有了字符组成的地图,那么显然我们可以使用C++的数据结构二维数组来存储上述字符,从而将其输出到命令行显示:
//使用二维数组定义地图
char p_map[16][16] = {" ", " "," ### "," #*# "," #O# "," #### ###### "," #*O H O *# "," #####O##### "," # # "," #*# "," ### "," "," "," "," "," "};void showMap(){//显示地图for(int i = 0; i < 16; i ++){puts(p_map[i]);}
}
由此我们得到了简易的地图显示。
1.2人物的移动
在讲到人物移动的逻辑之前,我们先来聊一下视频动画是如何形成的。
如下图是一个摇头的向日葵
我们之所以能看到这个向日葵在摇摆,是由下面一系列图片刷新显示,当图片连续的刷新显示使人眼产生一种向日葵动起来的错觉,这也是视频播放的原理。
每显示一张图片我们称作一帧,而每秒钟刷新的次数(显示的帧数)称为帧率,帧率越高,动态效果就越真实。
而游戏肯定离不开动态,因此几乎所有的游戏都要刷新的去显示,形成所谓的动态效果。我们可以通过一个程序来实现一个简单的动画效果。
1.2.1小人移动
我们在命令行上输出一个小人:
printf("O\nI\nH\n");
如下:
OIH
若想让小人跑起来,即向右移动 ,我们可以在字符OIH前分别加入一个空格使其右移一格
OIH
若想连续的跑动则将程序放入循环中,依次在每个字符左边多加入一个空格即可,但是这样会出现以下的问题:
for(int i = 0; i < 20 ; ++i){for(int j = 0; j < i; j ++){//加入i个空格printf(" ");}printf(" O\n");for(int j = 0; j < i; j ++){printf(" ");}printf(" I\n");for(int j = 0; j < i; j ++){printf(" ");}printf(" H\n");}
小人没有像我们预期的一样跑动,而是成了一条斜线。
这是因为我们只顾着去显示忘记了刷新屏幕上已显示过的小人,因此我们需要每次将小人显示前清除上一次的显示结果,从而实现刷新显示。
使用如下程序:
system("cls");
是C++清空命令框的指令,使用时需加上stdlib.h的头文件,但此时运行会发行小人跑的太快了,这也不符合我们的预期,因此可以改变小人的刷新率(帧率)
使用如下程序:
Sleep(n);
其作用为让进程休眠n毫秒,我们这里n取1000,即帧率为1(虽然很低但是为了看清小人移动的过程),使用时需加上windows.h的头文件
for(int i = 0; i < 20 ; ++i){system("cls");for(int j = 0; j < i; j ++){//加入i个空格printf(" ");}printf(" O\n");for(int j = 0; j < i; j ++){printf(" ");}printf(" I\n");for(int j = 0; j < i; j ++){printf(" ");}printf(" H\n");Sleep(1000);}
这时就可以看到小人一步一步的移动啦
1.2.2其他移动
综上,要显示移动效果,需要如下步骤
1.画面的清除
2.新画面的显示
3.时间的控制(用于控制刷新率、移动的快慢等)
因此我们可以将以上封装为画面显示的函数paint(),在每次画面更新的逻辑处理完后调用即可。
在推箱子中小人的移动就很简单了,我们基于其在二维数组中的坐标x,y,每次使用键盘输入后更改x,y的值,并在地图中更新,然后调用paint()函数即可。
char p_map[16][16] = {" ", " "," "," "," "," H "," "," "," "," "," "," "," "," "," "," "};
在上图中H的坐标为(6,5),我们将地图的打印、刷新、刷新率封装为一个函数:
void paint(){system("cls");//清空上一次图片for(int i = 0; i < 16; ++i){//更新地图打印puts(p_map[i]);}Sleep(10);//10毫秒刷新
}
使用getch()函数来获取键盘的输入,该函数需要conio.h头文件
在循环中判断输入的键,对于不同方向做相应的处理更新小人坐标和地图:
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<conio.h>void paint(){system("cls");//清空上一次图片for(int i = 0; i < 16; ++i){//更新地图打印puts(p_map[i]);}Sleep(10);//10毫秒刷新
}int main(){//记录坐标int x = 6;int y = 5;while(1){paint();char z = getch();if(z == 'w'){//上p_map[y][x] = ' ';//之前的位置更新为空格p_map[y-1][x] = 'H';//移动后的位置变为小人,后面同理y--;}else if(z == 'a'){//左p_map[y][x] = ' ';//之前的位置更新为空格p_map[y][x-1] = 'H';x--;}else if(z == 's'){//下p_map[y][x] = ' ';//之前的位置更新为空格p_map[y+1][x] = 'H';y++;}else if(z == 'd'){//右p_map[y][x] = ' ';//之前的位置更新为空格p_map[y][x+1] = 'H';x++;}}system("pause");return 0;
}
由此即实现了小人的移动,但是由于没有边界限制条件,因此当小人走出了数组的界限会因数组越界报错,所以还需做更详细的处理。
1.3墙壁的碰撞
墙壁碰撞的逻辑就很简单了,只要我们判断移动后的位置不是表示墙壁的字符即可,如:
if(z == 'w' && p_map[y-1][x]!='#'){//不为墙壁即可向上移动p_map[y][x] = ' ';//移动前的位置更新y--;//坐标更新p_map[y][x] = 'H';//地图更新
}
1.4箱子的推动
箱子的推动相比于小人移动的逻辑要复杂一点,我们要明确两点:
1.什么时候推箱子
2.什么情况可以推箱子
1.4.1什么时候推箱子
当小人移动后的位置如果为箱子的话,此刻判断为推箱子,如下四种情况:
HO OH O HH O
1.4.2什么情况可以推箱子
当箱子移动后的位置如果是空位,则可以推箱子(不能撞墙,不能重叠推两个及以上箱子)
因此我们得到如下代码:
if(z == 'w' && p_map[y-1][x]=='O' && p_map[y-2][x]==' '){//移动的下一位为箱子,箱子移动的下一位为空位p_map[y][x] = ' ';//移动前的位置更新y--;//坐标更新p_map[y][x] = 'H';//地图更新p_map[y-1][x] = 'O';//更新箱子
}
1.5胜利的判断
当所有箱子都处于目标点时,即获得游戏胜利,因此我们需要遍历所有的目标点,这时要考虑,对于不同的关卡,目标点的个数也是不同的,因此我们考虑两种解决办法:
1.定义数组p_win[DEF_LENGTH],DEF_LENGTH要尽量大一些,保证所有关卡的目标点数不超过它。
2.使用STL中的vector
上述两种方法皆可,但如果使用方法1,在传参时除了要传入数组指针还要传入目标点个数,为了减少传参,我们直接使用vector作为容器。
1.6卡关的处理
当箱子处于下面的情况时,箱子将无法被推动到任何其他位置,也没有处于目标点处,这时既不会宣布游戏胜利,也不会宣布游戏失败,即出现了卡关。为了解决这一问题,我们可以加入重置关卡的功能:比如当我们按下R键,所有箱子和小人的位置就会复原到初始位置,这便解决了卡关的问题。
#######
#O H *#
#######
1.7关卡的切换
为了方便关卡的切换,我们将游戏的运行封装在一个函数中,而每一关我们只需传入关卡地图、判断目标点的容器、小人起始坐标三个变量即可。这样每当通关后,就自动切换到下一关。
以上就是推箱子游戏的基本逻辑,下面我们来做出推箱子的简单DEMO:
二、DEMO代码
2.1游戏框架
一般的游戏运行过程大致分为一下几步:
1.游戏初始化
2.游戏运行
3.游戏画面显示
4.游戏结束
那么我们先简单定义一个游戏类的接口,以便规范今后我们再去开发其他游戏。
定义抽象类GameFrame,并给出四个纯虚函数:
class GameFrame{
public:GameFrame(){}~GameFrame(){}virtual void Init() = 0;//游戏初始化virtual void Close() = 0;//游戏结束virtual void Paint() = 0;//游戏画面绘制virtual void Run() = 0;//游戏运行
};
接下来定义坐标结构体,方便后续使用
struct Point{int x;int y;void set(int px,int py){//给出设置坐标的set函数x = px;y = py;}
};//玩家坐标
定义推箱子类并继承GameFrame
class PushBox : public GameFrame{
public:PushBox(){}~PushBox (){}void Init();void Close();void Paint();void Run();
};
加入类成员变量:
#include<vector>using namespace std;class PushBox : public GameFrame{
public:PushBox(){}~PushBox (){}void Init();void Close();void Paint();void Run();
private:int p_id;Point p_point;vector<Point> p_check;char p_map[16][16];
};
其中p_id是用于分辨当前的关卡,以及后续关卡切换的操作;p_point是小人当前坐标;p_check用于存储所有的目标点坐标,以便遍历目标点判断是否获胜;p_map是当前关卡地图,在切换关卡和重置关卡时都要对其进行改变。
2.2各功能函数的实现
Init()
初始化函数的功能是设置小人初始坐标,将目标点容器清空并填充并设置地图。为了后续的关卡切换功能,我们在该函数中传入参数p_id,通过判断其值来初始化不同的关卡:
void Init(int id){switch (id){case 1:{p_check.clear();//容器清空char temp[16][16] = {" ", " "," ### "," #*# "," # # "," ####O###### "," #*O H O *# "," #####O##### "," # # "," #*# "," ### "," "," "," "," "," "};for(int i = 0; i < 16;i ++){//地图初始化for(int j = 0; j < 16; j ++){p_map[i][j] = temp[i][j];}}//填充四个目标点p_point.set(7,6);Point t_point;t_point.set(4,6);p_check.push_back(t_point);t_point.set(12,6);p_check.push_back(t_point);t_point.set(8,9);p_check.push_back(t_point);t_point.set(7,3);p_check.push_back(t_point);break;}case 2:{//TODO:对第二关进行初始化设置}break;default:break;}
}//游戏初始化
Paint()
该函数在1.2.2小结已经讲过,主要作用就是刷新显示画面
void Paint() {//游戏画面绘制system("cls");//清空上一次图片for(int i = 0; i < 16; ++i){//更新地图打印puts(p_map[i]);}Sleep(10);//10毫秒刷新
}
Run()
该函数是推箱子的主体运行函数,为了使程序在通关前始终运行,我们需要将进程卡在循环中,而死循环可以使用while(true)的逻辑实现,具体如下:
void Run() {while(1){Paint();//刷新显示画面if(//判断是否通过){break;}char z = getch();//读取当前输入的按键//小人的移动处理}
}//游戏运行
Close()
close()函数的作用就是资源的回收处理和游戏结束处理工作,由于本DEMO并没有使用到堆区的空间,所以不需要在这部分进行资源回收,只需要显示游戏结束即可。
void Close(){system("cls");cout<<"you win!"<<endl;
}//游戏结束
2.3额外添加的函数
接下来是我们游戏框架中没有体现的部分,属于推箱子游戏特有的功能,需要我们单独定义。
Move(char key)
该函数是负责判断键盘输入的按键来进行不同的处理,包括上下左右控制小人移动,按R建重置关卡,此外也可以考虑按Q键退出(该功能暂不实现,可以自行考虑)等等。
如下:
void Move(char key){if(key == 'w'){if(p_map[p_point.y-1][p_point.x] == ' '||p_map[p_point.y-1][p_point.x] == '*'){//小人移动的判断p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y--;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新}else if(p_map[p_point.y-1][p_point.x]=='O' && (p_map[p_point.y-2][p_point.x]==' '||p_map[p_point.y-2][p_point.x]=='*')){//推箱子的判断p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y--;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新p_map[p_point.y-1][p_point.x] = 'O';//更新箱子}}else if(key == 'a'){//左if(p_map[p_point.y][p_point.x-1] == ' '||p_map[p_point.y][p_point.x-1] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x--;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y][p_point.x-1]=='O' && (p_map[p_point.y][p_point.x-2]==' '||p_map[p_point.y][p_point.x-2]=='*')){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x--;p_map[p_point.y][p_point.x] = 'H';p_map[p_point.y][p_point.x-1] = 'O';//更新箱子}}else if(key == 's'){//下if(p_map[p_point.y+1][p_point.x] == ' '||p_map[p_point.y+1][p_point.x] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.y++;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y+1][p_point.x]=='O' && (p_map[p_point.y+2][p_point.x]==' '||p_map[p_point.y+2][p_point.x]=='*')){p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y++;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新p_map[p_point.y+1][p_point.x] = 'O';//更新箱子}}else if(key == 'd'){//右if(p_map[p_point.y][p_point.x+1] == ' '||p_map[p_point.y][p_point.x+1] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x++;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y][p_point.x+1]=='O' && (p_map[p_point.y][p_point.x+2]==' '||p_map[p_point.y][p_point.x+2]=='*')){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x++;p_map[p_point.y][p_point.x] = 'H';p_map[p_point.y][p_point.x+1] = 'O';//更新箱子}}else if(key == 'r'){//重置Init(p_id);}}
Check()
该函数是判断胜利的条件的关键,我们在这一部分遍历所有的目标点位置,如果所有的目标点位置都变成了箱子的字符,则返回true,否则返回false。另外我们还要处理一种情况如下:
########### ###########
#H * O*# -> # * HO#
########### ###########
如上所示,我们控制小人经过一个目标点后将箱子推至最右侧目标点,那么按照我们上述对小人移动的逻辑实现,小人经过目标点后会将其“扫空”,即当箱子和小人离开了目标点,地图上将不会再显示,因为已经将地图中目标点位置的字符换为了空格。为解决这一bug也要在本函数中作处理。
如下:
bool Check(){//用于检测游戏胜利int len = p_check.size();int flag = 1;for(int i = 0; i < len; i ++){if(p_map[p_check[i].y][p_check[i].x] == ' '){//还原目标点p_map[p_check[i].y][p_check[i].x] == '*';}if(p_map[p_check[i].y][p_check[i].x] != 'O'){//判断是否为箱子flag = 0;}}if(flag){return true;}else{return false;}
}
main()
最后我们在主函数中直接定义推箱子对象并调用执行:
int main(){PushBox* p_game = new PushBox;p_game->Run();//游戏进行,进程将在死循环中p_game->Close();//游戏结束delete p_game;//释放指针空间p_game = nullptr;//指针置空,防止野指针system("pause");return 0;
}
三、完整源代码
#include<iostream>
#include<stdio.h>
#include<stdlib.h>
#include<windows.h>
#include<vector>
#include<conio.h>using namespace std;class GameFrame{
public:GameFrame(){}~GameFrame(){}virtual void Init() = 0;//游戏初始化virtual void Close() = 0;//游戏结束virtual void Paint() = 0;//游戏画面绘制virtual void Run() = 0;//游戏运行
};struct Point{int x;int y;void set(int px,int py){x = px;y = py;}
};//玩家坐标class PushBox : public GameFrame{
public:PushBox(){p_id = 1;Init(p_id);}~PushBox(){}void Init(){}void Init(int id){switch (id){case 1:{p_check.clear();char temp[16][16] = {" ", " "," ### "," #*# "," # # "," ####O###### "," #*O H O *# "," #####O##### "," # # "," #*# "," ### "," "," "," "," "," "};for(int i = 0; i < 16;i ++){//地图初始化for(int j = 0; j < 16; j ++){p_map[i][j] = temp[i][j];}}p_point.set(7,6);Point t_point;t_point.set(4,6);p_check.push_back(t_point);t_point.set(12,6);p_check.push_back(t_point);t_point.set(8,9);p_check.push_back(t_point);t_point.set(7,3);p_check.push_back(t_point);break;}default:break;}}//游戏初始化void Close(){system("cls");cout<<"you win!"<<endl;}//游戏结束void Paint() {//游戏画面绘制system("cls");//清空上一次图片for(int i = 0; i < 16; ++i){//更新地图打印puts(p_map[i]);}Sleep(10);//10毫秒刷新}void Run() {while(1){Paint();if(Check()){break;}char z = getch();Move(z);}}//游戏运行void Move(char key){if(key == 'w'){if(p_map[p_point.y-1][p_point.x] == ' '||p_map[p_point.y-1][p_point.x] == '*'){p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y--;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新}else if(p_map[p_point.y-1][p_point.x]=='O' && (p_map[p_point.y-2][p_point.x]==' '||p_map[p_point.y-2][p_point.x]=='*')){p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y--;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新p_map[p_point.y-1][p_point.x] = 'O';//更新箱子}}else if(key == 'a'){//左if(p_map[p_point.y][p_point.x-1] == ' '||p_map[p_point.y][p_point.x-1] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x--;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y][p_point.x-1]=='O' && (p_map[p_point.y][p_point.x-2]==' '||p_map[p_point.y][p_point.x-2]=='*')){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x--;p_map[p_point.y][p_point.x] = 'H';p_map[p_point.y][p_point.x-1] = 'O';//更新箱子}}else if(key == 's'){//下if(p_map[p_point.y+1][p_point.x] == ' '||p_map[p_point.y+1][p_point.x] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.y++;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y+1][p_point.x]=='O' && (p_map[p_point.y+2][p_point.x]==' '||p_map[p_point.y+2][p_point.x]=='*')){p_map[p_point.y][p_point.x] = ' ';//移动前的位置更新p_point.y++;//坐标更新p_map[p_point.y][p_point.x] = 'H';//地图更新p_map[p_point.y+1][p_point.x] = 'O';//更新箱子}}else if(key == 'd'){//右if(p_map[p_point.y][p_point.x+1] == ' '||p_map[p_point.y][p_point.x+1] == '*'){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x++;p_map[p_point.y][p_point.x] = 'H';}else if(p_map[p_point.y][p_point.x+1]=='O' && (p_map[p_point.y][p_point.x+2]==' '||p_map[p_point.y][p_point.x+2]=='*')){p_map[p_point.y][p_point.x] = ' ';//之前的位置更新为空格p_point.x++;p_map[p_point.y][p_point.x] = 'H';p_map[p_point.y][p_point.x+1] = 'O';//更新箱子}}else if(key == 'r'){//重置Init(p_id);}}bool Check(){//用于检测游戏胜利int len = p_check.size();int flag = 1;for(int i = 0; i < len; i ++){if(p_map[p_check[i].y][p_check[i].x] == ' '){p_map[p_check[i].y][p_check[i].x] == '*';}if(p_map[p_check[i].y][p_check[i].x] != 'O'){flag = 0;}}if(flag){return true;}else{return false;}}
private:int p_id;Point p_point;vector<Point> p_check;char p_map[16][16];//地图
};int main(){PushBox* p_game = new PushBox;p_game->Run();p_game->Close();delete p_game;p_game = nullptr;system("pause");return 0;
}
四、总结
本程序只是实现推箱子逻辑的DEMO,感兴趣的同学可以自己查找推箱子的其他关卡实现并完成关卡切换的部分。代码需改进的地方还有很多,如:
1.为了方便将源码一次性列出,我没有定义头文件编写,最好是将类以及全局变量的定义放在头文件中,类成员函数在源文件中实现,由main源文件调用头文件接口运行,如下:
pushbox.h ->定义类、全局变量
pushbox.cpp ->实现类的成员函数
main.cpp ->引用头文件<pushbox.h>后只写main函数
2.if-else的过多使用,这会使代码看起来很冗长,对于这部分还有可优化的地方,后续我会配合宏定义将这部分补充。
如果有其他优化的建议欢迎大家在我的评论区留言~
下期我会将游戏框架的代码完整的进行封装,并使用QT实现推箱子的图画版,类似下面的样子也将可以实现,敬请期待。
Codemon2024.02.02
相关文章:

【C++游戏开发-01】推箱子
C游戏开发 文章目录 C游戏开发[TOC](文章目录) 前言一、逻辑分析1.1地图实现1.2人物的移动1.2.1小人移动1.2.2其他移动 1.3墙壁的碰撞1.4箱子的推动1.4.1什么时候推箱子1.4.2什么情况可以推箱子 1.5胜利的判断1.6卡关的处理1.7关卡的切换 二、DEMO代码2.1游戏框架2.2各功能函数…...

【lesson26】学习MySQL事务前的基础知识
文章目录 CURD不加控制,会有什么问题?CURD满足什么属性,能解决上述问题?什么是事务?为什么会出现事务事务的版本支持 CURD不加控制,会有什么问题? CURD满足什么属性,能解决上述问题&…...
持续积累分享金融知识
持续积累分享金融知识 一、什么是两融余额?二、什么是量化?三、散户可以进行量化投资么? 一、什么是两融余额? 两融余额是指投资者在融资买入和融券卖出交易中,通过向券商借入资金或证券进行交易,并且在交…...

网络协议 UDP协议
网络协议 UDP协议 在之前的文章中有对UDP协议套接字的使用进行讲解,本文主要对UDP协议进行一些理论补充。 文章目录 网络协议 UDP协议1. 概念2. UDP协议格式2.1 数据报长度2.2 校验和/检验和2.2.1 CRC校验2.2.2 MD5算法 1. 概念 UDP,即User Datagram P…...

爬虫笔记(三):实战qq登录
咳咳,再这样下去会进橘子叭hhhhhh 以及,这个我觉得大概率是成功的,因为测试了太多次,登录并且验证之后,qq提醒我要我修改密码才可以登录捏QAQ 1. selenium 有关selenium具体是啥,这里就不再赘述了&#x…...

又涨又跌 近期现货黄金价格波动怎么看?
踏入2024年一月的下旬,现货黄金价格可以说没了之前火热的状态,盘面上是又涨又跌。面对这样的行情,很多投资者不知道如何看了。下面我们就来讨论一下怎么把握近期的行情。 先区分走势类型。在现货黄金市场中有两种主要的走势类型,一…...

软件压力测试:探究其目的与重要性
随着软件应用在各行各业中的广泛应用,确保软件在高负载和极端条件下的稳定性变得至关重要。软件压力测试是一种验证系统在不同负载条件下的性能和稳定性的方法。本文将介绍软件压力测试的目的以及为什么它对软件开发和部署过程至关重要。 验证系统性能的极限&#x…...

Android.bp入门指南之浅析Android.bp文件
文章目录 Android.bp文件是什么?Android.bp的主要作用模块定义依赖关系构建规则模块属性插件支持模块的可配置性 为什么会引入Android.bp语法例子 Android.bp文件是什么? Android.bp 文件是 Android 构建系统(Android Build Systemÿ…...

2024年美赛 (D题ICM)| 湖流网络水位控制 |数学建模完整代码+建模过程全解全析
当大家面临着复杂的数学建模问题时,你是否曾经感到茫然无措?作为2022年美国大学生数学建模比赛的O奖得主,我为大家提供了一套优秀的解题思路,让你轻松应对各种难题。 让我们来看看美赛的D题! 完整内容可以在文章末尾领…...

安卓网格布局GridLayout
<?xml version"1.0" encoding"utf-8"?> <GridLayout xmlns:android"http://schemas.android.com/apk/res/android"xmlns:tools"http://schemas.android.com/tools"android:layout_width"match_parent"android:la…...

DHCP简介
定义 动态主机配置协议DHCP(Dynamic Host Configuration Protocol)是一种用于集中对用户IP地址进行动态管理和配置的技术。即使规模较小的网络,通过DHCP也可以使后续增加网络设备变得简单快捷。 DHCP是在BOOTP(BOOTstrap Protoc…...
Hadoop生态系统中一些关键组件的详细解析
1. Hadoop核心组件 HDFS(Hadoop Distributed File System): 分布式文件存储系统。提供高吞吐量的数据访问,非常适合用于大规模数据集。有高容错性,通过在多个节点间复制数据块来实现。 MapReduce: 一种编程模型,用于在…...
功能强大的开源数据中台系统 DataCap 2024.01.1 发布
推荐一套基于 SpringBoot 开发的简单、易用的开源权限管理平台,建议下载使用: https://github.com/devlive-community/authx 推荐一套为 Java 开发人员提供方便易用的 SDK 来与目前提供服务的的 Open AI 进行交互组件:https://github.com/devlive-commun…...

Redis的bitmap使用不当,我内存爆了
背景 最近发现Redis的内存持续暴涨, 涨的有点吓人,机器都快扛不住了,不得不进行Redis内存可视化分析,发现大量的String类型的大key 经分析,最近上线了页面UV的统计,那目前如何做的呢? 通过访…...
基于python的新闻爬虫
咱们这个任务啊,就是要从一个指定的网站上,抓取新闻内容,然后把它们整整齐齐地保存到本地。具体来说,就是要去光明网的板块里,瞅瞅里面的新闻,把它们一条条地保存下来。 首先,咱得有个网址&…...
C#基础题
值类型和引用类型之间的区别是什么? 值类型在内存中存储实际值,而引用类型存储对对象的引用。值类型在栈上分配内存,而引用类型在堆上分配内存。值类型是不可变的,而引用类型是可变的。值类型的大小是固定的,而引用类型…...

AI大语言模型学习笔记之三:协同深度学习的黑魔法 - GPU与Transformer模型
Transformer模型的崛起标志着人类在自然语言处理(NLP)和其他序列建模任务中取得了显著的突破性进展,而这一成就离不开GPU(图形处理单元)在深度学习中的高效率协同计算和处理。 Transformer模型是由Vaswani等人在2017年…...

c++阶梯之auto关键字与范围for
auto关键字(c11) 1. auto关键字的诞生背景 随着程序的逐渐复杂,程序代码中用到的类型也越来越复杂。譬如: 类型难以拼写;含义不明确容易出错。 比如下面一段代码: #include <string> #include &…...

第八篇:node模版引擎Handlebars及他的高级用法(动态参数)
🎬 江城开朗的豌豆:个人主页 🔥 个人专栏 :《 VUE 》 《 javaScript 》 📝 个人网站 :《 江城开朗的豌豆🫛 》 ⛺️ 生活的理想,就是为了理想的生活 ! 目录 📘 引言: …...

css3 属性 backface-visibility 的实践应用
backface-visibility 是一个用于控制元素在面对屏幕不同方向时的可见性的CSS3特性。它有两个可能的值: visible:当元素不面向屏幕(即背面朝向用户)时,元素的内容是可以被看到的。hidden:当元素不面向屏幕…...
conda相比python好处
Conda 作为 Python 的环境和包管理工具,相比原生 Python 生态(如 pip 虚拟环境)有许多独特优势,尤其在多项目管理、依赖处理和跨平台兼容性等方面表现更优。以下是 Conda 的核心好处: 一、一站式环境管理:…...
【根据当天日期输出明天的日期(需对闰年做判定)。】2022-5-15
缘由根据当天日期输出明天的日期(需对闰年做判定)。日期类型结构体如下: struct data{ int year; int month; int day;};-编程语言-CSDN问答 struct mdata{ int year; int month; int day; }mdata; int 天数(int year, int month) {switch (month){case 1: case 3:…...

HDFS分布式存储 zookeeper
hadoop介绍 狭义上hadoop是指apache的一款开源软件 用java语言实现开源框架,允许使用简单的变成模型跨计算机对大型集群进行分布式处理(1.海量的数据存储 2.海量数据的计算)Hadoop核心组件 hdfs(分布式文件存储系统)&a…...
A2A JS SDK 完整教程:快速入门指南
目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库ÿ…...
libfmt: 现代C++的格式化工具库介绍与酷炫功能
libfmt: 现代C的格式化工具库介绍与酷炫功能 libfmt 是一个开源的C格式化库,提供了高效、安全的文本格式化功能,是C20中引入的std::format的基础实现。它比传统的printf和iostream更安全、更灵活、性能更好。 基本介绍 主要特点 类型安全:…...

ubuntu系统文件误删(/lib/x86_64-linux-gnu/libc.so.6)修复方案 [成功解决]
报错信息:libc.so.6: cannot open shared object file: No such file or directory: #ls, ln, sudo...命令都不能用 error while loading shared libraries: libc.so.6: cannot open shared object file: No such file or directory重启后报错信息&…...

HTML版英语学习系统
HTML版英语学习系统 这是一个完全免费、无需安装、功能完整的英语学习工具,使用HTML CSS JavaScript实现。 功能 文本朗读练习 - 输入英文文章,系统朗读帮助练习听力和发音,适合跟读练习,模仿学习;实时词典查询 - 双…...

使用VMware克隆功能快速搭建集群
自己搭建的虚拟机,后续不管是学习java还是大数据,都需要集群,java需要分布式的微服务,大数据Hadoop的计算集群,如果从头开始搭建虚拟机会比较费时费力,这里分享一下如何使用克隆功能快速搭建一个集群 先把…...

循环语句之while
While语句包括一个循环条件和一段代码块,只要条件为真,就不断 循环执行代码块。 1 2 3 while (条件) { 语句 ; } var i 0; while (i < 100) {console.log(i 当前为: i); i i 1; } 下面的例子是一个无限循环,因…...

实现p2p的webrtc-srs版本
1. 基本知识 1.1 webrtc 一、WebRTC的本质:实时通信的“网络协议栈”类比 将WebRTC类比为Linux网络协议栈极具洞察力,二者在架构设计和功能定位上高度相似: 分层协议栈架构 Linux网络协议栈:从底层物理层到应用层(如…...