问题引入
之前的讲解中,我们可以发现创建的窗体在进行动画的显示时会出现闪烁现象,本节课将会一步一步探讨如何解决,可以使以后学习中的动画效果更加流畅。
- 首先创建窗体,利用之前所学习的内容在窗体上绘制圆形,但这次是利用循环绘制出多个圆形,并且使其在窗体内不断运动,为其赋上随即初始坐标及运动方向,设置不同的运动速度,为将其全部保存在窗体内,可以设置为触碰到窗体即可反弹。
绘制画面
实现思路如下
这次设置窗体颜色为白色。
不要忘记使用cleardevice();重新粉刷窗体。
在实现多个小球同时运行前,先看一个小球的情况,速度随机,初始运动方向随机,当碰到窗体边缘时反弹。
代码如下
int main() { initgraph(800, 600); setorigin(400, 300); setaspectratio(1, -1); setbkcolor(WHITE); cleardevice(); srand(time(NULL)); int r = 50; int x = rand() % 700 - 400+50; int y = rand() % 500 - 300+50; int vx = rand() % 3 +9; int vy = rand() % 3 +9; if (x % 2 == 0) { vx = -vx; } if (y % 2 == 0) { vy = -vy; } while (1) { cleardevice(); setfillcolor(BLACK); solidcircle(x, y, r); x += vx; y += vy; Sleep(50); //碰到墙壁反弹 if (x >= 400 - r || x <= -400 + r) { vx = -vx; } if (y >= 300 - r || y <= -300 + r) { vy = -vy; } } closegraph(); return 0; }
运行后如图
然而一个小球在窗体内运动是这样,要使所有小球运动起来,就需要记录每个小球的位置,方向,与绘制静态和绘制单个运动的小球不同,所有的小球都需要不断进行重新绘制,需要重复不断地计算每个小球移动后的坐标,不断记录更新。
因为每个小球有很多变量类型,我们创建出一个结构体记录小球的每个变量特征。
typedef struct { int x; int y; int r; int vx; int vy; COLORREF color; }ball;
创建一个结构体数组,保存每一个小圆的信息
定义小球数量方便更改看效果
#define NUM 100
创建结构体数组,保存每个小球的数据。
ball balls[NUM];
设置循环,为数组内的所有小球设置初始值
for (int i = 0; i < NUM; i++) { int m = -400 + r; int n = 400 + r; balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫 m = -300 + r; n = 300 + r; balls[i].y = rand() %(n-m + 1) + m; m = 5; n = 7; balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间 if (balls[i].x % 2 == 0) { balls[i].vx = -balls[i].vx; } balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间 if (balls[i].y % 2 == 0) { balls[i].vy = -balls[i].vy; } balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256)); }
这里小圆的初始坐标的位置值得一提,如果想要获得m到n的范围,假设m为-1,n为5。 n-m为6,rand()%6的范围是0~5。
rand()%(m+n+1)的范围是0~6,再加上m,就得到了我们想要的范围。
所以想要得到m到n的数据范围,可以写作rand()%(n-m+1)+m;
开始进行绘制,利用循环绘制出所有的小球。在第二个for循环中更新小球的位置,判断每个小球是否撞击窗体,然后再次重新绘制,利用计算机的高速计算,就可以实现多个小球同时运动。
(下图调小手机亮度)
while (1)//不断地重新更新小球 { cleardevice(); for (int i = 0; i < NUM; i++) { setfillcolor(balls[i].color); fillcircle(balls[i].x, balls[i].y, r); } Sleep(40); for (int i = 0; i < NUM; i++) { if (balls[i].y >= 300 - r || balls[i].y <= -300 + r) { balls[i].vy = -balls[i].vy; } if (balls[i].x >= 400 - r || balls[i].x <= -400 + r) { balls[i].vx = -balls[i].vx; } balls[i].x += balls[i].vx; balls[i].y += balls[i].vy; } }
运行代码
批量绘图解释
运行后会发现画面有十分严重的闪烁现象。
原因是什么呢?
我们要了解要使程序在窗体上显示图像,需要进行那些步骤
1,程序将图像放置在显示缓存区中
2,显卡将缓存区的数据绘制到屏幕上
把程序比作快递员,图像就是我们要运送的快递,显示缓存区就是驿站,把快递放在驿站里,等待着显卡将其送给收件人,就是将图像绘制到屏幕上。
在这里1000个小球被看作一千个快递,快递员要一次一次将每个快递放置在驿站,收件人要一个一个取走包裹,快递员要送一千次,收件人要取1000次,在生活中,快递员通常是一次性运送多个快递,收件人也会一次取出多个快递,这样不用跑那么多次节省效率。
那么为什么画面会出现一卡一卡的现象呢,就是快递小哥太累了来不及投送某些快递,也就是程序或显卡因为要处理的太多,无法及时工作。
我们就利用生活中一次性送多个包裹,一次性接受多个包裹的方法来解决画面闪烁问题。将100个快递打包为一个大快递,直接运送大快递,显卡只需要从快递柜中取出一个大的合并的包裹就可以了。这样就可以大大提高绘图效率。
因为是将多个数据打包后传输,所以也就称为批量绘图。
然而批量绘图== 有利也有弊==
单次绘图的情况下,只要有一个图像传递过来,就直接将其送至显卡,显示在屏幕上,而批量绘图要等到存至100个画面信息才会将其交给显卡,时效性降低了。但大多数程序对于显示的时效性要求没有那么高,所以这种方法还是可行的。因为批量绘图被广泛的使用在各个场景中。
批量绘图使用
先在一个小的例子中解释
int main() { initgraph(800, 600); setorigin(400, 300); setaspectratio(1, -1); setbkcolor(RED); cleardevice(); setfillcolor(RGB(23, 45, 147)); solidcircle(-320, 0, 40); Sleep(1000); solidcircle(-220, 0, 40); Sleep(1000); solidcircle(-120, 0, 40); Sleep(1000); solidcircle(-20, 0, 40); Sleep(1000); solidcircle(80, 0, 40); Sleep(1000); solidcircle(180, 0, 40); Sleep(1000); getchar(); closegraph(); }
运行后每隔1秒才会绘制一个圆。
函数BeginBatchDraw
函数BeginBatchDraw可以开启批量绘图模式,开启批量绘图后,所有的绘图操作将不再进入显示缓存区,直到调用了EndBatchDraw函数结束批量绘图,才会将从BeginBatchDraw函数以来的所有绘图操作放入显示缓存区。
添加两个函数进入上述代码,在第二次绘制圆形后开始进行批量绘制,在最后一次绘制圆前加入结束绘制的函数。
运行后如图
可以发现,前两个绘制结束后,就不再进行绘制了,等了4秒后,后边的四个圆形立马就画出来了。
再介绍一个函数FlashBatchDraw();
BeginBatchDraw函数使用后,就只能等遇到EndBatchDraw函数才将图像传至显示缓存区。太霸道了吧,FlashBatchDraw给了他一巴掌,说,遇到我FlashBatchDraw,就要把在我之前的图像全部传过去,BeginBatchDraw一看是个大佬,顿时服软,就将遇到FlashBatchDraw前捕获的图像全部放了,然而过了FlashBatchDraw,他就继续捕获图像,不让其到达显示缓存区。BeginBatchDraw是坏人,抓图像,不让走,FlashBatchDraw是侠客,放开那些图像,但没抓BeginBatchDraw,EndBatchDraw就是警察,抓了Begin,解救图像。
将BeginBatchDraw移至第一个绘图后,EndBetchDraw移至最后,在第三个绘图后加入FlashBatchDraw,运行观察效果。
可以发现结果如我们所料。
回到上述画面闪烁的问题
当累计绘制1000个小球后,在将这1000个小球打包装进显示缓存区,只需要在循环前加上BeginBatchDraw函数,在一千个小球初始化后加入FlashBatchDraw函数,在绘制循环结束时,关闭批量绘图即可。
运行结果
由于插入图片大小有要求,所以不能录制太长的片段
代码如下,大家可自行参考
#include <stdio.h> #include <easyx.h> #include <stdlib.h> #include <time.h> typedef struct { int x; int y; int vx; int vy; COLORREF color; }ball; #define NUM 1000 int main() { initgraph(800, 600); setorigin(400, 300); setaspectratio(1, -1); setbkcolor(WHITE); cleardevice(); srand(time(NULL)); int r = 10; ball balls[NUM]; for (int i = 0; i < NUM; i++) { int m = -400 + r; int n = 400 + r; balls[i].x = rand() % (n-m + 1) + m;//圆形的全部都不能超出窗体,为之后的碰撞改变方向做铺垫 m = -300 + r; n = 300 + r; balls[i].y = rand() %(n-m + 1) + m; m = 5; n = 7; balls[i].vx = rand() % (n-m + 1) + m;//速度在5~7之间 if (balls[i].x % 2 == 0) { balls[i].vx = -balls[i].vx; } balls[i].vy = rand() % (n-m + 1) + m;//速度在5~7之间 if (balls[i].y % 2 == 0) { balls[i].vy = -balls[i].vy; } balls[i].color = (RGB(rand() % 256, rand() % 256, rand() % 256)); } BeginBatchDraw(); while (1)//不断地重新绘制更新小球 { cleardevice(); for (int i = 0; i < NUM; i++) { setfillcolor(balls[i].color); fillcircle(balls[i].x, balls[i].y, r); } FlushBatchDraw(); Sleep(40); for (int i = 0; i < NUM; i++) { if (balls[i].y >= 300 - r || balls[i].y <= -300 + r) { balls[i].vy = -balls[i].vy; } if (balls[i].x >= 400 - r || balls[i].x <= -400 + r) { balls[i].vx = -balls[i].vx; } balls[i].x += balls[i].vx; balls[i].y += balls[i].vy; } } EndBatchDraw(); getchar(); closegraph(); return 0; }
本章结束,希望能给大家带来帮助,谢谢大家