C语言雷霆战机

C语言写雷霆战机(图形库)

这里我用的VS2019,图形库是EasyX(可以到官方上下载,超级简单几KB)
在这里插入图片描述
下载好这个.exe文件
然后点击,会自动识别电脑上可以安装的软件
在这里插入图片描述
然后安装就行了,这是打开VS2019就可以直接引入头文件
#include<graphics.h>
雷霆战机源码以及素材:
链接:https://pan.baidu.com/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 起别名
}
//用于欢迎以及介绍界面d
bool 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(),不过vs2012以后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;
}
  • 3
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值