EasyXnote5关于批量绘图

专栏:EasyX图形化编程

文章目录

      • 问题引入
    • 绘制画面
    • 批量绘图解释
    • 批量绘图使用

问题引入

 之前的讲解中,我们可以发现创建的窗体在进行动画的显示时会出现闪烁现象,本节课将会一步一步探讨如何解决,可以使以后学习中的动画效果更加流畅。

  • 首先创建窗体,利用之前所学习的内容在窗体上绘制圆形,但这次是利用循环绘制出多个圆形,并且使其在窗体内不断运动,为其赋上随即初始坐标及运动方向,设置不同的运动速度,为将其全部保存在窗体内,可以设置为触碰到窗体即可反弹。

绘制画面

实现思路如下
这次设置窗体颜色为白色。

不要忘记使用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;
}

运行后如图
EasyXnote5关于批量绘图_第1张图片

 然而一个小球在窗体内运动是这样,要使所有小球运动起来,就需要记录每个小球的位置,方向,与绘制静态和绘制单个运动的小球不同,所有的小球都需要不断进行重新绘制,需要重复不断地计算每个小球移动后的坐标,不断记录更新。
因为每个小球有很多变量类型,我们创建出一个结构体记录小球的每个变量特征。

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秒才会绘制一个圆。
EasyXnote5关于批量绘图_第2张图片
函数BeginBatchDraw
在这里插入图片描述

函数BeginBatchDraw可以开启批量绘图模式,开启批量绘图后,所有的绘图操作将不再进入显示缓存区,直到调用了EndBatchDraw函数结束批量绘图,才会将从BeginBatchDraw函数以来的所有绘图操作放入显示缓存区。

添加两个函数进入上述代码,在第二次绘制圆形后开始进行批量绘制,在最后一次绘制圆前加入结束绘制的函数。
运行后如图
EasyXnote5关于批量绘图_第3张图片
可以发现,前两个绘制结束后,就不再进行绘制了,等了4秒后,后边的四个圆形立马就画出来了。
再介绍一个函数FlashBatchDraw();
 BeginBatchDraw函数使用后,就只能等遇到EndBatchDraw函数才将图像传至显示缓存区。太霸道了吧,FlashBatchDraw给了他一巴掌,说,遇到我FlashBatchDraw,就要把在我之前的图像全部传过去,BeginBatchDraw一看是个大佬,顿时服软,就将遇到FlashBatchDraw前捕获的图像全部放了,然而过了FlashBatchDraw,他就继续捕获图像,不让其到达显示缓存区。BeginBatchDraw是坏人,抓图像,不让走,FlashBatchDraw是侠客,放开那些图像,但没抓BeginBatchDraw,EndBatchDraw就是警察,抓了Begin,解救图像。
 将BeginBatchDraw移至第一个绘图后,EndBetchDraw移至最后,在第三个绘图后加入FlashBatchDraw,运行观察效果。
EasyXnote5关于批量绘图_第4张图片
可以发现结果如我们所料。


回到上述画面闪烁的问题
当累计绘制1000个小球后,在将这1000个小球打包装进显示缓存区,只需要在循环前加上BeginBatchDraw函数,在一千个小球初始化后加入FlashBatchDraw函数,在绘制循环结束时,关闭批量绘图即可。
运行结果

由于插入图片大小有要求,所以不能录制太长的片段
代码如下,大家可自行参考

#include 
#include 
#include 
#include 

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;
}

本章结束,希望能给大家带来帮助,谢谢大家

你可能感兴趣的:(Easyx图形化小游戏实现,c语言,笔记,动画)