打砖块

打砖块游戏

问题分析: 需要做哪些模块

  1. 绘制砖块与小球
  2. 绘制木板,木板用键盘控制
  3. 物理引擎,小球的运动以及小球的反射
  4. 消除砖块

回顾一下图形绘制的基础知识

/***********************
基础
    1.创建窗口
    2.基本绘图函数
        2.1 颜色设置
        2.2 画填充矩形和圆
***************/

#include
int main()
{
    //1.创建一个窗口 initgraph(int w, int h);
    initgraph(800,800); //创建一个 800*800的窗口

    // 创建窗口,关闭窗口,但是程序是顺序执行,如果没有while死循环,程序就直接一闪过去了
    while(1);

    //坐标系 横向X轴,纵向Y轴

    //矩形,左上角坐标画到右下角坐标
    //画圆,圆心坐标x,y 和半径 R

    //2.关闭窗口
    closegraph();
    return 0;
}

基本绘图函数

    //基本绘图函数
    //1.画填充矩形 fillrectangle(intx ,int y, int xx, int yy);
    fillrectangle(100,100,200,200);
    //填充圆
    fillcircle(400,400,10);

设置填充颜色

    setfillcolor(BLUE);//颜色宏,相应的英文单词大写
    fillrectangle(100,100,200,200);

RGB模式设置颜色

    setfillcolor(RGB(128,0,255));
    fillcircle(400,400,50);

运行试一下

#include
int main()
{
    //1.创建一个窗口 initgraph(int w, int h);
    initgraph(800,800); //创建一个 800*800的窗口


    //基本绘图函数
    //1.画填充矩形 fillrectangle(intx ,int y, int xx, int yy);
    

    //颜色的表示方式

    setfillcolor(BLUE);//颜色宏,相应的英文单词大写
    fillrectangle(100,100,200,200);

    setfillcolor(RGB(128,0,255));
    fillcircle(400,400,50);

    while(1);//停在这里


    closegraph();
    return 0;
}
image-20210127103458862

说做就做,我们开始

首先要绘制地图,这个砖块地图,我们用不同的数字代表不同的颜色表示,这个横纵排列就可以用一个int类型二维的数组来表示了(5行8列) int m[5][8]

image-20210127103939565

1.绘制砖块地图

#include
//画砖块地图
int map[5][8]; //描述整个地图
//用1-3 给数组初始化赋值

1.1随机数知识

地图的初始化,当然最好是随机赋值了,所以需要封装一个函数 initMap()来初始化二维数组,并使用随机功能

srand((unsigned int)time(0));//设置随机数的范围跟随时间改变而改变
    //srand(100);// 100-MAX_INT 这类方式每次生成的数据是一样的
    int num=rand();//随机产生一个正整数
    //如何确定范围
    //rand()%x [0,x-1]
    //产生1到3的随机数 rand()%3+1

1.2初始化二维数组的initmap()函数封装

initmap()函数

#include
#include
//画砖块地图
int map[5][8]; //描述整个地图
void initMap()
{
    //给二维数组赋初值,嵌套for循环 i行j列
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
            map[i][j]=rand()%3+1;
        }
    }
    
}
int main()
{
    return 0;
}

1.3根据随机二维数组的数字1,2,3标记填充不同颜色的砖块

然后根据二维数组的值绘制不同颜色的砖块 封装函数 drawMap(), 根据不同的map[][]数组的值通过switch选择判断绘制颜色。同时由于绘制砖块 是一个几何叠加的过程,需要坐标参数x和y

假设砖块是100*25,那么i行j列方块的左上角坐标是(100×j,25×i),右下角坐标相对加100,25即可

void drawMap()
{
    setlinestyle(PS_SOLID,2,0); //设置砖块边框线型
    setlinecolor(WHITE);//设置砖块边框颜色
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
           int x=100*j;//左上角x坐标
           int y=25*i;//左上角y坐标
            switch(map[i][j])
            {
                case 0: //设定一个0值,留下做方块的消除
                    break;
                case 1:
                    setfillcolor(YELLOW);
                    fillrectangle(x,y,x+100,y+25); //从x,y坐标绘制填充砖块到相对坐标
                    break;
                case 2:
                    setfillcolor(LIGHTBLUE);
                    fillrectangle(x,y,x+100,y+25);//代码复用
                    break;
                case 3:
                    setfillcolor(GREEN);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                    
            }
        }
    }
}

1.4组合代码调试一下

现在我们组合起来

#include
#include
//画砖块地图
int map[5][8]; //描述整个地图
void initMap()
{
    //给二维数组赋初值,嵌套for循环 i行j列
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
            map[i][j]=rand()%3+1;
        }
    }
    
}
void drawMap()
{
    setlinestyle(PS_SOLID,2,0);
    setlinecolor(WHITE);
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
           int x=100*j;//左上角x坐标
           int y=25*i;//左上角y坐标
            switch(map[i][j])
            {
                case 0: //设定一个0值,留下做方块的消除
                    break;
                case 1:
                    setfillcolor(YELLOW);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 2:
                    setfillcolor(LIGHTBLUE);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 3:
                    setfillcolor(GREEN);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                    
            }
        }
    }
}

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    drawMap();//根据map数组来绘制砖块

    while(1);

    closegraph();
    return 0;
}

我们运行一下,生成地图,并且每次生成地图颜色不一样。

image-20210127123918901

2.绘制木板行为

分析:因为木板需要移动,所以木板的坐标,木板的大小,颜色,移动速度等变量,我们可以用结构体统一来描述木板

2.1定义木板的属性,声明一个Board类型的 结构体类型

struct Board
{
    int x;
    int y;
    int speed;
    COLORREF color;
    int width;
    int height;
};

2.2用返回结构体Board类型的结构体变量的地址的函数,来初始化木板

以上思路是参考面向对象的属性初始化方式,C语言没有对象概念,但是可以模仿一下

struct Board * createBoard(int x,int y,int speed, COLORREF color, int width, int height)
{
    //申请一个结构体变量,就要申请内存,用指针接受malloc()函数申请到的内存空间
    struct Board *pBoard=(struct Board*)malloc(sizeof(struct Board));
    //一个Board结构体类型的指针变量pBoard接受一个空间地址,这个空间地址大小是结构体Board类型的大小,这个void类型的地址要并且强制类型转换为 结构体Board类型的地址。
    pBoard->x=x; //通过结构体指针 加上运算符->访问成员变量
    pBoard->y=y;
    pBoard->speed=speed;
    pBoard->color=color;
    (*pBoard).width=width;//通过结构体变量 加上成员运算符. 访问成员变量 各取所需
    (*pBoard).height=height;
    return pBoard;
    
}

主函数里面调用一下 我们创建木板

2.3主函数创建木板结构体变量

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    drawMap();//根据map数组来绘制砖块

    while(1);

    closegraph();
    return 0;
}

运行一下,并没有木板出来。是因为我们创建的只是木板这个变量,并没有在Map上绘制

并且,木板是要移动的,所以木板的绘制需要一个函数封装进行移动控制

2.4木板创建drawBoard()

void drawBoard(struct Board *pBoard) //传递木板结构体变量指针
{
    setfillcolor(pBoard->color);//封装的函数绘制,尽量传递进入变量
    fillrectangle(pBoard->x,pBoard->y,pBoard->x+pBoard->width,pBoard->y+pBoard->height); //从坐标x,y绘制到 x+宽度,y+高度坐标
}

组合起来,并且main函数调用drawBoard()函数并且传递进入pBoard指针

#include
#include
//画砖块地图
int map[5][8]; //描述整个地图
void initMap()
{
    //给二维数组赋初值,嵌套for循环 i行j列
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
            map[i][j]=rand()%3+1;
        }
    }
    
}
void drawMap()
{
    setlinestyle(PS_SOLID,2,0);
    setlinecolor(WHITE);
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
           int x=100*j;//左上角x坐标
           int y=25*i;//左上角y坐标
            switch(map[i][j])
            {
                case 0: //设定一个0值,留下做方块的消除
                    break;
                case 1:
                    setfillcolor(YELLOW);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 2:
                    setfillcolor(LIGHTBLUE);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 3:
                    setfillcolor(GREEN);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                    
            }
        }
    }
}
//绘制木板 
struct Board
{
    int x;
    int y;
    int speed;
    COLORREF color;
    int width;
    int height;
};
struct Board * createBoard(int x,int y,int speed, COLORREF color, int width, int height)
{
    //申请一个结构体变量,就要申请内存,用指针接受malloc()函数申请到的内存空间
    struct Board *pBoard=(struct Board*)malloc(sizeof(struct Board));
    //一个Board结构体类型的指针变量pBoard接受一个空间地址,这个空间地址大小是结构体Board类型的大小,这个void类型的地址要并且强制类型转换为 结构体Board类型的地址。
    pBoard->x=x; //通过结构体指针 加上运算符->访问成员变量
    pBoard->y=y;
    pBoard->speed=speed;
    pBoard->color=color;
    (*pBoard).width=width;//通过结构体变量 加上成员运算符. 访问成员变量 各取所需
    (*pBoard).height=height;
    return pBoard;
    
}
void drawBoard(struct Board *pBoard) //传递木板结构体变量指针
{
    setfillcolor(pBoard->color);//封装的函数绘制,尽量传递进入变量
    fillrectangle(pBoard->x,pBoard->y,pBoard->x+pBoard->width,pBoard->y+pBoard->height); //从坐标x,y绘制到 x+宽度,y+高度坐标
}
int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    drawMap();//根据map数组来绘制砖块

    drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板
    while(1);
    closegraph();
    return 0;
}

我们在主函数里面,定义木板的属性,坐标,颜色,移动速度及几何大小

如果不适应的同学,可以不用struct Board * createBoard()函数,全部注释掉

简单的方式,是用全局变量初始化来取代

//通过全局变量初始化
struct Board
{
    int x;
    int y;
    int speed;
    COLORREF color;
    int width;
    int height;
};
struct Board board={300,800-25,1,WHITE,200,25};

当然 main函数也要修改,传递board的地址

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    drawMap();//根据map数组来绘制砖块
    drawBoard(&board); //传入board变量的地址
    while(1);
    closegraph();
    return 0;
}

我们更推荐使用函数的方式,方便进行属性修改,而不是全局变量造成太多影响

我们回顾一下第一个中创建木板的方式,首先全局变量声明一个 Board的结构体类型,然后通过一个返回结构体变量的指针的函数,然后返回的这个指针pBoard传递给绘制木板的函数来绘制出木板,一切实例化的过程是在main函数的调用过程中,传递结构体变量成员的值来实现。

2.5木板移动

我们现在要开始控制木板移动,因为移动的过程中是不断的重新绘制新的地图和木板,所以我们把drawMap()和drawBoard()函数的调用,移入while(1)死循环中,main()函数的改动如下

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块

    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板
    }
    closegraph();
    return 0;
}

木板的按键操作需要封装函数为 keyDown()

//木板的按键操作
void keyDown(struct Board *pBoard)
{
    // C语言的基本函数: scanf(),getch(),getchar(),gets()为主筛函数,先后顺序,不太实用
    //游戏是实时的,我们需要实现异步的按键操作(类似边刷牙边打电话)
    if(GetAsyncKeyState('A')||GetAsyncKeyState(VK_LEFT))
    {
        //按左键,x变小,做减运算
        pBoard->x=pBoard->x-pBoard->speed;
        
    }
    if(GetAsyncKeyState('D')||GetAsyncKeyState(VK_RIGHT))
    {
        pBoard->x=pBoard->x+pBoard->speed;
        
    }
}

main()函数调用一下键盘移动

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块

    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板
        keyDown(pBoard);
    }
    closegraph();
    return 0;
}

我们试运行一下,会发现屏幕会一直闪烁..

这是一个异步带来的副作用,我们可以通过双缓冲,在内存中绘制好之后,再移植到屏幕

具体实现通过 BeginBatchDraw();while循环中不断丢弃FlushBatchDraw();循环结束后结束双缓存机制EndBatchDraw();

main()函数改动如下

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块

    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板
        keyDown(pBoard);
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}

现在我们没有闪屏行为了,左右键一定木板移动了,但是这个移动行为,发现他居然会溢出

这就是常见的一个没有边界条件的Bug,要限制当x坐标在0到800-200内移动

在键盘的if逻辑语句更新为

void keyDown(struct Board *pBoard)
{
    // C语言的基本函数: scanf(),getch(),getchar(),gets()为主筛函数,先后顺序,不太实用
    //游戏是实时的,我们需要实现异步的按键操作(类似边刷牙边打电话)
    if((GetAsyncKeyState('A')||GetAsyncKeyState(VK_LEFT))&&pBoard->x>=0)//限制边界在大于0,注意逻辑语句我增加了一个 (),不然只有方向键收到限制
    {
        //按左键,x变小,做减运算
        pBoard->x=pBoard->x-pBoard->speed;
        
    }
    if((GetAsyncKeyState('D')||GetAsyncKeyState(VK_RIGHT))&&pBoard->x<=800-200)//限制边界小于800-200
    {
        pBoard->x=pBoard->x+pBoard->speed;
        
    }
}

调试一下,不会溢出边框了

3.小球绘制及行为模块

分析:

1.反射

2.撞击木板,进行范围判断

3.撞击砖块,进行条件判断

3.1创建球 ①声明类型struct Ball{}; ②创建球函数 struct Ball *createBall(属性){return pBall;}③绘制球drawBall(struct Ball *pBall)

//球
//1.反射

//2.撞击木板,进行范围判断

//3.撞击砖块,进行条件判断
struct Ball
{
    int x;
    int y;
    int r;//半径
    int dx;//x增量
    int dy;
    COLORREF color;
};
//下面的创建函数对着模仿
struct Ball *createBall(int x,int y,int r, int dx, int dy, COLORREF color)
{
    struct Ball *pBall=(struct Ball*)malloc(sizeof(struct Ball));
    pBall->x=x;
    pBall->y=y;
    pBall->r=r;
    pBall->dx=dx;
    pBall->dy=dy;
    pBall->color=color;
    return pBall;
}
//画球
void drawBall(struct Ball *pBall)
{
    setfillcolor(pBall->color);
    solidcircle(pBall->x,pBall->y,pBall->r);
}

main()函数里面,同样的创建出这个球来

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    //同样的我们创建球与绘制球
    struct Ball *pBall=createBall(400,400,15,5,-5,RED); //球放在中间,大小为15,通过控制dx,dy作为运动矢量来控制球的初始化运动方向
    
    //相同的代码我们放一起方便观察

    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板

        drawBall(pBall); //画出球来

        keyDown(pBoard);
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}
image-20210127214718053

球我们绘制出来了,移动的过程再继续做

3.2移动球

void moveBall(struct Ball *pBall)

//先不考虑反射,简单学习入手
void moveBall(struct Ball *pBall)
{
    pBall->x+=pBall->dx;
    pBall->y+=pBall->dy; // 移动,注意斜着向上运动,x增加,y减小
}

主函数的while(1)循环中调用moveBall(pBall)函数

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    //同样的我们创建球与绘制球
    struct Ball *pBall=createBall(400,400,15,5,-5,RED); //球放在中间,大小为15,通过控制dx,dy作为运动矢量来控制球的初始化运动方向
    //相同的代码我们放一起方便观察
    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板

        drawBall(pBall);
        moveBall(pBall);

        keyDown(pBoard);
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}

试运行一下,速度相当快,电脑运行快的可能都没发现这个过程,我们结合反射的代码,同时写下延时函数Sleep(10); 放在while(1)的死循环体中,根据电脑的速度配置延迟时间

分析一下反射:

主要是增量的变化,exp:斜向上撞击右边墙前,dx为正,dy为负,反射后,dx为负,dy不变仍为负

所以是 dx=-dx

①左右墙相撞反射,dx变号

②上下墙反射,dy变号

开始加入反射过程

void moveBall(struct Ball *pBall)
{
    //增加反射
    //左右碰壁
    if(pBall->x-pBall->r<=0||pBall->x+pBall->r>=800) //记录球的半径就是这个作用
        //左右碰壁,x减去半径小于0或者x加上半径大于800
    {
        pBall->dx=-pBall->dx; //左右墙,dx反射
    }
    //上下碰壁
    if(pBall->y-pBall->r<=0||pBall->y+pBall->r>=800) //x坐标改为y坐标就行了,窗口一样大
    {
        pBall->dy=-pBall->dy; //上下墙,dy反射
    }
    
    pBall->x+=pBall->dx;
    pBall->y+=pBall->dy; // 移动,注意斜着向上运动,x增加,y减小
}

我们调试一下,球的坐标防止的歪一点,从400,400改为400 600,不然永远是45度完美重复反射

AT!现在球撞到地板没有game over,并且与木板没有互动

接下来我们处理撞木板

球的x坐标范围,在木板的左右边界范围上

球的y坐标坐标=木板厚度+球半径 这根线上

3.3撞击木板处理函数 hitBoard()

//撞木板
//需要木板的坐标
int hitBoard(struct Ball *pBall, struct Board *pBoard) //木板和球的参数都需要
{
    if(pBall->y+pBall->r==pBoard->y) //y满足
    {
        if(pBall->x>=pBoard->x&&pBall->x<=pBoard->x+pBoard->width)
            return 1; //表示撞击
    } 
    return 0;
    
    
}

然后我们moveBall的撞下底板的处理就可以更换为 hitBoard()是否撞击到木板的返回值,并且注意到moveBall()的参数只有一个,还需要传递pBoard进来,所以需要增加参数

void moveBall(struct Ball *pBall,struct Board *pBoard)
{
    //增加反射
    //左右碰壁
    if(pBall->x-pBall->r<=0||pBall->x+pBall->r>=800) //记录球的半径就是这个作用
        //左右碰壁,x减去半径小于0或者x加上半径大于800
    {
        pBall->dx=-pBall->dx; //左右墙,dx反射
    }
    //上下碰壁
    if(pBall->y-pBall->r<=0||hitBoard(pBall,pBoard)) //x坐标改为y坐标就行了,窗口一样大
    {
        pBall->dy=-pBall->dy; //上下墙,dy反射
    }
    
    pBall->x+=pBall->dx;
    pBall->y+=pBall->dy; // 移动,注意斜着向上运动,x增加,y减小
}

这两个函数都有pBoard参数,所以放置位置要在pBoard的结构体之后

main()函数修改如下

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    //同样的我们创建球与绘制球
    struct Ball *pBall=createBall(400,600,15,5,-5,RED); //球放在中间,大小为15,通过控制dx,dy作为运动矢量来控制球的初始化运动方向
    //相同的代码我们放一起方便观察
    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板

        drawBall(pBall);
        moveBall(pBall,pBoard);
        Sleep(10);

        keyDown(pBoard);
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}

调试一下发现,碰见木板确实反射,碰到底部就没了,增加一个撞地板游戏结束

int gameOver(struct Ball *pBall)
{
    if(pBall->y>800-25)
    {return 1;} 
   return 0;
}

然后我们用windows弹出一个窗口,需要一个句柄变量存储一下这个窗口,在全局变量的地方增加

int map[5][8];//描述整个地图
HWND hwnd=NULL;

然后在main()函数里面调用 gameOver的返回值,然后调出MessageBox()

int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,1,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    //同样的我们创建球与绘制球
    struct Ball *pBall=createBall(400,600,15,5,-5,RED); //球放在中间,大小为15,通过控制dx,dy作为运动矢量来控制球的初始化运动方向
    //相同的代码我们放一起方便观察
    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板

        drawBall(pBall);
        moveBall(pBall,pBoard);
        Sleep(10);

        keyDown(pBoard);
        if(gameOver(pBall))
        {
            MessageBox(hwnd,"游戏结束","game over",MB_OK);
            exit(0); //全部退出
        }
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}

3.4砖块消失

分析一下:我们回头看看绘制砖块的相关代码中的坐标

int x=100*j;//左上角x坐标
int y=25*i;//左上角y坐标

反过来,我们可以用 j=x/100,(x整除100),i=y/25,来判断球的坐标是否在5行8列

并且是否有砖块才做反射,反射之后需要做什么操作?

map[i][j]!=0的时候才做反射
int hitBricks(struct Ball *pBall)
{
    //1.算出球的行和列是属于地图的
    int ballJ=pBall->x/100;
    int ballI=(pBall->y-pBall->r)/25;// 考虑半径
    //2.当前下标下,数组中油不等于0的砖块需要反射,并且重置为0
    if((ballJ<8&&ballI<5)&&map[ballI][ballJ]!=0) //在5行8列里面,并且有砖块
        //
    {
        map[ballI][ballJ]=0;//重置为0
        return 1;//返回一个反射的指令
    }
    return 0;
}

然后撞击砖块是在moveBall的上下反射里面判断,增加这个函数调用

void moveBall(struct Ball *pBall,struct Board *pBoard)
{
    //增加反射
    //左右碰壁
    if(pBall->x-pBall->r<=0||pBall->x+pBall->r>=800) //记录球的半径就是这个作用
        //左右碰壁,x减去半径小于0或者x加上半径大于800
    {
        pBall->dx=-pBall->dx; //左右墙,dx反射
    }
    //上下碰壁
    if(pBall->y-pBall->r<=0||hitBoard(pBall,pBoard)||hitBricks(pBall)) //x坐标改为y坐标就行了,窗口一样大 。增加碰砖块消除
    {
        pBall->dy=-pBall->dy; //上下墙,dy反射
    }
    
    pBall->x+=pBall->dx;
    pBall->y+=pBall->dy; // 移动,注意斜着向上运动,x增加,y减小
}

整体运行一下,如果Board的移动速度太慢,调整一下

调试成功!

只剩下胜利的判断,游戏结束的收尾

int gameWin()//全局变量map[][]不需要传递参数进来
{
    for(int i=0;i<5;i++)
        for(int j=0;j<8;j++)
        {
            if(map[i][j]!=0)//只要有砖块就不判断为胜利
            {
                return 0;
            }
        }
    return 1;
}

main()函数里面照抄一下句柄窗口弹出


if(gameOver(pBall))
        {
            MessageBox(hwnd,"游戏结束","game over",MB_OK);
            exit(0);
        }
        if(gameWin())
        {
            MessageBox(hwnd,"游戏结束","game win",MB_OK);
            exit(0);

        }

报告需求——回答以下问题

  1. 总结结构体类型的声明方式,以及该结构体类型变量的初始化方式
  2. 构造函数的参数、返回值分别是什么?main函数如何调用实现木板的绘制?
    3.指针变量在绘制木板、球的函数、以及判断移动、撞击的函数中,是如何传递数据的? 请以判断球撞击砖块函数为例,将指针变量改写为结构体变量的形式,并给出该函数的完整定义,以及调用方式。

整体代码

/**************************
问题分析: 需要做哪些模块
1.  绘制砖块与小球
2.  绘制木板,木板用键盘控制
3.  物理引擎,小球的反射
4.  消除砖块


**********************/

/***********************
基础
    1.创建窗口
    2.基本绘图函数
        2.1 颜色设置
        2.2 画填充矩形和圆
***************/
#include
#include
//画砖块地图
int map[5][8]; //描述整个地图
HWND hwnd=NULL;
void initMap()
{
    //给二维数组赋初值,嵌套for循环 i行j列
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
            map[i][j]=rand()%3+1;
        }
    }
    
}
int gameWin()//全局变量map[][]不需要传递参数进来
{
    for(int i=0;i<5;i++)
        for(int j=0;j<8;j++)
        {
            if(map[i][j]!=0)//只要有砖块就不判断为胜利
            {
                return 0;
            }
        }
    return 1;
}
void drawMap()
{
    setlinestyle(PS_SOLID,2,0);
    setlinecolor(WHITE);
    for(int i=0;i<5;i++)
    {
        for(int j=0;j<8;j++)
        {
           int x=100*j;//左上角x坐标
           int y=25*i;//左上角y坐标
            switch(map[i][j])
            {
                case 0: //设定一个0值,留下做方块的消除
                    break;
                case 1:
                    setfillcolor(YELLOW);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 2:
                    setfillcolor(LIGHTBLUE);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                case 3:
                    setfillcolor(GREEN);
                    fillrectangle(x,y,x+100,y+25);
                    break;
                    
            }
        }
    }
}
//球
//1.反射

//2.撞击木板,进行范围判断

//3.撞击砖块,进行条件判断
struct Ball
{
    int x;
    int y;
    int r;//半径
    int dx;//x增量
    int dy;
    COLORREF color;
};
//下面的创建函数对着模仿
struct Ball *createBall(int x,int y,int r, int dx, int dy, COLORREF color)
{
    struct Ball *pBall=(struct Ball*)malloc(sizeof(struct Ball));
    pBall->x=x;
    pBall->y=y;
    pBall->r=r;
    pBall->dx=dx;
    pBall->dy=dy;
    pBall->color=color;
    return pBall;
    
}
void drawBall(struct Ball *pBall)
{
    setfillcolor(pBall->color);
    solidcircle(pBall->x,pBall->y,pBall->r);
}

//绘制木板 
struct Board
{
    int x;
    int y;
    int speed;
    COLORREF color;
    int width;
    int height;
};
struct Board * createBoard(int x,int y,int speed, COLORREF color, int width, int height)
{
    //申请一个结构体变量,就要申请内存,用指针接受malloc()函数申请到的内存空间
    struct Board *pBoard=(struct Board*)malloc(sizeof(struct Board));
    //一个Board结构体类型的指针变量pBoard接受一个空间地址,这个空间地址大小是结构体Board类型的大小,这个void类型的地址要并且强制类型转换为 结构体Board类型的地址。
    pBoard->x=x; //通过结构体指针 加上运算符->访问成员变量
    pBoard->y=y;
    pBoard->speed=speed;
    pBoard->color=color;
    (*pBoard).width=width;//通过结构体变量 加上成员运算符. 访问成员变量 各取所需
    (*pBoard).height=height;
    return pBoard;
    
}
int hitBricks(struct Ball *pBall)
{
    //1.算出球的行和列是属于地图的
    int ballJ=pBall->x/100;
    int ballI=(pBall->y-pBall->r)/25;// 考虑半径
    //2.当前下标下,数组中油不等于0的砖块需要反射,并且重置为0
    if((ballJ<8&&ballI<5)&&map[ballI][ballJ]!=0) //在5行8列里面,并且有砖块
        //
    {
        map[ballI][ballJ]=0;//重置为0
        return 1;//返回一个反射的指令
    }
    return 0;
}
//木板的按键操作
//木板的按键操作
void keyDown(struct Board *pBoard)
{
    // C语言的基本函数: scanf(),getch(),getchar(),gets()为主筛函数,先后顺序,不太实用
    //游戏是实时的,我们需要实现异步的按键操作(类似边刷牙边打电话)
    if((GetAsyncKeyState('A')||GetAsyncKeyState(VK_LEFT))&&pBoard->x>=0)//限制边界在大于0
    {
        //按左键,x变小,做减运算
        pBoard->x=pBoard->x-pBoard->speed;
        
    }
    if((GetAsyncKeyState('D')||GetAsyncKeyState(VK_RIGHT))&&pBoard->x<=800-200)//限制边界小于800-200
    {
        pBoard->x=pBoard->x+pBoard->speed;
        
    }
}
void drawBoard(struct Board *pBoard) //传递木板结构体变量指针
{
    setfillcolor(pBoard->color);//封装的函数绘制,尽量传递进入变量
    fillrectangle(pBoard->x,pBoard->y,pBoard->x+pBoard->width,pBoard->y+pBoard->height); //从坐标x,y绘制到 x+宽度,y+高度坐标
}
//先不考虑反射,简单学习入手
int hitBoard(struct Ball *pBall, struct Board *pBoard) //木板和球的参数都需要
{
    if(pBall->y+pBall->r==pBoard->y) //y满足
    {
        if(pBall->x>=pBoard->x&&pBall->x<=pBoard->x+pBoard->width)
            return 1; //表示撞击
    } 
    return 0;
    
    
}
void moveBall(struct Ball *pBall,struct Board *pBoard)
{
    //增加反射
    //左右碰壁
    if(pBall->x-pBall->r<=0||pBall->x+pBall->r>=800) //记录球的半径就是这个作用
        //左右碰壁,x减去半径小于0或者x加上半径大于800
    {
        pBall->dx=-pBall->dx; //左右墙,dx反射
    }
    //上下碰壁
    if(pBall->y-pBall->r<=0||hitBoard(pBall,pBoard)||hitBricks(pBall)) //x坐标改为y坐标就行了,窗口一样大
    {//增加碰砖块消除
        pBall->dy=-pBall->dy; //上下墙,dy反射
    }
    
    pBall->x+=pBall->dx;
    pBall->y+=pBall->dy; // 移动,注意斜着向上运动,x增加,y减小
}
int gameOver(struct Ball *pBall)
{
    if(pBall->y>800-25)
    {return 1;} 
   return 0;
}
int main()
{
    srand((unsigned int)time(0));//设置随机数种子
    initgraph(800,800);  //绘制界面,8行8列,每个砖块是100,所以横向至少800。
    initMap(); //调用初始化二维数组
    struct Board *pBoard=createBoard(100+400/2,800-25,5,WHITE,200,25); //木板初始坐标计算,在底部中心,速度为1,白色,200×25的方块
    //同样的我们创建球与绘制球
    struct Ball *pBall=createBall(400,600,15,5,-5,RED); //球放在中间,大小为15,通过控制dx,dy作为运动矢量来控制球的初始化运动方向
    //相同的代码我们放一起方便观察
    BeginBatchDraw(); //内存中绘制
    while(1)
    {
        cleardevice();
        drawMap();//根据map数组来绘制砖块
        drawBoard(pBoard); //传入 pBoard结构体指针绘制一下木板

        drawBall(pBall);
        moveBall(pBall,pBoard);
        Sleep(10);

        keyDown(pBoard);
        if(gameOver(pBall))
        {
            MessageBox(hwnd,"游戏结束","game over",MB_OK);
            exit(0);
        }
        if(gameWin())
        {
            MessageBox(hwnd,"游戏结束","game win",MB_OK);
            exit(0);

        }
        FlushBatchDraw(); //画一帧,丢弃一帧
    }
    EndBatchDraw();//结束
    closegraph();
    return 0;
}

你可能感兴趣的:(打砖块)