文章目录
- 其他文章
- 最终结果
- 设计过程
- 定义小球的属性
- 窗口过程函数
- 绘制小球
- 空格回弹
- 小球碰壁
- 完整代码
其他文章
部分知识可以查看如下文章:
C语言编写注册窗口
最终结果
先放一下本篇文章最终结果展示图吧,如图,一个绿色的小球,在碰到窗口边缘的时候会自动弹回,并且按空格,会让小球反方向移动。
设计过程
定义小球的属性
把小球的属性定义成全局变量,使其可以在整个文件中使用。
我们需要设置小球的初始位置和速度,用于控制小球的移动。初始位置可以通过x和y两个坐标来表示。
代码如下所示:
// 小球的初始位置和速度
int ballX = 50;
int ballY = 50;
int velocityX = 5;
int velocityY = 5;
窗口过程函数
接着我们需要声明窗口过程函数,该函数将作为窗口的消息处理函数,用于处理窗口的各种消息。
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
//......
}
}
如上,我们声明 WindowProc 函数为窗口过程函数,然后我们需要将窗口过程函数的地址赋给WNDCLASS结构体变量的lpfnWndProc成员。代码如下。
wc.lpfnWndProc = WindowProc;
绘制小球
要想绘制小球,我们需要接收WM_PAINT窗口消息类型,该窗口消息用于绘图和更新窗口的外观。
case WM_PAINT:
{
return 0;
}
最后的 return 0; 表示消息已经得到处理,不需要进一步传递处理。
接收到窗口消息后,首先通过调用 BeginPaint 函数获取绘图设备上下文(hdc)和绘图相关的信息(ps)。
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
然后,通过调用 GetClientRect 函数获取窗口的客户区矩形(rc),该矩形表示窗口的绘图区域。
RECT rc;
GetClientRect(hwnd, &rc);
接下来,通过调用 FillRect 函数,使用指定的画刷将窗口的客户区填充为指定的颜色。
FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
这里使用 (HBRUSH)(COLOR_WINDOW + 1) 表示使用系统默认的窗口背景色。
然后,通过调用 CreatePen 和 CreateSolidBrush 创建画笔(hPen)和画刷(hBrush),并将它们选入绘图设备上下文(hdc)中,用于绘制小球。
// 设置画笔和画刷
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
SelectObject(hdc, hPen);
SelectObject(hdc, hBrush);
通过Ellipse()方法,我们可以绘制出一个椭圆。
该方法的第一个参数是设备上下文句柄,这个参数指定了要在哪个设备上下文中进行绘制。第二和第三个参数是小球的左上角坐标,表示小球在窗口中的位置。分别是x和y的坐标,这就用我们之前声明的全局变量就好了。最后两个参数是我们小球的右下角坐标,我们可以通过对我们左上角的坐标+一个值,来规定小球的大小。比如说我们可以使用ballX + 20 和 ballY + 20 作为小球的右下角坐标,表示小球的宽高都是 20 像素。
// 绘制小球
Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);
然后,将不再使用的画笔(hPen)和画刷(hBrush)资源释放,以避免内存泄漏,通过调用 DeleteObject 函数进行资源删除。
// 释放资源
DeleteObject(hPen);
DeleteObject(hBrush);
最后,通过调用 EndPaint 函数结束绘图操作,并通知系统已完成窗口绘制。
EndPaint(hwnd, &ps);
完整代码如下所示:
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 清除窗口内容
RECT rc;
GetClientRect(hwnd, &rc);
FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
// 设置画笔和画刷
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
SelectObject(hdc, hPen);
SelectObject(hdc, hBrush);
// 绘制小球
Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);
// 释放资源
DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
return 0;
}
}
}
空格回弹
接下来我们实现空格回弹的操作。
在窗口消息处理函数中,我们检查窗口消息,如果窗口消息类型是 WM_KEYDOWN 消息,就表示当前有按键按下了。
case WM_KEYDOWN:
{
}
接下来我们检查wParam变量,查看按下的按键是否是空格。
wParam变量是窗口消息处理函数的一个参数,这个变量中有哪个按键被按下的消息,它是一个无符号整数,代表按下的键的虚拟键码。
虚拟码是定义在 Windows.h 头文件中的常量,也叫虚拟键码。
而我们空格键的虚拟码就是VK_SPACE.
通过如下代码判断按下的是否是空格。
if (wParam == VK_SPACE)
当空格键被按下时,我们改变小球的移动速度,将速度 velocityX 和 velocityY 的值取反,以改变小球的速度。通过将速度取反,使小球在水平和垂直方向上的运动方向都将改变。
最后,通过 return 0; 表示消息已经得到处理,不需要进一步传递处理。
如下所示:
case WM_KEYDOWN:
{
// 按下空格键时改变小球速度
if (wParam == VK_SPACE)
{
velocityX = -velocityX;
velocityY = -velocityY;
}
return 0;
}
小球碰壁
要知道小球是否碰壁,我们要时刻监视,通过WM_TIMER窗口消息,我们可以定时检查小球是否碰壁。
WM_TIMER表示定时器消息,它会周期性地触发。
case WM_TIMER:
{
return 0;
}
当接收到 WM_TIMER 消息时,即定时器到达了指定的时间间隔时,会执行case后的代码块。
最后的 return 0; 表示消息已经得到处理,不需要进一步传递处理。
接下来,我们需要更新小球的位置。根据当前的速度 (velocityX 和 velocityY),将小球的水平和垂直位置分别增加对应的速度值。
// 更新小球的位置
ballX += velocityX;
ballY += velocityY;
然后,通过调用 GetClientRect 方法,可以获取到窗口的客户区域(注意,该区域不包括标题栏和边框),然后将其存储在一个RECT类型的变量中。
RECT rc;
GetClientRect(hwnd, &rc);
接下来,要根据小球的位置以及窗口的边界,检查小球是否与窗口边界发生碰撞。如果小球的横坐标 (ballX) 小于等于 0 或大于等于窗口的宽度减去小球的宽度,即小球到达了窗口的左右边界,那么将水平速度取反,从而使小球改变水平方向的运动。
如果小球的纵坐标 (ballY) 小于等于 0 或大于等于窗口的高度减去小球的高度,即小球到达了窗口的上下边界,那么将垂直速度取反,从而使小球改变垂直方向的运动。
if (ballX <= 0 || ballX >= rc.right - 20)
{
velocityX = -velocityX;
}
if (ballY <= 0 || ballY >= rc.bottom - 20)
{
velocityY = -velocityY;
}
最后,通过调用 InvalidateRect 函数,请求重新绘制窗口的客户区域,使得小球的位置更新后能够在窗口中显示出来。
InvalidateRect(hwnd, NULL, TRUE);
完整代码
#include <windows.h>
// 小球的初始位置和速度
int ballX = 50;
int ballY = 50;
int velocityX = 5;
int velocityY = 5;
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam);
// 程序入口
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
{
// 注册窗口类
WNDCLASS wc = {0};
wc.lpfnWndProc = WindowProc;
wc.hInstance = hInstance;
wc.lpszClassName = "MyWindowClass";
RegisterClass(&wc);
// 创建窗口
HWND hwnd = CreateWindowEx(
0,
"MyWindowClass",
"移动小球",
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
800,
600,
NULL,
NULL,
hInstance,
NULL);
// 设置定时器,控制小球移动
SetTimer(hwnd, 1, 30, NULL);
// 显示窗口
ShowWindow(hwnd, nCmdShow);
// 消息循环
MSG msg;
while (GetMessage(&msg, NULL, 0, 0))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
return 0;
}
// 窗口过程函数
LRESULT CALLBACK WindowProc(HWND hwnd, UINT uMsg, WPARAM wParam, LPARAM lParam)
{
switch (uMsg)
{
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hwnd, &ps);
// 清除窗口内容
RECT rc;
GetClientRect(hwnd, &rc);
FillRect(hdc, &rc, (HBRUSH)(COLOR_WINDOW + 1));
// 设置画笔和画刷
HPEN hPen = CreatePen(PS_SOLID, 2, RGB(255, 0, 0));
HBRUSH hBrush = CreateSolidBrush(RGB(0, 255, 0));
SelectObject(hdc, hPen);
SelectObject(hdc, hBrush);
// 绘制小球
Ellipse(hdc, ballX, ballY, ballX + 20, ballY + 20);
// 释放资源
DeleteObject(hPen);
DeleteObject(hBrush);
EndPaint(hwnd, &ps);
return 0;
}
case WM_KEYDOWN:
{
// 按下空格键时改变小球速度
if (wParam == VK_SPACE)
{
velocityX = -velocityX;
velocityY = -velocityY;
}
return 0;
}
case WM_TIMER:
{
// 更新小球的位置
ballX += velocityX;
ballY += velocityY;
// 碰到窗口边界时改变方向
RECT rc;
GetClientRect(hwnd, &rc);
if (ballX <= 0 || ballX >= rc.right - 20)
{
velocityX = -velocityX;
}
if (ballY <= 0 || ballY >= rc.bottom - 20)
{
velocityY = -velocityY;
}
// 重绘窗口
InvalidateRect(hwnd, NULL, TRUE);
return 0;
}
case WM_DESTROY:
PostQuitMessage(0);
return 0;
default:
return DefWindowProc(hwnd, uMsg, wParam, lParam);
}
}