遊戲編程初步 Ⅰ-Windows編程初步

來源:互聯網
上載者:User

譯註:最近遇到點感情問題,已經兩個月了, 陷入感情問題真是無法自拔。 自認為理性的人,卻是感性得超越了一般人。 是時候慢慢走出來了,只有知識才是實在的,學到後永遠屬於自己。必須做點別的事。本人英語水平有限,如果你覺得文中有任何不妥,請別介意。 可任意轉載,但請註明文章原始連結,最終著作權屬於原作者。

原文地址:http://www.gamedev.net/reference/programming/features/gpgenesis1/default.asp

緒論:
   本部份介紹windows編程最基礎的識知。到最後,你應用運用所學,完成簡單的windows編程。 學習本部份前題是C語言的基礎。 在我代碼中幾乎不會運用C++的擴充特性。 但是, Windows本身就是而向對象的, 因此會涉及到一點關於類的知識, 但不會成為你學習的障礙。如果你對它不熟悉, 也不必擔心,你並不需要運用過於複雜的東西,因此只要你看下去你會很快學會它。所有的範例程式碼都在Microsoft Visual C++ 6.0下編譯測試通過。如果你還沒有win32 C/C++編譯器, VC6是很好的選擇。現在我們旅程開始吧!

安裝:
   我們需要windows.h與windowsx.h這兩個標頭檔, 它們包括windows大部份方法。請確保在你程式中包含了他們。除此之處, 你還要用到標準C的標頭檔, 例如:stdio.h, conio.h,等等。另外,在許多的windows程式中的第一行中有這麼一行代碼:
#define WIN32_LEAN_AND_MEAN
   它具有很特殊的意義, 它去掉了windows標頭檔中一些MFC相關資源,提高了編譯速度而減少時間。你可能很不願意在你的遊戲編程中運用MFC,因此在大多數情況下這是一個很好的處理方式。在此之前你可能沒有見過這種代句語句--一個#define指示後面接著的僅僅是一個宏--,它背用來進行條件編譯。讓我們來看一個樣本:
#ifdef DEBUG_MODE
    printf("Debug mode is active!");
#endif
   如果程式中包括了這個範例程式碼,並且在此之前遇到過#define DEBUG_MODE,那麼printf()這句將被編譯,否而不於編譯。這種方式在你代碼中非常有用,它會開關你的代碼,協助你排除可能發生的邏輯錯誤。而WIN32_LEAN_AND_MEAN這個宏用來關掉windows標頭檔中很少用的組件。理解了嗎? 很好, 我們進入實際代碼...

WinMain()方法:
   與Dos下C編程以main()方法做為執行入口一樣,Windows程式則以WinMain()方法為執行入口。最簡單的WimMain()方法如下:
int WINAPI WinMain(HINSTANCE hinstance, HINSTANCE hPrevInstance,
                   LPSTR lpCmdLine, int nCmdShow)
{
    return(0);
}
   該方法除了返回一個值外,什麼也沒有做,它也帶來了許多新特性。首先, 作為WINAPI的聲明有什麼用? 這是一個關於呼叫慣例的說明。它關係到參數傳入方法的方式,而這個方法充當棧清理,還關係到一些你根本不可能看到的事情。一個方法用WINAPI的呼叫慣例的時候,以從左至右的方式傳入參數,與預設的從右至左的呼叫慣例相反。除非你藉助彙編指令編程,否則你不需要知道呼叫慣例的工作細節, 只有WinMain()必需要WINAPI的呼叫慣例。接著,讓我們來看一看該方法的四個參數:

HINSTANCE hinstance:這個是你程式的執行個體控制代碼。基本上, 它們有點類似指標,用來追蹤任何時候所有啟動並執行程式。許多的windows方法都需要這個執行個體控制代碼作為參數, 因此它才知道是哪個應用程式發出的調用申請。

HINSTANCE hPrevInstance:你不需要擔心這個參數,因此現在它而不再被利用。在老版本的windows中,這指向啟用你現在這個程式的程式。之所以還保留的原因是為了向後相容。在今後的windows編程中你還會看到許多類似的情況。

LPSTR lpCmdLine:它是一個指標,指向一個安符串,這個字串包含了程式被調用時命令列參數。注意這兒並沒有參數描述命令列參數的個數,因此你需要自行決定。

int nCmdShow:該整數指示主視窗出現方式。如果你不想指定,你可以不做任何處理。它的值都是以SW.開頭的。比如:SW_SHOWNORMAL表示默方式, SW_MAXIMIZE與SW_MINIMIZE分別表示最大與已最小化的視窗,等等。

   這些就是關於WinMain()的參數介紹。常常,有且只有一個重要角色是hinstance。在我們繼續講解如何才能顯示一個視窗前, 微軟的變數命名有待說明。

匈牙利命名:
  微軟對變數,方法,常量與類運用了一套標準的命名規則,它就是匈牙利命名法。你已在WinMain中看到了這種命名方法的樣子。該命名方法就是在變數名前面加一些代表資料類型的首碼。 這兒有些首碼的用法說明:
b BOOL (int)
by BYTE or UCHAR (unsigned char)
c char
cx, cy short (usually lengths; c stands for "count")
dw DWORD (unsigned long)
fn Function pointer
h Handle (like a pointer, used for Windows objects)
i int
l LONG (long int)
lp Long pointer (32-bit)
msg Message (we'll cover this later)
n number (short or int)
s String
sz, str Null-terminated ("ASCIIZ") string
w WORD or UINT (unsigned int)
x, y short (usually coordinates)
 
  另外, 如果變數名多於一個單詞,那麼每個單詞首寫字每要大寫,單詞與單詞之間不需要底線。 例如: 一個指向記憶體地區的指標,而它名字與player data有關係,那麼命各的時候可能表示為:lpPlayerData。 這種標準化的命名規則對於代碼理解很有協助。 就拿上面那個WinMain()方法為例, 不需要查看它的協助文檔, 它的命名方式就說明了參數的意義, hinstance與hPrevInstance是控制代碼, lpCmdLine是一個32位的長指標, 而nCmdshow是一個整數。

  用匈牙利命名方式給函數命名與變數命名方式一樣, 只不過去掉了首碼。也就是說, 方法名的第一個字母是大寫的,另外,方法名中所有的單詞首寫字母都大寫。比如: ShowCharacterStats();
 
  對於常量的命名規則是所有的字母都大寫,並用底線區分單詞或首碼。比如:WIN32_LEAN_AND_MEAN。在Windows中你會看到,常量前常常有一個方法名的縮寫,而這個方法就是要用到這個常數的方法。比如:先前提到的SW_SHOWNORMAL, SW_MAXIMIZE,與SW_MINIMIZE這些常量,它們都有一個SW_的首碼,它說明這些常量將被ShowWindow()當著參數運用。

  最後對於類的命名除了在前加一個大寫的C字母外與方法命名是一樣, 比如:在速度競賽遊戲中用到的速度類可以命名為:CVehicle.
 
  在你程式中,你可以不運用這套命名方法, 但是我應該很熟悉它, 因為微軟的所有產品都是這套規則。 如果你選擇用它, 將會非常方便。 我就遵循這個規則。 暫且不管你了, 我們繼續學習...

訊息
  當你在DOS上寫程式的時候,你不需要擔心別的程式運行情況,因為DOS是一個單任務作業系統。 但是在Windows中,你必須考慮到。介於種種原因, Windows中採用了訊息的機制來與應用程式通訊,它可以告訴程式幹什麼。訊息有許多作用。通過訊息,應用程式就知道什麼時候視窗改變大小,什麼時候移動,什麼時候關閉。它們給出程式什麼時候關閉的訊號。它們通知程式什麼時候視窗要重繪。 它可以追蹤滑鼠移動與按鈕點擊狀況。這樣的便子舉不勝數。無論如何,你的視窗程序必須能夠處理這些訊息。

  處理這些訊息的特殊方法就叫著回呼函數。回呼函數就不需要你顯示的在你代碼中調用它, 而是某種事件觸發它被調用。你可以通過CALLBACK這種調用約寫建立一個回呼函數, 就像WinMain()中的WINAPI的呼叫慣例。我將儘快結束這個話題,因為在你能夠處理訊息前,你更應能夠懂得如何建立一個視窗。

視窗類別:
 
  這兒你會學到一點關於C++的東西,因為建立視窗前你必須建立一個視窗類別。 這個類包括了視窗的所有資訊, 比如所用的表徵圖, 關聯的菜單等等。 你所建立的所有視窗程序,你都需要一個你滿足你要求的視窗類別。你所要做的就是填充一個WNDCLASSEX的結構體。"EX"代表"extended"即擴充的意思,表示以前有一個版本的結構體是WNDCLASS。我們將會運用擴充版本。下面就是他的廬山真面目:
typedef struct _WNDCLASSEX {
    UINT    cbSize;
    UINT    style;
    WNDPROC lpfnWndProc;
    int     cbClsExtra;
    int     cbWndExtra;
    HANDLE  hInstance;
    HICON   hIcon;
    HCURSOR hCursor;
    HBRUSH  hbrBackground;
    LPCTSTR lpszMenuName;
    LPCTSTR lpszClassName;
    HICON   hIconSm;
} WNDCLASSEX;
 
  這個結構體中有許多成員, 建立的視窗類別必須一個不漏的填充完。 其實關不難, 下面我們簡要的說明一下它的成員。

UINT cbSize: 以位元組為單位標識這個結構體的大小。你會常常看到這樣的結構體成員, 特別是在DX中。它存在的原因是,如果以這個結構體為參數傳遞給一個方法的時候, 不需要通過計算,只需要簡單的查看一下就能知道結構體的大小。 它的值總是等於sizeof(WNDCLASSEX).

UINT style: 這個是視窗風格,它是一些CS_為首碼的常量。 此外,你可以用|操作符串連幾個常量共同使用。通常情況下你只需要用到其中四個。為了減短文章的長度, 我將只展示這四個。你可以通過MSDN查看其餘的。 你真的要記得擷取Visual C++,千萬記住。
CS_HREDRAW Specifies that the window should be redrawn if it is horizontally resized.
CS_VREDRAW Specifies that the window should be redrawn if it is vertically resized.
CS_OWNDC Allows each window to have a unique device context or DC (not covered in this article).
CS_DBLCLKS Discerns between single- and double-clicks while the mouse is in this window.

WNDPROC lpfnWndProc:一個指標,該指標指向一個回呼函數,這個回呼函數處理視窗訊息。如果你從來沒有用過函數指標,其實它就指向函數地址,就像一個函數名一樣,只不過它沒有括弧跟在它後面。

int cbClsExtra: 它用來接收其它的類資訊,一般情況下不使用它。開發遊戲的時候你也不會發現它被用過,因此設定它為0。

int cbWndExtra:差不多與cbClsExtra相同,只不過它是儲存擴充視窗的資訊。你用的時候也只需設為0就可以。

HANDLE hInstance: 這是利用這個視窗類別的應用程式執行個體控制代碼, 它就是傳遞給WinMain()中的一個參數,它們必須是相等。

HCURSOR hCursor: 當滑鼠在視窗中的時候,它處理這個游標。 它常常用LoadCursor()的傳回值賦值。當然,你可以載入你自己定義的游標,這要等你學會如何載入才行。 或者你可以用標準的視窗游標, 用法為: LoadCursor(NULL, IDC_ARROW).

HBRUSH hbrBackground: 當你視窗收到被重新整理(或重繪)的訊息時,視窗就會用純色或畫刷重繪。這個參數就定義一個畫刷。你可以用GetStockObject()載入幾種純色畫刷,它們其中有:BLACK_BRUSH, WHITE_BRUSH, CARY_BRUSH等等。 現在你可以這樣運用GetStockObject(BLACK_BRUSH)。 對不起,我在介紹這些方法的時候是如此簡要, 我是為了減少篇幅。我將再以後的文章裡再談論到相關知識點, 我保證!

LPCTSTR lpszMenuName:如果你想建立一個下彈的菜單, 這個參數就是要載入菜單的名字,並且關聯到視窗。 因為現在你還不知道如何建立菜單, 所以你設為NULL表示沒有菜單。

LPCSTR lpszClassName:這個是你要建立類的名字。 你可以以你喜歡的名字命名,最好用一個繪述性的名字。 比如,"Game_Class" 這樣的名字。

HICON hIconSm:在視窗的標題列與開始功能表列裡會有一個小表徵圖,而這個小表徵圖就是這個參數傳入的。它與設定hIcon時是一樣的, 你可以用LoadIcon. 現在,我們將用LoadIcon(NULL, IDI_WINLOGO)載入一個標準視窗logo icon.

  這就是所有的參數說明。現在你對WNDCLASSEX這個結構體所有成員都熟悉了, 你可以填充它,並為建立視窗準備好。下面是一個簡單的樣本:
WNDCLASSEX sampleClass;                                   // declare structure variable

sampleClass.cbSize =        sizeof(WNDCLASSEX);           // always use this!
sampleClass.style =         CS_DBLCLKS | CS_OWNDC |
                            CS_HREDRAW | CS_VREDRAW;      // standard settings
sampleClass.lpfnWndProc =   MsgHandler;                   // we need to write this!
sampleClass.cbClsExtra =    0;                            // extra class info, not used
sampleClass.cbWndExtra =    0;                            // extra window info, not used
sampleClass.hInstance =     hinstance;                    // parameter passed to WinMain()
sampleClass.hIcon =         LoadIcon(NULL, IDI_WINLOGO);  // Windows logo
sampleClass.hCursor =       LoadCursor(NULL, IDC_ARROW);  // standard cursor
sampleClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH);  // a simple black brush
sampleClass.lpszMenuName =  NULL;                         // no menu
sampleClass.lpszClassName = "Sample Class"                // class name
sampleClass.hIconSm =       LoadIcon(NULL, IDI_WINLOGO);  // Windows logo again

   你設定完所有成員了。 但是這兒有值得提出的地方。 注意GetStockObject()返回的時候有一個HBRUSH的強制類型轉化。這是因為GetStockObject()可以用來載入許多個物件,並非單獨畫刷, 它的傳回值是HGDIOBJ類型的變數,它是更為概括的類型。 在Visual C++的老版本中, 你不需要強制轉化,但是VC++ 6.0對這方面更為嚴格, 如果你沒有強制轉化你就會得到一個編譯錯誤。
 
  最後一件事就是你需要向Windows註冊這個新類,然後你用這個類去建立新的視窗。只需要一個簡單的調用RegisterClassEx(),你就可以完成註冊。 它只有一個參數:結構體的地址。接著上面的樣本, 你可以這枯註冊它:
RegisterClassEx(&sampleClass);

  現在Windows已經能認識你所建立的新類, 你可以用它來建立視窗。現在時機成熟了, 不是嗎?

建立視窗:
 
  首先有一個好訊息,建立視窗的時候你只需要調用CreateWindowEx(). 也有一個壞訊息, 這個方法有許多參數。現在你可能很厭煩它,但是這兒沒有那麼糟糕。 下面是方法原型:
HWND CreateWindowEx(
    DWORD dwExStyle,      // extended window style
    LPCTSTR lpClassName,  // pointer to registered class name
    LPCTSTR lpWindowName, // pointer to window name
    DWORD dwStyle,        // window style
    int x,                // horizontal position of window
    int y,                // vertical position of window
    int nWidth,           // window width
    int nHeight,          // window height
    HWND hWndParent,      // handle to parent or owner window
    HMENU hMenu,          // handle to menu, or child-window identifier
    HINSTANCE hInstance,  // handle to application instance
    LPVOID lpParam        // pointer to window-creation data
);

  首先是關於這個傳回值。到現在, 可能對Windows的資料類型開始眼熟。 如果還沒有, 也不必擔心, 你會馬上用到它們而比你預先想得要早。傳回值是HWND, 它是視窗控制代碼。你希望把CreateWindowEx()的傳回值儲存下來, 因為你在許多Windows的函數中會用到它。現在,咱們來處理參數列表。 有許多參數是不喻自明的。

DWORDdwExStyle:擴充的視窗風格,這些你會很少用到, 所以許多情況下設為NULL。如果你感興趣, 在協助文檔中有大量的常量給你在此使用,它們都是以WS_EX_開頭的。

LPCTSTR lpClassName: 還記得你給類命名嗎? 這兒就需要那名字。

LPCTSTR lpWindowName:它是在視窗標題列顯示的內容。

DWORD dwStyle:視窗風格, 繪述你想建立視窗的類型。在此有許多的常量供你使用,它們都是以WS_開頭的, 你可以用|操作符聯合使用。下面只是向征性的列幾個通用的:
WS_POPUP A window that has no controls built into it.
WS_OVERLAPPED A window with simply a title bar and a border.
WS_OVERLAPPEDWINDOW A window with a title bar including all standard controls.
WS_VISIBLE Specifies that the window is initially visible.

WS_OVERLAPPEDWINDOW常常與別的常量聯合使用來建立出標準視窗。基本上你可以按著這種方法建立。如果你想要一個有最大化,最小化,可以改變大小...的視窗,就用WS_OVERLAPPEDWINDOW。如果你想要一個有標題列且固定大小的視窗, 就用WS_OVERLAPPED. 如果你想要一個在它上面沒有控制按鈕的視窗,就用WS_POPUP.比如一塊黑色地區窗然出現的視窗。你可以用它來製作全屏遊戲。當然你還需要WS_VISIBLE, 除非由於某些原因你不想讓別人看到你的視窗, 或是你想先做其它事然後再顯示視窗。

int x,y:它是視窗在螢幕左上方出現在座標位置。

int nWidth, nHeight:你可能已猜到, 它就是視窗的長與寬, 以象素為單位。

HWNF hWndParent:它處理你所建立視窗的父視窗。 大多數情況下被控制項使用,比如checkbox, pushbutton. 如果是主視窗,那麼設為NULL就行了,表示Windows的桌面視窗。

HMENU hMenu:它處理與視窗關聯的菜單。如果你載入一個資源菜單--在你學會如何載入之後--你可以用LoadMenu(). 如果沒有菜單關聯,設為NULL。

HINSTANCE hInstance:這是應用程式執行個體。用WinMain()中傳入的那個參數傳進去就行。

LPVOID lpParam:這個是你不喜歡用到的,特別在在遊戲開發中,只有很少的視窗才用到它。它用來建立類似多文檔介面的東西。 我們設為NULL。

  最後,我們已有了建立視窗所有需要的東西。下面樣本就完成這樣的工作:
HWND hwnd;
if (!(hwnd = CreateWindowEx(NULL,                   // extended style, not needed
                            "Sample Class",         // class identifier
                            "Sample Window",        // window title
                            WS_POPUP | WS_VISIBLE,  // parameters
                            0, 0, 320, 240,         // initial position, size
                            NULL,                   // handle to parent (the desktop)
                            NULL,                   // handle to menu (none)
                            hinstance,              // application instance handle
                            NULL)))                 // who needs it?
    return(0);

  這個視窗你可以用來寫遊戲,因為它是popup類型的視窗。注意我把CreateWindowEx()的調用放在if當中的。這是因為如果CreateWindowsEx()調用失敗, 它就會返回NULL。這樣做是的值得的, 如撒由於種種原因建立視窗失敗,WinMain()就會返回,程式就會結束。

  現在你已經掌握了足夠的Windows程式開發知識去建立一個功能性的視窗。還記得我們建立"Sample Class"的時候,我們提供了一個指向訊息處理方法的指標嗎? 在Windows允許我們建立其它東西之前,我們需要完成這個方法。

訊息處理:
  我已經涉及到了訊息在Windows中的應用。現在我們過一遍它的用法。訊息處理方法的原型與下面的相似:
LRESULT CALLBACK MsgHandler(
    HWND hwnd,     // window handle
    UINT msg,      // the message identifier
    WPARAM wparam, // message parameters
    LPARAM lparam  // more message parameters
};

  LRESULT這種類型的傳回值是專門為訊息處理函數設計的,就像我們要寫的一樣。我們先前也講過CALLBACK呼叫慣例。這個函數的參數非常簡單:

UINT msg: 定義一個訊息。它的值是以WM_(Windows message)開頭的常量。可發送的訊息數量非常多, 但是這兒列了些重要的:
WM_ACTIVATE A new window is receiving the focus.
WM_CLOSE A window is being closed.
WM_COMMAND A menu option has been selected.
WM_CREATE A window has been created.
WM_LBUTTONDBLCLK Left mouse button has been double-clicked.
WM_LBUTTONDOWN Left mouse button has been pressed.
WM_MOUSEMOVE The mouse has been moved.
WM_MOVE A window has been moved.
WM_PAINT Part of a window needs to be repainted.
WM_RBUTTONDBLCLK Right mouse button has been double-clicked.
WM_RBUTTONDOWN Right mouse button has been pressed.
WM_SIZE A window has been resized.
WM_USER Use this for whatever you want.

WPARAM wparam, LPARAM lparam: 精確的利用這些參數要看是發送的是什麼訊息, 但是他們為用來更為準確的繪述訊息的意義。

  如果你想寫出處理你視窗接收到的所有訊息的代碼,這將是非常愚蠢的想法。我知道我曾經就這樣想過。幸運的是,Windows提供了一些預設的訊息處理方法。如果你還沒有任何特別需要去處理某個訊息,你撒以調用DefWindowProc().只要有了這種思維, 這兒有一個極為簡單的,但是功能完整的訊息處理函數讓你做為參考:

LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    return(DefWindowProc(hwnd, msg, wparam, lparam));
}

  是不是很簡單?通常情況下,你想處理你自己的訊息。在這種情況下, 你可以寫你自己的代碼處理訊息,然後返回0告訴程式你已經處理了這個訊息。這兒有一個訊息處理的樣本, 當視窗被建立的時候調用初始化函數,其它的訊息被預設處理。

LRESULT CALLBACK MsgHandler(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
    if (msg == WM_CREATE)
    {
        Initialize_Game();
        return(0);
    }

    return(DefWindowProc(hwnd, msg, wparam, lparam));
}

  你的訊息處理函數中可以用一個switch語句來分配你想處於的訊息,最後調用預設處理函數來處理其它的訊息。為了在以後更順暢的講解,這兒有幾件非常重要的知識點展示給你。它就是如何確保你的訊息處理函數如何被執行。

讀取訊息佇列:
 
  在你開始你的程式主迴圈的時候, 你需要看看訊息佇列中是否有你等待的訊息。這個訊息佇列就儲存了所有未決的訊息。為了讓你處理函數正確運作,你需要做一些事。 你需要PeekMessage(), 它原型是:
BOOL PeekMessage(
    LPMSG lpMsg,         // pointer to structure for message
    HWND hWnd,           // handle to window
    UINT wMsgFilterMin,  // first message
    UINT wMsgFilterMax,  // last message
    UINT wRemoveMsg      // removal flags
);
 
  它傳回值類型是BOOL, 其實是int型, 只不過它只TRUE與FALSE兩個值。如果訊息佇列中有等待的訊息,那麼它返回TRUE。否則,它返回FALSE。它參數意思更為明確:

LPMSG lpMsg:指向MSG類型變數的指標。如果有個訊息在等待, 這個變數就被訊息所填充。

HWND hWnd:你想檢測的訊息佇列所屬的視窗控制代碼。

UINT wMsgFilterMin,wMsgFilterMax: 指示隊列中最先還是最後的訊息被檢測。 通常情況下, 你都是對第一個訊息感興趣,因此你設定兩個參數都為0。

UINT wRemoveMsg:一般有兩個值選擇: PM_REMOVE或PM_NOREMOVE。前一個表示,讀取一個訊息後將從訊息佇列中移除訊息,後一個表示讀取後保留訊息在隊中。通常, 如果一個訊息正在等待, 你就要馬上處理,也就是說你應用PM_REMOVE.
 
  當一個訊息等待的時候,你需要做一些事,讓你的訊息處理函數能夠命中訊息。 只需要兩個簡單的調用就行: 一個是TranslateMessage(),另一個是DispatchMessage().它們的函數原型是:

BOOL TranslateMessage(CONST MSG *lpmsg);
LONG DispatchMessage(CONST MSG *lpmsg);

  你可能猜到, 第一個方法是用來移出訊息,第二個方法調用你的訊息處理函數,並把MSG結構體中的適當資訊發過去。你只需要瞭解這些。在你主迴圈中所執行的就是:如果一個訊息正在等待,你就調用這上面那兩個方法,接著你的訊息處理函數接著完成剩下的。 這兒有程式碼範例:
if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
{
    TranslateMessage(&msg);
    DispatchMessage(&msg);
}

  沒問題吧! 現在你可以建立,註冊視窗類別,建立一個有效訊息處理。 並不是想像的那麼難,不是嗎?在我結束這篇文章之前,我還要提及一些事情。 回到訊息主題, 我們要用要用一點時間來說說手動發送訊息。

發送訊息

  有兩種方式發送訊息。 你可以調用PostMessage()或SendMessage(). 它們原型都很簡單:
BOOL PostMessage(
    HWND hWnd,      // handle of destination window
    UINT Msg,       // message to post
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
);

LRESULT SendMessage(
    HWND hWnd,      // handle of destination window
    UINT Msg,       // message to post
    WPARAM wParam,  // first message parameter
    LPARAM lParam   // second message parameter
);

  它們的參數與我們寫的MsgHandler()方法的參數一樣, 因此我不想再說明一偏。你只需要瞭解它們兩的不同, 因此我一個一個說明。

   PostMessage()用來加入一個訊息到訊息佇列中,並讓你的程式來處理它。如果成功返回一個非零值(TRUE),否則返回零(FALSE). 它只是簡單的向隊列中加入一個訊息,並立即返回。大多數情況下, 調用PostMessage()就可以很好的滿足需求。

   SendMessage()有點困難。 請注意,它傳回值是LRESULT,這種類型用在訊息處理函數當中。這是因為它並不是向隊列中加入一個訊息,它是傳遞訊息並立即調用訊息處理, 真到訊息被處理完成它才返回。如果事情是高優先權或需要快速響應的,Sendmessage()就比PostMessage()更適用。如果你想立即做某事,就用它吧。
  
  現在,你知道, 訊息這個主題是我最後提及到的內容, 它也是Windows編程與DOS編程最主要的區別。

面各過程編程:
  在DOS時代,你並不需要處理這些訊息,多個程式同時啟動並執行時候你也不需要注意。但是在Windows下面開發,這些訊息卻很重要的因素。因此,你編程的時候與DOS下完全不一樣。下面有虛擬碼為例:
// main game loop
do
{
    // handle messages here

    // ...

    // update screen if necessary
    if (new_screen)
    {
        FadeOut();
        LoadNewMap();
        FadeIn();
    }

    // perform game logic
    WaitForInput();
    UpdateCharacters();
    RenderMap();

} while (game_active);

  設想FadeOut()完成這樣的工作: 當被調用的時候,它讓一幅圖片在螢幕上一秒鐘內漸漸消失。 當螢幕完全變黑後,它才返回。FadeIn()的功能與它有點像。WaintForInput()只是簡單的等待, 直到鍵被按下。 它可能儲存輸入值到全域變數中。現在,基於DOS的遊戲,這是很一個非常適合的方式。在Windows下的遊戲,方式完全不一樣。

  為什麼不一樣呢? 很好,看看new_screen為真的時候會發生什嗎?它將漸隱螢幕,然後載入圖片,然後漸顯螢幕。所有過程一起花費2秒時間。在這兩秒鐘內將沒有訊息產生,因此使用者可以已最小化的視窗,但是這個程式卻一直在運行,就像使用者沒有最小化一樣。這一類的情況會發生錯誤,產生保護錯誤等等。不必說,這是不可取的。WaitForInput()更糟糕,因為它使過程化的程式的每一幀處於掛起狀況,直到有鍵按下。上面那個樣本有發生麻煩的隱患,這是可以確定的。
 
  最後一行中,你的程式如果能跑到30的幀率,你要確保你的主迴圈每秒能執行30次。主迴圈中每一次執行體只顯示一幀, 理論上並不是每幀所調用FadeOut()。 首次學習Windows編程, 這些可能比較難懂,因為這種思維很困難。但是,一旦你撐握了按這種方法創立程式, 你就會發現它有助於你寫出條理清淅,靈活的程式。

結束語

  這就是Windows編程的基礎。文章當中的樣本除了顯示一個視窗外沒做什麼別的了,它包含了Windows應用程式的整個架構。下一次,我將介紹處理資源,它允許你整合自訂表徵圖,游標,聲音,菜單...到你的.exe檔案中。

  如果你有任何問題與建議, 請通過郵件:ironblayde@aeon-software.com, ICQ: 53210499 與我聯絡。下次見!

Joseph "Ironblayde" Farrell
Aeon Software 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.