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.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);
………………………………………………………………………………………………………………………………………………………………
需要更多,欢迎进群!
来源:51CTO
作者:wx5de7b5143d243
链接:https://blog.51cto.com/14632565/2464722