可视化编程的作业(讲了快2个月了还在讲win32 api,然而我们最后不是考mfc吗喂老师),内容是按下鼠标擡起后小球弹上去又掉下来,弹跳高度与鼠标按下时间长度相关。
相关的实现细节用注释写在程序里,没有注释的部分就是vs自动生成的。
Ps.使用的是visual studio 2015,不保证能在别的版本上能直接通过
#include "stdafx.h"
#include "jumpingBall.h"
#include <math.h>
#define MAX_LOADSTRING 100
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
LoadStringW(hInstance, IDS_APP_TITLE, szTitle, MAX_LOADSTRING);
LoadStringW(hInstance, IDC_JUMPINGBALL, szWindowClass, MAX_LOADSTRING);
MyRegisterClass(hInstance);
if (!InitInstance (hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_JUMPINGBALL));
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
return (int) msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_JUMPINGBALL));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW+1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_JUMPINGBALL);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance;
HWND hWnd = CreateWindowW(szWindowClass, szTitle, WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT, 0, CW_USEDEFAULT, 0, nullptr, nullptr, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
// 自己定义的一个返回rect的函数,一直没找到原生的相应api
tagRECT makeRECT(int x1, int y1, int x2, int y2) {
tagRECT tag;
tag.left = x1;
tag.top = y1;
tag.right = x2;
tag.bottom = y2;
return tag;
}
// 程序时钟
long timingSecond = 0;
// 基准点
POINT startPos{ 120,400 };
// 小球半径
int radius = 35;
// 当前是否按下按钮
bool buttonDown = false;
// 按下按钮的总时间
int spanSecond = 0;
// 最高时间
int topTime = 0;
// 当前高度
int height = 0;
// 初速度,对应于spanSecond
int v0;
// 力场加速度
double g = 2;
// 开始运动的时长
int t = 0;
// 按动时间与初速度转化系数
int K_timeToGetSpeed = 1;
// 落地速度衰减率
double decrease = 0.8;
// 计算距离上次更新图像时间段内的位移
int computeX() {
// 这一步没什么意义,只是感觉时间比较长除以2而已; 完全可以直接用v计算
// 当然也不是完全没意义,除数越大小球状态更新越慢,可以自己改了看看
int t2 = t / 2;
int x = v0*t2 - 0.5*g*t2*t2;
int dis = x - height;
return dis;
}
// 控制弹跳初速度衰减
void changeHeight(int dis) {
// 得到小球真实高度
height += dis;
// 运动落地,开始反弹
if (height < 0) {
height = 0;
// 速度衰减,开启新一轮运动计时
v0 *= decrease;
t = 0;
}
}
void drawBall(HDC& hdc) {
// 计算得到小球此时的座标
POINT center = startPos;
center.y -= height;
// 获取客户区大小
RECT clientRECT;
HWND hwnd = WindowFromDC(hdc);
::GetClientRect(hwnd, &clientRECT);
// 创建内存缓冲区
HDC hdcmem = ::CreateCompatibleDC(hdc);
// 创建可以选入当前设备的HBITMAP(指针结构)
HBITMAP hbit = ::CreateCompatibleBitmap(hdc, clientRECT.right, clientRECT.bottom);
// 将hbitmap选入内存缓冲区
::SelectObject(hdcmem, hbit);
// 构造笔刷(涂色)和笔(画轮廓)
HBRUSH hbrush = ::CreateSolidBrush(RGB(111, 224, 162));
HPEN pen = ::CreatePen(0, 2, RGB(66, 219, 140));
// 填充白色背景
HBRUSH hb = ::CreateSolidBrush(RGB(255, 255, 255));
::SelectObject(hdcmem, hb);
::FillRect(hdcmem, &clientRECT, hb);
::DeleteObject(hb);
::SelectObject(hdcmem, hbrush);
::SelectObject(hdcmem, pen);
// 绘制小球
::Ellipse(hdcmem, center.x - radius, center.y - radius, center.x + radius, center.y + radius);
::DeleteObject(hbrush);
::DeleteObject(pen);
// 绘制高光
hbrush = ::CreateSolidBrush(RGB(255,255,255));
pen = ::CreatePen(0, 1, RGB(144, 231, 184));
::SelectObject(hdcmem, hbrush);
::SelectObject(hdcmem, pen);
::Ellipse(hdcmem, center.x - radius*0.7, center.y - radius*0.7, center.x - radius*0.3, center.y - radius*0.3);
::DeleteObject(pen);
// 绘制地面2条线
pen = ::CreatePen(0, 2, RGB(145, 93, 36));
::SelectObject(hdcmem, pen);
::MoveToEx(hdcmem, startPos.x - 120, startPos.y + radius, NULL);
::LineTo(hdcmem, startPos.x + 120, startPos.y + radius);
::DeleteObject(pen);
pen = ::CreatePen(0, 1, RGB(171, 120, 54));
::SelectObject(hdcmem, pen);
::MoveToEx(hdcmem, startPos.x - 90, startPos.y + radius + 10, NULL);
::LineTo(hdcmem, startPos.x + 90, startPos.y + 10 + radius);
::DeleteObject(pen);
// 将内存缓冲区写入真实的屏幕客户区
::BitBlt(hdc, 0, 0, clientRECT.right, clientRECT.bottom, hdcmem, 0, 0, SRCCOPY);
// 如果不释放,程序将会迅速占用大量内存
// 你别说,注释掉以后还真没有多占内存,有点神奇啊
::DeleteObject(hbit);
::DeleteDC(hdcmem);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_CREATE:
{
// 启动计时器,ID为10010(我用联通)
SetTimer(hWnd, 10010, 3, NULL);
// 获取客户区大小
RECT clientRECT;
::GetClientRect(hWnd, &clientRECT);
// 设定小球基准位置
startPos.x = clientRECT.right / 2;
startPos.y = clientRECT.bottom - 80;
// 计算最大速度 -> 计算半程运动最长的时间(达到屏幕客户区最高点)
int vm = sqrt(2 * g*startPos.y);
topTime = vm / K_timeToGetSpeed;
break;
}
case WM_SIZE:
{
// 窗口变化后重新计算基准位置与顶点时间
RECT clientRECT;
::GetClientRect(hWnd, &clientRECT);
startPos.x = clientRECT.right / 2;
startPos.y = clientRECT.bottom - 80;
// 重新计算最大时间
int vm = sqrt(2 * g*startPos.y);
topTime = vm / K_timeToGetSpeed;
break;
}
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
DestroyWindow(hWnd);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
// TODO: 在此处添加使用 hdc 的任何绘图代码...
drawBall(hdc);
EndPaint(hWnd, &ps);
}
break;
case WM_LBUTTONDOWN:
{
// 标记此时鼠标按下,不在计时器消息响应中更新画面
buttonDown = true;
// 记录当前时刻,以得出鼠标按下时长
spanSecond = timingSecond;
break;
}
case WM_LBUTTONUP:
{
// 鼠标擡起,小球开始运动
buttonDown = false;
t = 0;
// 获得鼠标按下时间,但不能过长,以防小球“飞出”客户区
spanSecond = timingSecond - spanSecond;
if (spanSecond > topTime)
spanSecond = topTime;
// 按照系数,将鼠标按下时长转换为小球初速度
v0 = spanSecond * K_timeToGetSpeed;
break;
}
case WM_TIMER: {
// 在计时消息中为程序已时间续一秒
timingSecond++;
// 在鼠标擡起时进行画面重绘
if (!buttonDown) {
// 运动时间加一
t++;
// 计算位移
int dis = computeX();
// 得到高度
changeHeight(dis);
// 重绘命令,会向消息队列发出WM_PAINT消息,NULL是重绘全部区域,
// 0是不使用背景擦除(在没有用双缓冲重写相关消息响应[好像是WM_ERASEBKGND]的情况下会闪屏)
InvalidateRect(hWnd, NULL, 0);
}
break;
}
case WM_DESTROY:
// 杀掉计时器
KillTimer(hWnd, 10010);
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}