有手就能学会- C语言零基础手写坦克大战

1.2 项目介绍

2.1. 项目需求
实现1款和经典的《90坦克大战》一样的游戏,任务是消灭敌对坦克,保护己方领地。防止敌方打破你的老窝围墙而把你的鹰打坏。

在这里插入图片描述

2.2. 学习目标

回顾经典,按照软件开发的标准流程,零基础开发第一个完整项目,快速提高编程能力和水平,真正理解软件开发的概念和基本流程!

2.3. 授课方式

手把手方式教大家完整的实现项目所有源码并进行讲解,不懂之处,随时可以咨询Martin老师答疑!QQ: 2684436901

2.项目准备

2.1 环境安装
请参考《项目经理带你-零基础学习C++》 第6节 C++开发环境 安装好开发工具vc2010 ,课程链接:https://ke.qq.com/course/377567?tuin=cc02ada

2.2图形库安装
1.下载easyx 图形库 网址:https://www.easyx.cn/
2. 安装

2.3开发环境测试

#include <graphics.h>

void main()
{
	initgraph(666, 666);          //定义画布大小 666*666
	system("pause");
	
}

3.项目启动

3.1模块划分
(作用:1.化繁为简 2.适合团队协作 3.高质量代码)
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
3.2源准备
美工素材请从奇牛C/C++学习交流群文件夹下载
群号:875300321
下载路径: 群文件夹 => 坦克大战资源 => 坦克大战_图片&音乐.rar

4.项目实现

4.1模块1 - 开始场景
在这里插入图片描述
“搭台唱戏”要素:

  • “戏台” - 绘图环境

  • Logo - 美工图片,游戏标志

  • 按钮 - 实现“说明”和“开始”导航

  • 说明 - 美工图片,操作说明

4.1.1戏台实现

#include <graphics.h>

void main()
{
	initgraph(650, 650);          //定义画布大小 650*650
	system("pause");
	
}

项目精讲 - 像素

像素是整个图像中不可分割的单位或者是元素,每个像素近似一个小方块,这些小方块都有一个明确的位置和被分配的色彩数值(显示不同的颜色)
在这里插入图片描述

项目精讲 - 分辨率

分辨率(屏幕分辨率)是屏幕图像的精密度,是指显示器所能显示的像素有多少,分辨率越大,单位面积内分布的像素点就越多,画面就越精细

如: 14英寸笔记本屏幕分辨率 1280 x 960 表示的意义是屏幕是由 1280 乘以 960 = 1228800 个像素点组成,其中宽占1280 个像素,高占960 像素
在这里插入图片描述

4.1.2显示LOGO

//显示 logo
	IMAGE logo_img;
	loadimage(&logo_img, _T("logo.bmp"), 433, 147);
	putimage(110, 20, &logo_img);

在这里插入图片描述

void menu(){
	//显示 logo
	IMAGE logo_img;
	loadimage(&logo_img, _T("logo.bmp"), 433, 147);
	putimage(110, 20, &logo_img);

	//实现导航按钮
	setlinecolor(WHITE);
	setfillcolor(BLACK);
	fillrectangle(230, 200, 310, 240);
	settextstyle(25, 0, _T("宋体"));
	outtextxy(240, 210, _T("说 明")); 
	fillrectangle(350, 200, 430, 240);
	outtextxy(360, 210, _T("开 始"));

	MOUSEMSG mouse;
	IMAGE illustrate_img;
	loadimage(&illustrate_img, _T("illustrate.jpg"), 300, 300);

	while(1==1){
		mouse=GetMouseMsg();

		switch(mouse.uMsg){
		case WM_MOUSEMOVE:
			if((mouse.x>230 && mouse.x<310) && (mouse.y>200 && mouse.y<240)){
				putimage(150, 250, &illustrate_img);
			}else {
				solidrectangle(150, 250, 450, 550);
			}
			break;
		case WM_LBUTTONDOWN:
			if((mouse.x >350 && mouse.x<430) && (mouse.y>200 && mouse.y<240)){
				cleardevice();
				return ;
			}
		}
	}
}

4.1.3导航按钮实现
使用矩形绘制函数和文字输出函数实现按钮显示效果
附:EasyX 帮助文档
群号:875300321
下载路径: 群文件夹 => 坦克大战资源 => EasyX_Help.chm

在这里插入图片描述

  • 使用鼠标事件实现导航效果
    1.当鼠标移到 “说明” 按钮时,显示操作说明,当鼠标离开隐藏
    2.当鼠标点击“开始”按钮时,就进入 游戏场景

4.2模块2 - 游戏场景

地图初始化

在这里插入图片描述

名称
角色国王皇后战车主教骑士禁卫军

地图表示:
使用二维数组

  • 游戏道具显示(墙、老鹰、我方坦克、敌方坦克、子弹)
  • 便于程序控制敌方坦克前进,控制子弹移动和判断子弹击中目标等

道具表示:
可消除墙为1,不可消除墙为2,老鹰(3,4),敌方坦克 100 - 109,我方坦克200

在这里插入图片描述
在这里插入图片描述
代码实现

//定义地图数组
int map[26][26] = {
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 2, 2, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 1, 1, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 1, 1 },
{ 2, 2, 0, 0, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 0, 0, 2, 2 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 1, 1, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 1, 1, 0, 0, 1, 1, 0, 0, 0, 1, 1, 1, 1, 0, 0, 0, 1, 1, 0, 0, 1, 1, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 },
{ 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 3, 3, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }
};


void menu();
void init_map();
void init_map_2(int *map, int rows, int cols);

int main(void){

	//搭建舞台
	initgraph(650,650);

	//开始场景,显示菜单
	menu();

	//初始化地图
	init_map_2(&map[0][0], 26, 26);

	system("pause");
}


/*
初始化地图,可消除墙为1,不可消除墙为 2,老鹰 (3 ,4)
*/
void init_map(){
	int i, j;
	IMAGE img_home, img_wall_1, img_wall_2;

	loadimage(&img_home, _T("home.jpg"), 50, 50);// 老鹰
	loadimage(&img_wall_1, _T("wall1.jpg"), 25, 25);//不可消除的墙
	loadimage(&img_wall_2, _T("wall2.jpg"), 25, 25);//可消除的墙

	for(i=0; i<26; i++){
		for(j=0; j<26; j++){
			if(map[i][j] == 1){
				putimage(25*j, 25*i, &img_wall_2);
			}else if(map[i][j] == 2){
				putimage(25*j, 25*i, &img_wall_1);
			}else if(map[i][j] == 3){
				putimage(25*j, 25*i, &img_home);

				map[i][j]     = 4;
				map[i][j+1]   = 4;
				map[i+1][j]   = 4;
				map[i+1][j+1] = 4;
			}
		}
	}
}
//逼格更高、兼容性更好的初始化地图函数
void init_map_2(int *map, int rows, int cols){
	int i, j;
	IMAGE img_home, img_wall_1, img_wall_2;

	loadimage(&img_home, _T("home.jpg"), 50, 50);// 老鹰
	loadimage(&img_wall_1, _T("wall1.jpg"), 25, 25);//不可消除的墙
	loadimage(&img_wall_2, _T("wall2.jpg"), 25, 25);//可消除的墙

	for(i=0; i<rows; i++){
		for(j=0; j<cols; j++){
			if(*(map+cols*i+j) == 1){
				putimage(25*j, 25*i, &img_wall_2);
			}else if(*(map+cols*i+j) == 2){
				putimage(25*j, 25*i, &img_wall_1);
			}else if(*(map+cols*i+j) == 3){
				putimage(25*j, 25*i, &img_home);
              //以下代码需要考虑数组越界,该如何处理?
				*(map+cols*i+j)   = 4;
				*(map+cols*i+(j+1))   = 4;
				*(map+cols*(i+1)+j)   = 4;
				*(map+cols*(i+1)+(j+1)) = 4;
			}
		}
	}
}


4.2.2我方坦克实现

坦克结构体定义

enum DIRECTION{
	UP,
	DOWN,
	LEFT,
	RIGHT
};


//坦克结构体
struct tank_s{
    int x;  //坦克在地图数组中所在列
	int y;  //坦克在地图数组中所在的行
	DIRECTION direction;  //坦克的方向,上、下、左、右
	int live;       //是否生存 1-活着  0-挂了
};

我方坦克显示

tank_s my_tank;
	IMAGE my_tank_img[4];

	//加载我方坦克的图片
	loadimage(&my_tank_img[UP],_T("tank_up.jpg"),50,50);
	loadimage(&my_tank_img[DOWN],_T("tank_down.jpg"),50,50);
	loadimage(&my_tank_img[LEFT],_T("tank_left.jpg"),50,50);
	loadimage(&my_tank_img[RIGHT],_T("tank_right.jpg"),50,50);


	//设定我方坦克的出场的位置
	my_tank.x = 8;
	my_tank.y = 24;

	my_tank.live = 1;
	my_tank.direction = UP;

	map[my_tank.y][my_tank.x] =200;
	map[my_tank.y][my_tank.x+1] =200;
	map[my_tank.y+1][my_tank.x] =200;
	map[my_tank.y+1][my_tank.x+1] =200;

	putimage(my_tank.x * 25, my_tank.y * 25, &my_tank_img[my_tank.direction]);

热键控制(aswd )

/*****************************
 *实现游戏场景
******************************/
void play(){

	tank_s my_tank;
	IMAGE my_tank_img[4];
	int key;

	//加载我方坦克的图片
	loadimage(&my_tank_img[UP],_T("tank_up.jpg"),50,50);
	loadimage(&my_tank_img[DOWN],_T("tank_down.jpg"),50,50);
	loadimage(&my_tank_img[LEFT],_T("tank_left.jpg"),50,50);
	loadimage(&my_tank_img[RIGHT],_T("tank_right.jpg"),50,50);


	//设定我方坦克的出场的位置
	my_tank.x = 8;
	my_tank.y = 24;

	my_tank.live = 1;
	my_tank.direction = UP;

	set_prop_map(my_tank.x, my_tank.y, 200);
	putimage(my_tank.x * 25, my_tank.y * 25, &my_tank_img[my_tank.direction]);

	while(1){
		if(_kbhit()){
			key = _getch(); 

			switch(key){
			case 'a':  //左
				if((my_tank.x-1)>=0 && map[my_tank.y][my_tank.x-1] ==0 &&map[my_tank.y+1][my_tank.x-1] ==0){//左边是空地
					my_tank.direction = LEFT;
					tank_walk(&my_tank, LEFT, &my_tank_img[my_tank.direction]);
				}
				break;
			case 'w':  //上
				if((my_tank.y-1)>=0 && map[my_tank.y-1][my_tank.x] ==0 &&map[my_tank.y-1][my_tank.x+1] ==0){//上边是空地
					my_tank.direction = UP;
					tank_walk(&my_tank, UP, &my_tank_img[my_tank.direction]);
				}
				break;
			case 's':  //下
				if((my_tank.y+2)<=25 && map[my_tank.y+2][my_tank.x] ==0 &&map[my_tank.y+2][my_tank.x+1] ==0){//下边是空地
					my_tank.direction = DOWN;
					tank_walk(&my_tank, DOWN, &my_tank_img[my_tank.direction]);
				}
				break;
			case 'd':  //右
				if((my_tank.x+2)<=25 && map[my_tank.y][my_tank.x+2] ==0 &&map[my_tank.y+1][my_tank.x+2] ==0){//右边是空地
					my_tank.direction = RIGHT;
					tank_walk(&my_tank, RIGHT,&my_tank_img[my_tank.direction]);
				}
				break;
			case 'j':  //开火
				break;
			case 'p':  //暂停
				system("pause");
				break;
			default:   //其他键盘输入无须处理
				break;
			}
		}

		Sleep(10);

	}
}


坦克移动


void set_prop_map(int x, int y, int val){
	map[y][x] = val;
	map[y][x+1] = val;
	map[y+1][x] = val;
	map[y+1][x+1] = val;
}

/*********************************
 *控制坦克按相应的方向前进一步
 *返回值:失败 - 0   成功 -1
 *********************************/
int tank_walk(tank_s *tank, DIRECTION direction, IMAGE *img){
	int new_x = tank->x;
	int new_y = tank->y;

	if(direction == UP){
		new_y -= 1;
	}else if(direction == DOWN){
		new_y += 1;
	}else if(direction == LEFT){
		new_x -= 1;
	}else if(direction == RIGHT){
		new_x += 1;
	}else {
		return 0; //无效的方向
	}

	set_prop_map(tank->x, tank->y, 0);
	setfillcolor(BLACK);
	solidrectangle(tank->x*25,tank->y*25, tank->x*25+50, tank->y*25+50);

	set_prop_map(new_x, new_y, 200);

	tank->x = new_x; 
	tank->y = new_y;
	putimage(tank->x * 25, tank->y * 25, img);
	return 1;
}

4.2.3子弹飞行控制实现

子弹结构体定义

//子弹结构体
struct bullet_s{
	int pos_x;   //子弹在“戏台”上的横坐标
	int pos_y;   //子弹在“戏台”上的纵坐标
	DIRECTION  direction; //子弹方向
	int status;  //子弹是否存在
};

子弹热键控制 (j)

if(my_bullet.status == 0){

					if(my_tank.direction == UP){
						my_bullet.pos_x = my_tank.x * 25 + 23;
						my_bullet.pos_y = my_tank.y * 25 -3;
					}else if(my_tank.direction == LEFT){
						my_bullet.pos_x = my_tank.x * 25 -3;
						my_bullet.pos_y = my_tank.y * 25 +23;
					}else if(my_tank.direction == DOWN){
						my_bullet.pos_x = my_tank.x * 25 + 23;
						my_bullet.pos_y = my_tank.y * 25 + 50;
					}else if(my_tank.direction == RIGHT){
						my_bullet.pos_x = my_tank.x * 25 + 50;
						my_bullet.pos_y = my_tank.y * 25 + 23;
					}

					my_bullet.direction = my_tank.direction;
					my_bullet.status = 1;
				}

子弹运行和碰撞检测

void bullet_action(bullet_s *bullet){
	int x,y,x1,y1;  //子弹目前所在的二维数组中的坐标

	x = bullet->pos_x/25;
	y = bullet->pos_y/25;

	//1.擦除上一次绘制的子弹
	setfillcolor(BLACK);
	solidrectangle(bullet->pos_x, bullet->pos_y, bullet->pos_x+3, bullet->pos_y+3);

	//2.根据方向计算子弹在“戏台”上的坐标
	if(bullet->direction == UP){
		bullet->pos_y -=  2;
		x1 = x+1;
		y1 = y;
	}else if(bullet->direction == DOWN){
		bullet->pos_y +=  2;
		x1 = x+1;
		y1 = y;
	}else if(bullet->direction == LEFT){
		bullet->pos_x -=  2;
		x1 = x;
		y1 = y+1;

	}else if(bullet->direction == RIGHT){
		bullet->pos_x +=  2;
		x1 = x;
		y1 = y+1;
	}else{
		return;
	}

	if(bullet->pos_x<0 || bullet->pos_x>650 || bullet->pos_y<0 || bullet->pos_y>650){
		bullet->status = 0;
		return;
	}

	//碰撞检测
	if(map[y][x] == 4 || map[y1][x1] == 4){
		return ;
	}

	if(map[y][x]== 1){//子弹击中可消除的墙
		map[y][x]= 0;
		bullet->status = 0;
		setfillcolor(BLACK);
		solidrectangle(x*25, y*25, x*25+25, y*25+25);
	}else if(map[y][x]== 2){
		bullet->status = 0;
	}

	if(map[y1][x1]== 1){//子弹击中可消除的墙
		map[y1][x1]= 0;
		bullet->status = 0;
		setfillcolor(BLACK);
		solidrectangle(x1*25, y1*25, x1*25+25, y1*25+25);
	}else if(map[y1][x1]== 2){
		bullet->status = 0;
	}


	//3.重新绘制子弹
	if(bullet->status == 1){
		setfillcolor(WHITE);
		solidrectangle(bullet->pos_x, bullet->pos_y, bullet->pos_x+3, bullet->pos_y+3);
	}
}


4.2.4敌方坦克实现

坦克出场

1.首先出场3台坦克,然后每隔小段时间出场一辆坦克

tank_s enemy_tank[ENEMY_NUM]; //敌方坦克
bullet_s enemy_bullet[ENEMY_NUM];//敌方坦克发射的子弹

IMAGE enemy_tank_img[4];
//加载敌方坦克的图片
loadimage(&enemy_tank_img[UP],_T("enemy_tank_up.jpg"),50,50);
loadimage(&enemy_tank_img[DOWN],_T("enemy_tank_down.jpg"),50,50);
loadimage(&enemy_tank_img[LEFT],_T("enemy_tank_left.jpg"),50,50);
loadimage(&enemy_tank_img[RIGHT],_T("enemy_tank_right.jpg"),50,50);

//设置敌方坦克出场的位置
	for(int i=0; i<ENEMY_NUM; i++){
		if(i%3 == 0){
			enemy_tank[i].x = 0;
		}else if(i%3 == 1){
			enemy_tank[i].x = 12;
		}else if(i%3 == 2){
			enemy_tank[i].x = 24;
		}
		enemy_tank[i].direction = DOWN;
		enemy_tank[i].y = 0;
		enemy_tank[i].live = 1;
		set_prop_map(enemy_tank[i].x, enemy_tank[i].y, 100+i);
		enemy_bullet[i].status = 0;

	}

	//前3辆坦克闪亮登场
	tank_walk(&enemy_tank[0], DOWN, &enemy_tank_img[DOWN], 0);
	tank_walk(&enemy_tank[1], DOWN, &enemy_tank_img[DOWN], 0);
	tank_walk(&enemy_tank[2], DOWN, &enemy_tank_img[DOWN], 0);

………………………………………………………………………………………………………………………………………………………………
需要获取更多录制视频、源码、课件、书籍资料、笔记、面试资料等加群:782648055
免费获取~本群提供免费解答,面试指导
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

C语言小火车

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值