【包含文件】#include <windows.h>
【WinMain函数】
int WINAPI WinMain(
HINSTANCE hInstance, // handle to current instance
HINSTANCE hPrevInstance, // handle to previous instance
LPSTR lpCmdLine, // command line
int nCmdShow // show state
);
当你程序执行时,被赋予了一个实例句柄hInstance,你可用来查找进程相关的东西(区分各个实例)。
命令行参数跟dos程序启动类似。
【事件和消息】
当某些事情发生的时候,Windows系统会让程序知道。比如说用户移动了鼠标,这个动作产生了事件event,系统会接收并处理。Windows将这个事件以消息message的形式发给程序。
Windows系统有一个全局的消息队列,然后填充程序的消息队列。在程序中有一个叫消息循环(message pump/loop),它连续的扫描消息队列来处理,它把扫描到的消息发给另一个函数window message procedure。
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
【注册窗口类】
CS_CLASSDC告诉Windows所有使用这个相同类的窗口共享绘图资源drawing resources。
// for DirectX games, use:
WNDCLASSEX wcex = { sizeof(WNDCLASSEX), CS_CLASSDC, \
WindowProc, 0L, 0L, hInstance, \
NULL, NULL, NULL, NULL, \
"GameClass", NULL };
// for standard applications, use:
WNDCLASSEX wcex = { sizeof(WNDCLASSEX), \
CS_HREDRAW | CS_VREDRAW, \
WindowProc, 0L, 0L, hInstance, \
LoadIcon(NULL, IDI_APPLICATION), \
LoadCursor(NULL, IDC_ARROW), \
(HBRUSH)GetStockObject(LTGRAY_BRUSH), \
NULL, "AppClass", NULL };
ATOM RegisterClassEx(CONST WNDCLASSEX *lpwcx);
BOOL UnregisterClass(
LPCTSTR lpClassName, // Class name to unregister
HINSTANCE hInstance); // Instance handle
【创建窗口】
HWND CreateWindow(
LPCTSTR lpClassName, // registered class name
LPCTSTR lpWindowName, // 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, // menu handle or child identifier
HINSTANCE hInstance, // handle to application instance
LPVOID lpParam // window-creation data
);
窗口样式:WS_OVERLAPPEDWINDOW可调整大小、WS_BORDER不可调整大小。
// Show the window
ShowWindow(hWnd, SW_SHOWNORMAL);
UpdateWindow(hWnd);
【消息队列】
// ... previous RegisterClass and CreateWindow function calls
MSG Msg;
while(GetMessage(&Msg, NULL, 0, 0)) {
TranslateMessage(&Msg);
DispatchMessage(&Msg);
}
游戏程序应该改一点:
MSG Msg;
// Clear out the message structure
ZeroMemory(&Msg, sizeof(MSG));
// Loop endlessly until you receive a quit message
while(Msg.message != WM_QUIT) {
// Peek into the queue and see if there’s a message waiting
if(PeekMessage(&Msg, NULL, 0, 0, PM_REMOVE)) {
// There’s a message! Handle it normally.
TranslateMessage(&Msg);
DispatchMessage(&Msg);
} else {
// No messages waiting. Go ahead and do time-crucial
// stuff here, such as rendering the game’s graphics.
}
}
【窗口消息过程】
LRESULT CALLBACK WindowProc(
HWND hwnd, // handle to window
UINT uMsg, // message identifier
WPARAM wParam, // first message parameter
LPARAM lParam // second message parameter
);
LRESULT CALLBACK WindowProc(HWND hWnd, UINT uMsg, \
WPARAM wParam, LPARAM lParam)
switch(uMsg) {
case WM_DESTROY:
PostQuitMessage(0); // Tell Windows to close application
break;
// Pass remaining messages to the default message handler
default: return DefWindowProc(hWnd, uMsg, wParam, lParam);
}
return 0;
}
代码见Windows.cpp
【对话框】
对话框dialog box是用通过对话框资源编辑器创建的模板来创建的程序窗口。对话框的悲剧的控制比较方便。
用CreateDialog创建对话框窗体。
HWND CreateDialog(
HINSTANCE hInstance, // handle to module
LPCTSTR lpTemplate, // dialog box template name
HWND hWndParent, // handle to owner window
DLGPROC lpDialogFunc // dialog box procedure
);
【资源】Resources
资源是添加的一个程序可执行文件的末尾的数据。
【关联资源到程序】
【找回资源数据】
使用FindResource、LoadResource、LockResource来使用关联的资源。
NOTE: MAKEINTRESOURCE宏将资源的名称转换为字符串指针。
LPTSTR *ResourcePointer = MAKEINTRESOURCE(IDR_MAP1);
HGLOBAL hResource = LoadResource(hInst, FindResource(hInst, \
MAKEINTRESOURCE(IDR_TEXT1), “TEXT”));
if(hResource != NULL) {
char *pText = (char*)LockResource(hResource);
MessageBox(NULL, pText, “Text”, MB_OK);
}
【线程和多线程】
创建线程,就是创建一个包含你想执行代码的函数。线程函数如下:
DWORD WINAPI ThreadProc(LPVOID lpParameter);
创建线程:
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAbilities, // NULL
DWORD dwStackSize, // 0
LPTHREAD_START_ROUTINE lpStartAddress, // thread function
LPVOID lpParameter, // user supplied pointer- can be NULL
DWORD dwCreationFlags, // 0
LPDWORD lpThreadId); // receives thread identifier
例子:
DWORD WINAPI MyThread(LPVOID lpParameter)
{
BOOL *Active;
Active = (BOOL*)lpParameter;
*Active = TRUE;
//...
*Active = FALSE;
ExitThread(0); // special call to close thread
}
void InitThread()
{
HANDLE hThread;
DWORD ThreadId;
BOOL Active;
hThread = CreateThread(NULL, 0, (LPTHREAD_START_ROUTINE)MyThread,
(void*) &Active, 0, &ThreadId);
while (Active == TRUE);
//...
CloseHandle(hThread);
}
【关键区】Critical Sections
使用它来保证在需要的时候,只有一个线程能够完全控制一些数据。
当激活的时候,一个关键区会阻碍所有尝试来访问共享内存(所有线程使用的程序内存)的进程。
CRITICAL_SECTION CriticalSection;
InitializeCriticalSection(&CriticalSection);
EnterCriticalSection(&CriticalSection);
// Do crucial data processing here
LeaveCriticalSection(&CriticalSection);
//...
DeleteCriticalSection(&CriticalSection);
【使用COM】Component Object Module
DirectX完全是由基于COM的组件构成的。
使用COM对象,要初始化COM系统:
// For single-threaded applications
HRESULT CoInitialize(
LPVOID pvReserved); // NULL
// For multithreaded applications
HRESULT CoInitializeEx(
void *pvReserved, // NULL
DWORD dwCoInit); // concurrency model
使用多线程程序的时候,需用CoInitializeEx,因为你必须指定dwCoInit为COINIT_MULTITHREADED。
使用完COM系统后,要关掉它:
void CoUninitialize(); // 它的调用次数应与CoInitialize同
【IUnknown】
它是所有COM接口的基类,它只有3个函数:AddRef、Release和QueryInterface。AddRef初始化它需要的,增加引用计数(实例化的)。
用QueryInterface来得到包含对象的接口(包括新的接口)。
为了依赖函数,一个对象需要从IUnknown派生,然后插入附加的函数。COM标准里说对象不能暴露它的变量,只有函数。
函数需要返回一个HRESULT表明一个错误或者成功码。
class IMyComObject : public IUnknown
{
public:
HRESULT Add(long *Num1, long *Num2, long *Result);
};
HRESULT IMyComObject::Add(long *Num1, long *Num2, long *Result)
{
*Result = *Num1 + *Num2;
return S_OK;
}
【初始化和释放对象】
使用一个COM对象,你必须用CoCreateInstance创建它:
STDAPI CoCreateInstance(
REFCLSID rclsid, // Class identifier of object
LPUNKNOWN pUnkOuter, // NULL
DWORD dwClsContext, // CLSCTX_INPROC
REFIID riid, // Reference to interface identifier
LPVOID *ppv); // Pointer to received object
你必须知道类id,以CLSID为前缀,和引用的,以IID_为前缀,表示你要找的接口。
比如你有一个叫Math且id为CLSID_MATH的类,它包含的对象有IAdd2(IID_IAdd2)。要引用IAdd2对象:
IAdd2 *pAdd2;
if (FAILED(CoCreateInstance(CLSID_MATH, NULL, CLSCTX_INPROC, IID_IAdd2, (void**)&pAdd2))
{
// Error occurred
}
所有你创建的COM对象最后都必须被释放。这就是IUnknown::Release函数的目的,在你使用完IAdd2接口后,你需要释放它:
IAdd2->Release();
【查询接口】
COM是向后兼容的。如果你有一个新的COM对象(包含新的接口),你仍然能通过这个对象访问以前的接口。这个是靠QueryInterface:
HRESULT IUnknown::QueryInterface(
REFIID iid, // Reference identifier of new interface
void **ppvObject); // New object pointer
IAdd *pAdd;
IAdd2 *pAdd2;
CoCreateInstance(CLSID_MATH, NULL, CLSCTX_INPROC, IID_IAdd, (void**)&pAdd);
if (SUCCEEDED(pAdd->QueryInterface(IID_IAdd2, (void**)&pAdd2)))
{
IAdd->Release();
}
【DirectX】
DirectX Graphics、DirectX Audio、DirectPlay、DirectInput。
【理解程序流程】Program flow
标准游戏程序:
1.初始化系统(Windows、图像、输入、声音)
2.准备数据(加载设置文件)
3.设置默认状态(一般标题屏幕)
4.开始主循环
5.决定状态并处理,通过获取输入,处理,输出
6.返回到步骤5直到程序终止,然后到步骤7
7.清理数据(释放内存资源)
8.释放系统(windows、图像、输入)
in-game处理,可以分成3部分:pre-frame(处理小任务),per-frame(更新对象、渲染),post-frame(处理遗留的函数)。
在游戏中,你可能有多个per-frame状态:一个处理主菜单,一个处理in-game显示。想那样保持多个状态会导致代码混乱,但使用state-processing 状态处理 能帮助减轻负担。
【模块化程序】Modular Programming(或者叫组件化吧)
可以把模块化程序想成C++的类,它包括自己的变量和函数。如果编写合适的话,这个类不需要外部的帮助。给你这个类,然后任何程序都可以使用其中的特性,只有知道怎样调用这些函数(通过函数声明):
cClass MyClass; // Instance the class
MyClass.Function1(); // Call a class function
为了做的真正的模块化,你需要保护数据,用c++的时候将它声明为protected,为了访问那些类的变量,你需要编写public的函数,那样在外部也可以使用(这就是COM的基础)。
class cCounter
{
private:
DWORD m_dwCount;
public:
cCounter() { m_dwCount = 0;}
BOOL Increment() { m_dwCount ++; return TRUE;}
BOOL Get(DWORD *Var) { *Var = m_dwCount; return TRUE;}
BOOL Set(DWORD Var) { m_dwCount = Var; return TRUE;}
};
【状态和过程】States and Processes
状态指的是操作的状态(state of operation),是你程序正在执行的当前过程。
当你开始添加各个状态的时候,你也需要提供一个方法来决定怎样来根据当前状态来处理这些操作的状态。每一帧你的程序决定该处理那个状态可有以下这种吓人的方法:
switch (CurrentState) {
case STATE_TITLESCREEN:
DoTitleScreen();
break;
case STATE_MAINMENU:
DoMainMenu();
break;
case STATE_INGAME:
DoGameFrame();
break;
}
当状态多或者每帧处理一个状态的时候就麻烦了。你可以使用基于状态的程序(state-based programming=SBP)。SBP将执行基于一个状态的堆栈(stack),每个堆栈代表一个对象或者函数集。当你需要函数的时候,你将他们添加到堆栈去。当你完成了这些函数,将他们移除。
你通过使用一个状态管理器(state manager)来添加、移除和处理状态。你需要引入一个状态管理器 允许函数的指针(代表状态的函数)。压栈一个状态,就添加函数的指针到堆栈去。你的工作是调用状态管理器,它来处理在堆栈最顶端的状态。(代码见ProcessManager.cpp)
使用cStateManager对象,你可以连续的添加需要的状态,在渲染帧函数中,你可以只调用Process函数。
【进程】Processes
简化帧函数的调用。使用进程管理器ProcessManager来处理所有的进程(input、netword、sound processing)而不用单个的处理。
代码见ProcessManager.cpp。
尽管这个类跟cStateManager很像,只有一个不同,它只添加进程,不移除。
注意每次Process调用的时候,所有在堆栈的进程都会被调用。当调用频率高的函数的时候比较有用。你可以为不同情况(situation)保留一个不同的进程管理器对象。比如,一个处理input和network,一个处理input和sound。
【处理程序数据】
每次你退出游戏时,你的角色的数据要被保存,以便下次加载。
【使用数据打包】
创建一个数据打包系统来处理保存和加载数据。创建一个包含数据缓存(buffer)的对象,你可添加一些函数来保存和加载。
代码见cDataPackage.cpp。
通过创建一些小的数据打包对象,你可以传递所有你要的指针,让它们自己保存和加载。
【创建程序框架】Application Framework
基础层面,一个架构应该包括初始化程序窗口、不同的引擎(图像、输入、网络和声音)、处理初始化、每帧的处理和关闭函数。
代码见WinMain.cpp。
【构造一个工程】
Arrange your entire project in easy-to-use modules that will not overwhelm you.
【调试程序】
使用assert函数,当返回FALSE的时候就会显示调试信息了。
#include <assert.h>
long dwValue = 10;
char *pPtr = NULL;
assert(dwValue == 20);
assert(pPtr != NULL); // must not be NULL
当你不想使用assert的时候,在最前面加上:
#define NDEBUG
【附件】