游戏编程初步 Ⅰ-Windows编程初步

译注:最近遇到点感情问题,已经两个月了, 陷入感情问题真是无法自拔。 自认为理性的人,却是感性得超越了一般人。 是时候慢慢走出来了,只有知识才是实在的,学到后永远属于自己。必须做点别的事。本人英语水平有限,如果你觉得文中有任何不妥,请别介意。 可任意转载,但请注明文章原始链接,最终版权属于原作者。

原文地址: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的函数中会用到它。现在,咱们来处理参数列表。 有许多参数是不喻自明的。

DWORDdwExStyle:扩展的窗口风格,这些你会很少用到, 所以许多情况下设为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 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值