25.打气球小游戏

  • 学习人数 15K+
  • 适合有C语言基础人群学习
avatar
林耿亮

你好编程主讲老师

这一节中,我们尝试如何在实践中使用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()添加上绘制气球和移动气球的相关代码。

  1. 清空窗体
  2. 绘制各个气球
  3. 批量显示
  4. 移动气球
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;
}