项目经理带你 - 零基础手写坦克大战

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/

  1. 安装

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

………………………………………………………………………………………………………………………………………………………………
需要更多,欢迎进群!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章