这一节中,我们尝试如何在实践中使用EasyX的消息处理特性,并且利用鼠标消息做一款打气球的小游戏。
1. 气球向上飘
声明两个符号常量作为窗体的宽和高。符号常量WINDOW_WIDTH
为窗体的宽度,其值设定为800。符号常量WINDOW_HEIGHT
为窗体的高度,其值设定为600。
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
创建一个宽度为WINDOW_WIDTH
、高度为WINDOW_HEIGHT
的窗体,并用白色背景色刷新窗体。
// 创建一个宽度为WINDOW_WIDTH、高度为WINDOW_HEIGHT的窗体,并用白色背景色刷新窗体。
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
setbkcolor(WHITE);
cleardevice();
我们用彩色的圆形当做气球,让5个气球从窗体的底部出现,飘到窗体的顶部后消失。
一个气球具有以下属性:
- 圆心x坐标
- 圆心y坐标
- 半径
- 向上移动的速度
- 颜色
声明一个气球结构体,拥有上述属性对应的成员。
typedef struct {
int x;
int y;
int r;
int v;
COLORREF color;
}balloon;
由于窗体中最多同时出现5个气球,声明一个宏MAX_IN_WINDOW
,用来表示同时出现的气球数量。再声明一个宏BALLOON_RADIUS
,用于表示气球的半径,其数值设置为30。
#define MAX_IN_WINDOW 5
#define BALLOON_RADIUS 30
为了记录这些气球的数据,声明一个长度为MAX_IN_WINDOW
、元素类型为balloon
的数组。
// 气球数组
balloon arrBalloons[MAX_IN_WINDOW];
接下来给气球数组中的各个元素设置初始值。
// 给气球数组设置初始值
for(int i = 0; i < MAX_IN_WINDOW; i++)
{
// x坐标在区间[100, 700]之间
int m, n;
m = 100;
n = 700;
arrBalloons[i].x = rand() % (n - m + 1) + m;
// y坐标在窗口底部
arrBalloons[i].y = WINDOW_HEIGHT;
// 半径为符号常量BALLOON_RADIUS
arrBalloons[i].r = BALLOON_RADIUS;
// 速度在区间[1, 3]之间
m = 1;
n = 3;
arrBalloons[i].v = rand() % (n - m + 1) + m;
// 颜色RGB值随机
arrBalloons[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
气球的x
坐标在区间[100, 700]
内,气球的y
坐标为WINDOW_HEIGHT
,即出现在窗体底部的随机位置。半径为符号常量BALLOON_RADIUS
,向上移动的速度在区间[1, 3]
之间,颜色RGB值随机。
增加新气球的循环结束后,5个气球的属性都被设置好了。接下来,可以逐帧绘制画面,让气球动起来了。为了更加精确地控制帧率,我们以精确控制帧率那一节课中讨论代码为基础进行修改。并且,将帧率设置为60帧,一秒钟有1000000
微秒,那么最长等待时间就是1000000 / 60
微秒。
timeBeginPeriod(1);
LARGE_INTEGER startCount, endCount, F;
QueryPerformanceFrequency(&F);
BeginBatchDraw();
while (1)
{
QueryPerformanceCounter(&startCount);
// 准备画面
QueryPerformanceCounter(&endCount);
long long elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
// 每秒帧率设置为60,因此最长等待时间为1000000 / 60
while (elapse < 1000000 / 60)
{
Sleep(1);
QueryPerformanceCounter(&endCount);
elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
}
FlushBatchDraw();
}
EndBatchDraw();
timeEndPeriod(1);
在代码QueryPerformanceCounter(&startCount)
与FlushBatchDraw()
添加上绘制气球和移动气球的相关代码。
- 清空窗体
- 绘制各个气球
- 批量显示
- 移动气球
timeBeginPeriod(1);
LARGE_INTEGER startCount, endCount, F;
QueryPerformanceFrequency(&F);
BeginBatchDraw();
// 主循环
while (1)
{
QueryPerformanceCounter(&startCount);
// 清空窗体
cleardevice();
// 绘制并移动气球
for (int i = 0; i < MAX_IN_WINDOW; i++)
{
// 绘制气球
setfillcolor(arrBalloons[i].color);
solidcircle(arrBalloons[i].x, arrBalloons[i].y, arrBalloons[i].r);
}
// 移动气球
for (int i = 0; i < MAX_IN_WINDOW; i++)
arrBalloons[i].y -= arrBalloons[i].v;
QueryPerformanceCounter(&endCount);
long long elapse = (endCount.QuadPart - startCount.QuadPart) * 1000000 / F.QuadPart;
while (elapse < 1000000 / 60)
{
Sleep(1);
QueryPerformanceCounter(&endCount);
elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
}
// 批量显示
FlushBatchDraw();
}
EndBatchDraw();
timeEndPeriod(1);
在其后的讨论中,我们把以上代码中最外层一个循环称作主循环。
现阶段代码如下:
#include <easyx.h>
#include <stdio.h>
#define WINDOW_WIDTH 800
#define WINDOW_HEIGHT 600
#define MAX_IN_WINDOW 5
#define BALLOON_RADIUS 30
typedef struct {
int x;
int y;
int r;
int v;
COLORREF color;
}balloon;
int main()
{
// 创建一个宽度为WINDOW_WIDTH、高度为WINDOW_HEIGHT的窗体,并用白色背景色刷新窗体。
initgraph(WINDOW_WIDTH, WINDOW_HEIGHT);
setbkcolor(WHITE);
cleardevice();
// 气球数组
balloon arrBalloons[MAX_IN_WINDOW];
// 给气球数组赋值
for (int i = 0; i < MAX_IN_WINDOW; i++)
{
// x坐标在区间[100, 700]之间
int m, n;
m = 100;
n = 700;
arrBalloons[i].x = rand() % (n - m + 1) + m;
// y坐标在窗口底部
arrBalloons[i].y = WINDOW_HEIGHT;
// 半径为符号常量BALLOON_RADIUS
arrBalloons[i].r = BALLOON_RADIUS;
// 速度在区间[1, 3]之间
m = 1;
n = 3;
arrBalloons[i].v = rand() % (n - m + 1) + m;
// 颜色RGB值随机
arrBalloons[i].color = RGB(rand() % 256, rand() % 256, rand() % 256);
}
timeBeginPeriod(1);
LARGE_INTEGER startCount, endCount, F;
QueryPerformanceFrequency(&F);
BeginBatchDraw();
while (1)
{
QueryPerformanceCounter(&startCount);
// 清空窗体
cleardevice();
// 绘制并移动气球
for (int i = 0; i < MAX_IN_WINDOW; i++)
{
// 绘制气球
setfillcolor(arrBalloons[i].color);
solidcircle(arrBalloons[i].x, arrBalloons[i].y, arrBalloons[i].r);
}
// 移动气球
for (int i = 0; i < MAX_IN_WINDOW; i++)
arrBalloons[i].y -= arrBalloons[i].v;
QueryPerformanceCounter(&endCount);
long long elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
// 每秒帧率设置为60,因此最长等待时间为1000000 / 60
while (elapse < 1000000 / 60)
{
Sleep(1);
QueryPerformanceCounter(&endCount);
elapse = (endCount.QuadPart - startCount.QuadPart)
* 1000000 / F.QuadPart;
}
// 批量显示
FlushBatchDraw();
}
EndBatchDraw();
timeEndPeriod(1);
closegraph();
return 0;
}