打砖块游戏

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

绘制砖块与小球

绘制木板,木板用键盘控制

物理引擎,小球的运动以及小球的反射

消除砖块

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

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

基础

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;

}

af07a0ae0cd6

image-20210127103458862

说做就做,我们开始

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

af07a0ae0cd6

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;

}

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

af07a0ae0cd6

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;

}

af07a0ae0cd6

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. 绘制木板,木板用键盘控制

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;

}

Logo

魔乐社区(Modelers.cn) 是一个中立、公益的人工智能社区,提供人工智能工具、模型、数据的托管、展示与应用协同服务,为人工智能开发及爱好者搭建开放的学习交流平台。社区通过理事会方式运作,由全产业链共同建设、共同运营、共同享有,推动国产AI生态繁荣发展。

更多推荐