打砖块游戏

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

绘制砖块与小球

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

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

消除砖块

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

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

基础

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

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;

}

c语言 打砖块,打砖块相关推荐

  1. 【C语言】反弹球消砖块游戏

    目录 0. 前期准备 1. 打印小球 2. 显示边界 3. 显示挡板 Tip one Tip two 4. 控制挡板

  2. C语言——反弹球消砖块

    目录 前言 一.小球代码的重构 函数 二. 实现游戏的边框 三.显示移动挡板 四.反弹小球 五.消除砖块 六.玩家输入 getchar() kbhit() 七.结尾--最后实现的代码 #define ...

  3. 前端JavaScript——打砖块小游戏

    游戏效果图所示: 打砖块小游戏实现的步骤: 1.搭建整体的三个盒子:  最外面的大盒子,游戏屏幕盒子,游戏信息盒子 2.右侧信息的初始化(搭建好结构,以及样式) 3.游戏屏幕盒子中   未加JavaS ...

  4. 打砖块小游戏php程序,利用原生js实现html5打砖块小游戏(代码示例)

    本篇文章给大家通过代码示例介绍一下利用原生js实现html5打砖块小游戏的方法.有一定的参考价值,有需要的朋友可以参考一下,希望对大家有所帮助. 前言 PS:本次项目中使用了大量 es6 语法,故对于 ...

  5. 少儿编程150讲轻松学Scratch(八)-如何设计经典游戏打砖块

    前言 打砖块游戏很多人都玩过,没玩过的孩子可以自己设计一个这样的游戏来动手尝试一下.打砖块游戏其实比较简单,用Scratch就能来实现,这也是少儿编程课程中的经典游戏案例之一.具体该如何使用Scrat ...

  6. 【scratch】class_3_打砖块小游戏

    一.游戏说明 实现一个打砖块的小游戏,基本元素包括小球,挡板,砖块,终点线,分数,背景自选. 二.需求分析 1. 小球 小球碰到挡板和墙壁时都要反弹 小球碰到砖块时砖块消失 小球碰到终点线时游戏结束 ...

  7. 用JAVA实现一个简易的打砖块小游戏

    概述 利用java实现打砖块小游戏,游戏界面包括以下元素 顶部的各色砖块 底部的挡板 小球 游戏信息展示面板 玩法包括 使用鼠标或者键盘移动挡板 打掉砖块,得分 每一局有3次机会,挡板没有接到小球,机 ...

  8. 打砖块教学(初学:5)

    首先制作所需要的UI界面: 在Hierarchy属性框内创建 3D Object 物体 并重命名为BackGround,调节位置使其充满摄像机的全部视角. Project->Assets-> ...

  9. 原生js做h5小游戏之打砖块

    前言 首先,先说明一下做这个系列的目的:其实主要源于博主希望熟练使用 canvas 的相关 api ,同时对小游戏的实现逻辑比较感兴趣,所以希望通过这一系列的小游戏来提升自身编程能力:关于 es6 语 ...

最新文章

  1. pandas计算滑动窗口中的数值总和实战(Rolling Sum of a Pandas Column):计算单数据列滑动窗口中的数值总和(sum)、计算多数据列滑动窗口中的数值总和(sum)
  2. 该功能仅支持Android5.0,Android 5.0 android:elevation适用于View,但不适用于Button?
  3. c#书写规范之---代码书写规范
  4. 使用 Python 获取 Linux 系统信息的代码
  5. Confluence 6 匿名用户
  6. 如何选择普通索引和唯一索引?|CSDN博文精选
  7. jsp 连接sql数据库查询(源代码)
  8. android studio outline,AndroidStudio常用设置
  9. 黑苹果电池电量补丁_[指南] 怎么给DSDT打补丁来显示电池状态
  10. windows 修改MySQL默认3306端口
  11. 内存碎片与malloc(转)
  12. 13.0.高等数学3-空间曲线
  13. p2p网络中的NAT穿透
  14. android恶意积分墙代码,传统积分墙的忧虑:微信积分墙刷榜优化分析
  15. js实现简单的点名器随机色
  16. 相机镜头选择:相机焦距、视场角和景深(可视距离)之间的关系
  17. LPC1768/1769之CAN控制器概述(附库函数下载地址)
  18. Spark SQL中StructField和StructType
  19. [python]百度AI接口实现人像抠图换背景色
  20. 菜鸡爬虫入门——爬取图片

热门文章

  1. R语言ggplot2可视化增加坐标轴的刻度数实战:自定义坐标轴刻度粒度、增加坐标轴刻度的粒度、更加精细地表达
  2. R语言数值累加函数cumsum实战
  3. R可视化绘制指数分布(Exponential Distribution)
  4. 编译+构建+链接+运行之间的关系分析
  5. c语言中将数组设为空,求助~~ 如何把数组变成动态输入的?
  6. java矩形碰撞检测_旋转矩形的Java碰撞检测?
  7. DNA sequencing technologies: 2006–2016
  8. Linux下面如何运行.sh文件
  9. 查看依赖树_Python中的依赖关系处理
  10. nignx uwsgi 启动多个flask网站