簡單的win32應用程序:彈跳的小球(使用雙緩衝)

可視化編程的作業(講了快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;
}


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章