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;
}