打砖块游戏
问题分析: 需要做哪些模块
- 绘制砖块与小球
- 绘制木板,木板用键盘控制
- 物理引擎,小球的运动以及小球的反射
- 消除砖块
回顾一下图形绘制的基础知识
/***********************
基础
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;
}
说做就做,我们开始
首先要绘制地图,这个砖块地图,我们用不同的数字代表不同的颜色表示,这个横纵排列就可以用一个int类型二维的数组来表示了(5行8列) int m[5][8]
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;
}
我们运行一下,生成地图,并且每次生成地图颜色不一样。
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;
}
球我们绘制出来了,移动的过程再继续做
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);
}
报告需求——回答以下问题
- 总结结构体类型的声明方式,以及该结构体类型变量的初始化方式
- 构造函数的参数、返回值分别是什么?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;
}