C语言写雷霆战机(图形库)
这里我用的VS,图形库是EasyX(可以到官方上下载,超级简单几KB)
下载好这个.exe文件
然后点击,会自动识别电脑上可以安装的软件
然后安装就行了,这是打开VS就可以直接引入头文件
#include<graphics.h>
雷霆战机源码以及素材:
链接:/s/1hqFnO12nebMgGB2gQJeVSg
提取码:3ecs
由于素材时间原因就大概,写到这但是思路已经打开,大家可以根据自己
的想法进行扩充
上图
#include<stdio.h>#include<graphics.h>//图形库,需要下载easyX,#include<Windows.h>#include<stdlib.h>#include<conio.h>#include<mmsystem.h>//系统设备头文件#pragma comment(lib,"winmm.lib")//加载多媒体设备库//将所有素材,拷贝到当前项目的文件里面//然后我们在使用图片的时候,需要将图片加载到内存,(就像我们要使用字母a一样我们同样需要在//在内存中加载一个a,因为程序是在内存上运行的,故我们需要一个变量来储存这个图片//在graphics.h--图形库中提供了一个变量IMAGE(图片的意思),IMAGE backInit_Phone;//初始界面,一闪而过;IMAGE backGame_Phone;//关卡的背景IMAGE player_Phone[2];//游戏角色的图片,需要掩码图,以及原位图IMAGE ammunition_Phone[2];//弹药图片;IMAGE enemy_Phone[2][2];//两种敌机的图片IMAGE ok;//确定图片enum gameAttributes//游戏各种属性,这里我就不用宏定义了,使用枚举类,因为我对枚举类不熟悉{X = 1200,Y = 750,ammunitionNumber = 300, //弹药数量Big = 9, //用于标志大飞机Small = 0, //用于标志小的飞机enemyNumber = 15 //一次最多出现多少敌机};//定义一个结构体用于描述,玩家战机的属性以及其他数据等,因为角色的好多属性都是变量;struct playerAttributes {int x, y; //玩家战机坐标 弹药坐标bool isLive;//是否存活弹药是否发出int speed;//飞机速度 弹药速度int HP;int killsNumber;}fighterPlane, ammunition[ammunitionNumber];//Fighter plane--战机//定义一个结构体,用于描述敌方战斗机的属性struct enemyAttributes {int x, y;int speed;bool isLive;int Type;int HP;}Plane[enemyNumber];//专门用于加载图片的方法,调用图形库中的loadimage()方法;,加载音乐void loadImage() {//这里会报错,因为字符集不对,我们图形库里面专门定义的字符集,识别错误..具体原因//博客上有,解决方案,将当前项目的字符姐由Unicode -> 多字节字符集loadimage(&player_Phone[0], "./image/player.jpg");//加载飞机掩位码图loadimage(&player_Phone[1], "./image/player2.jpg");//加载飞机源图loadimage(&backInit_Phone,"./image/初始化背景.jpg");//加载游戏欢迎以及介绍背景图loadimage(&backGame_Phone,"./image/游戏背景--最终.jpg");//加载关卡图loadimage(&ammunition_Phone[0], "./image/导弹.png");loadimage(&ammunition_Phone[1], "./image/导弹2.png");loadimage(&enemy_Phone[0][0], "./image/敌机.jpg");loadimage(&enemy_Phone[0][1], "./image/敌机2.jpg");loadimage(&enemy_Phone[1][0], "./image/大敌机.jpg");loadimage(&enemy_Phone[1][1], "./image/大敌机2.jpg");loadimage(&ok, "./image/2.png");mciSendString("open ./image/初始背景音乐.mp3",0,0,0);//先打开音乐,然后后面三个擦书不用管;//mciSendString("open ./image/死亡音乐.mp3 alias deathBgm", 0, 0, 0);//先打开音乐,然后后面三个擦书不用管;//mciSendString("open ./image/导弹.mp3 alias missileBgm", 0, 0, 0);//先打开音乐,然后后面三个擦书不用管;//mciSendString("open ./image/boss.mp3", 0, 0, 0);//先打开音乐,然后后面三个擦书不用管;mciSendString("open ./image/Boss背景.mp3", 0, 0, 0);//先打开音乐,然后后面三个擦书不用管;mciSendString("open ./image/游戏音乐.mp3", 0, 0, 0);//先打开音乐,然后后面三个擦书不用管;//alias 起别名}//用于欢迎以及介绍界面dbool Welcome() {initgraph(X, Y);putimage(0, 0, &backInit_Phone);//将图片贴到窗口上,0.0是坐标,后是哪张图putimage(500,400,&ok);mciSendString("play ./image/初始背景音乐.mp3", 0, 0, 0);while (1) {if (_getch() == 'b') {//_getch()这个函数就是getch(),不过vs以后getch()不知到为啥//会报错,所以我在博客上搜出了这个函数_getch();closegraph();return true;}}return false;}//初始化玩家数据,以及地图,子弹void initData() {loadImage();//先将图片加载到内存里;由于图片只需要加载一次就可以了,所以我们就放在初始化数据的地方fighterPlane.x = X / 2;fighterPlane.y = Y - 82;fighterPlane.speed = 2;fighterPlane.isLive = true;fighterPlane.HP = 20;fighterPlane.killsNumber = 0;//初始化弹药 for (int i = 0; i < ammunitionNumber;++i) {ammunition[i].x = 0; //我们这里是为了给个默认值,实际上弹药的初始坐标是根据飞机来定位的ammunition[i].y = 0;ammunition[i].isLive = false;ammunition[i].speed = 1;}//初始化敌机for (int i = 0; i < enemyNumber;++i) {Plane[i].isLive = false;Plane[i].speed = 1;Plane[i].Type = 0;Plane[i].HP = 1;Plane[i].x = 0;Plane[i].y = 0;}}//自动创建敌方战斗机,并完成数据赋值void createEnemyPlane() {mciSendString("play ./image/游戏音乐.mp3 repeat", 0, 0, 0);//mciSendString("play ./image/Boss背景.mp3 ", 0, 0, 0);static DWORD time1, time2 = 0;int r = rand() % 11;if (time2 - time1 > 1000) {if (r==0) {for (int i = 0; i < enemyNumber;++i) {if (!Plane[i].isLive) {Plane[i].isLive = true;Plane[i].HP = 3;Plane[i].speed = 1;Plane[i].Type = Big;Plane[i].x = rand() % (X - 192/2);Plane[i].y = rand() % 500 - 500;break;}}}else {for (int i = 0; i < enemyNumber; ++i) {if (!Plane[i].isLive) {Plane[i].isLive = true;Plane[i].HP = 1;Plane[i].speed = 1;Plane[i].Type = Small;Plane[i].x = rand() % (X - 90/2);Plane[i].y = rand() % 500 - 500;break;}}}time1 = time2;}time2 = GetTickCount();}//如果玩家按下空格键,代表飞机发射了弹药,那就把该弹药标记位true,且根据当前飞机的位置定位弹药位置void createAmmunition() {//这里只需要对弹药数据进行修改就行了,真正按键实在其他地方for (int i = 0; i < ammunitionNumber;++i) {if (!ammunition[i].isLive) {ammunition[i].isLive = true;//让子弹存活ammunition[i].x = fighterPlane.x + 82 / 2;//这个数据就是战机的头部ammunition[i].y = fighterPlane.y;break;}}}//打印游戏图片void printGameImage() {putimage(0, 0, &backGame_Phone);//贴图putimage(fighterPlane.x, fighterPlane.y, &player_Phone[1], NOTSRCERASE);//先贴掩码图//NOTSRCERASE使用OR运算符组合源位图,的反*与目标矩阵的颜色*//SRCINVERT使用XOR运算符,组合目标矩阵形与模板颜色*;putimage(fighterPlane.x, fighterPlane.y, &player_Phone[0], SRCINVERT);//后贴原位图outtextxy(0,0,fighterPlane.killsNumber);//如果弹药不为false,说明弹药已经被发射出去了,那就绘制弹药for (int i = 0; i < ammunitionNumber; ++i) {if (ammunition[i].isLive) {putimage(ammunition[i].x, ammunition[i].y, &ammunition_Phone[1], NOTSRCERASE);//后贴原位图putimage(ammunition[i].x, ammunition[i].y, &ammunition_Phone[0], SRCINVERT);//后贴原位图}}//绘制敌机for (int i = 0; i < enemyNumber;++i) {if (Plane[i].isLive) {if (Plane[i].Type == Big) {putimage(Plane[i].x, Plane[i].y, &enemy_Phone[1][1], NOTSRCERASE);putimage(Plane[i].x,Plane[i].y,&enemy_Phone[1][0], SRCINVERT);}else {putimage(Plane[i].x, Plane[i].y, &enemy_Phone[0][1], NOTSRCERASE);putimage(Plane[i].x, Plane[i].y, &enemy_Phone[0][0], SRCINVERT);}}//printf("xy(%d ,%d) HP = %d isLive = %d\n",Plane[i].x, Plane[i].y, Plane[i].HP, Plane[i].isLive);//Sleep(10);}}//移动战机,移动子弹void moveFighterPlane() {//有两种方式,1._getch()从键盘读取一个字符,但是它是阻塞的,而且不是C语言的正规函数,这是windows里面//的函数,包含头文件conio.h。可以借助_kbhit()函数来实现类似不阻塞,(该函数功能,检测键盘是否按下键,如果按下//返回真,否则返回假2.另外方法,使用windows里面的函数,GetAsyncKeyState(键盘按键名字),获取键盘输入)//这里我们使用第二种;//为了防止飞机飞出边界,这里还需要判断一下;//如果用字母必须是大写,这样大小写才能检测到,否则都检测不到if (GetAsyncKeyState(VK_UP)|| GetAsyncKeyState('W')) {//上,这里的VK_UP其实就是宏定义,代表键盘的上,类似还有VK_DOWN等if(fighterPlane.y > 0)fighterPlane.y -= fighterPlane.speed;}//飞机也是右长度的,所以我们需要一尾部的点判断下移的情况;//当前y + 飞机长 = 尾y坐标,但是又有个问题,飞机发射的子弹是从中间发射的,而且走直线//那如果敌机出现在窗口边缘,就打不到了,所以我们可以让飞机出去一半if (GetAsyncKeyState(VK_DOWN)|| GetAsyncKeyState('S')) {//下if (fighterPlane.y + 82 < Y)fighterPlane.y += fighterPlane.speed;}if (GetAsyncKeyState(VK_LEFT)|| GetAsyncKeyState('A')) {//左if (fighterPlane.x + 82/2 > 0)fighterPlane.x -= fighterPlane.speed;}//右移同理if (GetAsyncKeyState(VK_RIGHT)|| GetAsyncKeyState('D')) {//右if (fighterPlane.x + 86/2 < X)fighterPlane.x += fighterPlane.speed;}//但是我们会发现,子弹好像只能发射一个,因为程序运行的太快了,你点一下空格,GetAsyncKeyState()//会检测很多次,以至于一次把子弹发射完了;所以你会看到有一个长长黑影,解决方案,使用局部休眠// 这个在数字雨中使用过;static DWORD time1, time2 = 0;if (GetAsyncKeyState(VK_SPACE) && (time2 - time1 > 200) ) {//mciSendString("play missileBgm",0,0,0);createAmmunition();time1 = time2;}time2 = GetTickCount();//弹药的移动for (int i = 0; i < ammunitionNumber;++i) {if (ammunition[i].isLive) {ammunition[i].y -= ammunition[i].speed;}}//敌机的移动static DWORD T1, T2 = 0;if (T2- T1>10) {for (int i = 0; i < enemyNumber;++i) {if (Plane[i].isLive) {Plane[i].y += Plane[i].speed;}}T1 = T2;}T2 = GetTickCount();}//敌机阵亡void planeDeath() {for (int i = 0; i < enemyNumber;++i) {if (!Plane[i].isLive) continue;for (int j = 0; j < ammunitionNumber;++j) {if (!ammunition[j].isLive)continue;if ((Plane[i].Type == Big) && ((ammunition[j].x > Plane[i].x) && (ammunition[j].x < Plane[i].x + 192)) && ((ammunition[j].y > Plane[i].y) && (ammunition[j].y < Plane[i].y + 290))) {//当弹药坐标,在敌方飞机的体内;即打中了飞机ammunition[j].isLive = false;Plane[i].HP--;}if ((Plane[i].Type == Small) && ((ammunition[j].x > Plane[i].x) && (ammunition[j].x < Plane[i].x + 90)) &&((ammunition[j].y > Plane[i].y) && (ammunition[j].y < Plane[i].y + 90))) {//当弹药坐标,在敌方飞机的体内;即打中了飞机ammunition[j].isLive = false;Plane[i].isLive = false;fighterPlane.killsNumber++;//putimage(ammunition[i].x, ammunition[i].y, &ammunition_Phone[0], SRCINVERT);//后贴原位图);mciSendString("play deathBgm", 0, 0, 0);}}if (Plane[i].HP == 0) {Plane[i].isLive = false;fighterPlane.killsNumber++;}}}int main(){srand(GetTickCount());initData();Welcome();//开始游戏界面initgraph(X, Y,SHOWCONSOLE);//游戏界面,showconsole这是第三个参数,把控制台输出出来//会看到一闪闪的;这是因为,在内存中加载的数据,我们的程序一直取,以至于速度过快,图片//还没有在内存画好,就被取出,让我们看到了绘制的过程;解决方案,使用双缓冲,让图片在缓冲区//绘制完毕后在取出BeginBatchDraw();while(1) {createEnemyPlane();printGameImage();FlushBatchDraw();//刷因缓冲区,让在缓冲区绘制好的图片输出出来;moveFighterPlane();planeDeath();}EndBatchDraw();//理论上实在这一步才将绘制好的图片输出出来;closegraph();return 0;}