多物体运动+图片+帧率控制+消息处理
- 一. 多物体运动
- 1.如何合理的管理小球:
- 2.初始化每一个球的信息:
- 3.绘制多物体的运动:
- 4.尝试添加小球个数:
- num==10:![请添加图片描述](https://img-blog.csdnimg.cn/ce058e08c11c4410a7d09d25ca9275b3.png)
- num==100:
- num==1000:
- #define num 100000
- 5.批量绘图:
- 二.图片
- 1.图片格式
- 2.IMAGE对象
- 3.加载图片到image中
- 4.图片的输出:
- 三.消息处理
- 1.基本概念:
- 2.获取消息
- 3.ExMessage结构体:
- 4.使用和操作:
一. 多物体运动
我们之前了解过有关弹球小游戏中多个小球在窗体中运动反弹,现在,我们不满足于只有一个小球在窗体内运动了。我们希望,有更多的小球可以在窗体内运动。初始状态下,它们随机分布在窗体内。接下来,按照各自的速度与方向进行运动。此外,各小球的颜色也不尽相同。为了实现这样的效果,需要分别记录每一个小球的以下数据:
1.如何合理的管理小球:
1.定义一个结构体,存储小球的消息,有x,y坐标,水平竖直的速度,和小球的颜色。
2.#define num 100 作为我们小球数量。
3.#define R 20 作为我们小球半径
#define num 100
#define R 10![请添加图片描述](https://img-blog.csdnimg.cn/9f137a006d0243389764067aaa977561.png)
typedef struct ball {
int x;
int y;
int vx;
int vy;
}Ba;
2.初始化每一个球的信息:
1.初始化小球生成的坐标范围;
2.根据随机生成的速度和角度计算水平和竖直速度;
3.使用hsv颜色模型生成随机颜色。
void InitBall(Ba* ball)
{
//循环初始化每一个数组内容
for (int i = 0; i < num; i++)
{
//初始化坐标
ball[i].x = (rand() % (800 - 2 * R)) - (400 - R);
ball[i].y = (rand() % (600 - 2 * R)) - (300 - R);
//随机生成实际速度
int V = (rand() % 6) + 3;
//随机生成角度
int angle = rand() % 360;
//确定随机的水平竖直速度
double rad = PI / 180;
ball[i].vx = V * cos(rad * angle);
ball[i].vy = V * sin(rad * angle);
//随机生成颜色
ball[i].color = HSVtoRGB(rand() % 360, 0.8, 0.5);
}
}
3.绘制多物体的运动:
1.cleardevice函数和Sleep函数一个在for循环的前面一个在for循环的后面。
2.如果把Sleep放到绘制小球的后面就会产生一个一个小球绘制然后移动的效果。
3.clear放到for循环的开始绘制一个球就清屏:
void Drawball(Ba* ball)
{
while (1)
{
// 清空画面
cleardevice();
for (int i = 0; i < num; i++)
{
//设置小球填充颜色
setfillcolor(ball[i].color);
// 绘制小球
solidcircle(ball[i].x, ball[i].y, R);
// 碰到或越过上下边界反弹
if (ball[i].y >= 300 - R || ball[i].y <= -300 + R)
{
ball[i].vy = -(ball[i].vy);
}
// 碰到或越过左右边界反弹
if (ball[i].x <= -400 + R || ball[i].x >= 400 - R)
{
ball[i].vx = -(ball[i].vx);
}
ball[i].x += ball[i].vx;
ball[i].y += ball[i].vy;
}
Sleep(40);
}
}
4.尝试添加小球个数:
num==10:
num==100:
num==1000:
#define num 100000
1.我们的球的变量是在栈区开辟的空间,栈的空间是比较有限的那么当我们需要非常多球的时候那么栈区的空间不足够我们去使用所以发生了栈溢出,所以我们需要进行动态开辟数组;
原来的:
int main()
{
initgraph(800, 600);
// 坐标系原点在窗体中心,X轴正方向向右,Y轴正方向向上
setorigin(400, 300);
setaspectratio(1, -1);
// 设置背景色
setbkcolor(RGB(164, 225, 202));
// 使用背景色清空窗体
cleardevice();
//开辟数组
Ba ball[num];
//获取当前时间
srand((unsigned int)time(NULL));
//初始化数数组内容:
InitBall(ball);
//传数组内容,进行小球运动;
Drawball(ball);
closegraph();
return 0;
}
更改之后的:
int main()
{
initgraph(800, 600);
// 坐标系原点在窗体中心,X轴正方向向右,Y轴正方向向上
setorigin(400, 300);
setaspectratio(1, -1);
// 设置背景色
setbkcolor(RGB(164, 225, 202));
// 使用背景色清空窗体
cleardevice();
//开辟数组
Ba* tmp=(Ba*)malloc(sizeof(Ba)*num);
if (tmp == NULL)
{
perror("malloc file\n");
exit(-1);
}
Ba* ball = tmp;
//获取当前时间
srand((unsigned int)time(NULL));
//初始化数数组内容:
InitBall(ball);
//传数组内容,进行小球运动;
Drawball(ball);
getchar()
closegraph();
return 0;
}
5.批量绘图:
1.我们发现绘制的过程中num比较大的时候我们画面闪烁会比较严重?
2.我们的球就是一个一个绘制到屏幕上的球比较少的时候cpu和显卡之间的交互还是比较好的但是当num比较多的时候,cpu出去当前需要绘制的球但是上一个还没有绘制完成,这个时候就有可能会产生闪烁;
3.有什么解决方法呢?
批量绘图:
void Drawball(Ba* ball)
{
//开启批量绘图,之后所有的绘图操作就会累计
BeginBatchDraw();
while (1)
{
// 清空画面
cleardevice();
for (int i = 0; i < num; i++)
{
//设置小球填充颜色
setfillcolor(ball[i].color);
// 绘制小球
solidcircle(ball[i].x, ball[i].y, R);
// 碰到或越过上下边界反弹
if (ball[i].y >= 300 - R || ball[i].y <= -300 + R)
{
ball[i].vy = -(ball[i].vy);
}
// 碰到或越过左右边界反弹
if (ball[i].x <= -400 + R || ball[i].x >= 400 - R)
{
ball[i].vx = -(ball[i].vx);
}
ball[i].x += ball[i].vx;
ball[i].y += ball[i].vy;
}
FlushBatchDraw();
//使累计的绘图操作一下绘制:
Sleep(40);
}
//结束批量绘图:
EndBatchDraw();
}
二.图片
1.图片格式
图片是人对视觉感知的物质再现。图片可以由光学设备获取,如照相机。也可以人为创作,如手工绘画。图片可以保存在纸质介质、胶片等等对光信号敏感的介质上。随着数字采集技术和信号处理理论的发展,越来越多的图像以数字形式存储。因而,有些情况下“图片”一词实际上是指数字图像。对于数字图像而言,有多种不同的方法组织和保存图片。常见的文件格式有有 bmp 、 gif 、 jpg 、 png 等。
Easyx图形库可以支持以下几种图片格式: bmp 、 gif 、 jpg 、 png 、 tif 、 emf 、 wmf 、 ico 。不在支持列表的文件格式无法被图形库读取。
2.IMAGE对象
受EasyX支持的图片格式可以被加载并存储在 IMAGE 对象当中。 对象 是一个C++中的概念,被用于面向对象风格的编程,它由C语言中的结构体升级而来。
3.加载图片到image中
现在,我们为刚刚声明的 IMAGE 对象填充图片信息。可以使用 loadimage 函数加载一个图片文件,如果加载成功,可以把这个图片的信息填充到 IMAGE 对象中。
使用loadimage函数加载图片到我们的image对象中:
第二个参数的类型为 ==LPCTSTR ==,你可能之前没有见过它。它是一个宏,用于适配多字节字符与宽字符。
该宏根据不同的设置,代表着不同的字符指针。
- 多字节字符模式下,它是 const char * 。
- 宽字符模式下,它是 const wchar_t * 。
多字节字符是一种字符长度可变的模式:一个英文字符占用一个 char ,而中文字符将会占用两
个 char 。
宽字符是一种字符长度恒定的模式:在windows系统下,不论英文还是中文字符,均占用2个字节。
目前我们并不想对字符模式展开讨论。暂时,我们使用较为熟悉的多字节字符,即 LPCTSTR 会被看做
为 const char * 。在 Visual Studio 中,默认情况下,将使用宽字符模式。你可以通过以下的办法,将
设置修改为多字节字符。
在【工程属性】选项卡中,将【配置属性】-【高级】-【字符集】调整为未设置即可。
4.图片的输出:
接着调用 putimage 函数,可以把 image 对象中的图片显示在窗体上。
//定义一个Image对象
IMAGE pho;
//加载图片到
loadimage(&pho,"./zw.jpg");
putimage(0,0, &pho);
1.注意putimage函数使用的是逻辑坐标;
2.如果改变x,y的正方向的化会导致加载的图片和原来的图片产生方向的改变。
三.消息处理
1.基本概念:
EasyX中的消息是用户对窗体进行操作而产生的事件的载体。简单地来说,用户在窗体上进行操作,将会产生消息。例如:用户在窗体上操作鼠标、按下键盘、调整窗体大小都将产生消息。消息产生后,会按照消息产生的先后顺序,放置到窗体的消息队列当中。等待程序从消息队列中获取消息进行处理。
2.获取消息
- 获取消息
通过 getmessage 函数可以从消息队列中获取到一个消息,并将这个消息从消息队列中移除。然后,程序可以对这个消息进行处理,并作出对应的响应。
让我们来看看 getmessage 这个函数该如何使用吧。
ExMessage getmessage(BYTE filter = -1);
void getmessage(ExMessage *msg, BYTE filter = -1);
getmessage 函数有两个重载版本,除了参数类型不同,函数的作用是相同的。
3.ExMessage结构体:
4.使用和操作:
实现一个代码完成鼠标在窗体中移动的过程中,正常移动以鼠标位置正常绘制一个黑色的小球,按下左键绘制一个红色的球,按下右键绘制一个蓝色的球,按下ctrl和左键绘制一个矩形。
#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<easyx.h>
int main()
{
initgraph(800, 600);
setbkcolor(WHITE);
cleardevice();
//消息处理
ExMessage msg;
//鼠标移动绘制小球
while (1)
{
//不段的获取鼠标消息
getmessage(&msg, EX_MOUSE);
switch (msg.message)
{
case WM_LBUTTONDOWN:
setfillcolor(RED);
solidcircle(msg.x, msg.y, 10);
break;
case WM_RBUTTONDOWN:
if (msg.ctrl)
{
setfillcolor(GREEN);
solidrectangle(msg.x, msg.y, msg.x + 20, msg.y + 20);
continue;
}
else
{
setfillcolor(BLUE);
solidcircle(msg.x, msg.y, 10);
}
break;
}
setfillcolor(BLACK);
solidcircle(msg.x, msg.y, 2);
}
getchar();
closegraph();
return 0;
}
总结:使用结构体中的消息标识通过switch case语句去走不同的消息标识进行不同的处理。
如果使用ifelse语句自己范围结构体一些数据有一些成员变量的类型你不清楚结构体变量的.操作范围不是很灵活的
使用消息标识+switch语句就可以非常好的获取对应的信息并且做出相关的操作