3D游戏编程入门探险第一章整装待发
l 前传
我写这个系列的目的之一其实是为了自己学习3D游戏编程,因为到现在为止我还只能算是一个初学者,我想通过写日志的方式来督促和激励自己的学习过程,也可以和大家分享学习的快乐;目的之二是因为在网上关于DX游戏编程的入门教程少之又少,希望我把自己的学习经历写下来能对初学者有所帮助。
我说过我也是一个初学者,所以如果发现本文中有错误,请您谅解,并给我写信或是留言告诉我,我的邮箱地址是:mickeyzzq@hotmail.com,在下先行谢过!
l 清点装备
热爱和激情:学习游戏编程将是一个痛苦但充满惊喜的探险之旅,只有热爱和激情才能坚持不懈,失去激情的时候,就需要同道中人的鼓励和支持。
基础编程知识:一门编程语言和基本的数据结构和算法知识,编程语言可以是C/C++/VB/Delphi等,具体的选择视自己的爱好而定,当然最好是C/C++。
Windows编程知识:指的是Windows SDK,当然不需要很精通,知道怎样调用就可以了。
数学知识:这个就不用说了,如果你忘记了,就在需要的时候找出来复习一下吧。
DircetX:我们的目的是学习Windows下的游戏编程,最好的选择当然是DirectX,这正是我们要学习的。
COM
What is COM? COM 是什么呢?COM 就是 Component Object Model, 组件对象模型。COM 接口和C++的抽象类相似(但不一样),就像抽象类没有与之相关的实际代码一样,COM描述了一套符号和语法而非实现过程。你也可以把 COM 对象就想象成一套为某个主题而设计的一整套库函数。DX 就提供了一套完整的设计3D游戏的库。最棒的就是,使用DX时,你不必去直接接触硬件,而由DX帮你代理了。这使得一些事情变得简单了。
使用 COM 时应该注意,必须在程序结束前释放所有的 COM 对象(或接口)。而且,释放它们的顺序应该和创建它们的顺序相反。就像这样:
1. Create interface A.
2. Create interface B.
3. Release interface B.
4. Release interface A.
调用它们的 Release 模块来释放它们。
Page Flipping (页翻动)
页翻动又是什么呢?嗯,你知道电影的原理吗?电影通过以每秒钟24幅的速度连续的闪动图像,每幅图像之间的差别又很小,由于人眼的滞留作用,我们看到的画面就是连续的了。这不难理解。其实,DirectX 也是这样工作的。我们把要显示的物体通通绘制到一个不可见的页上,我们称这个页为“后缓冲区”。绘制完后,快速的把它翻动到可见的“前缓冲区”上,并重复这个过程。当用户正在观看新绘制的可见页(前缓冲区)时,程序要降下一幅要显示的东西绘制到“后缓冲区”上。快速而连续的重复次过程,用户就会看到像电影一样连续的图像了。不过一般情况下,我们每秒钟能绘制的页数要比电影多很多。
如果我们不是用页翻动技术,那用户看到的屏幕中的物体,将会一个个的被绘制出来,虽然速度可能很快,但效果会很差,那并不是我们想要的。
所以,我们的游戏需要一个循环,称之为“Game Loop”。每次循环,我们都要清除“后缓冲区”,把该绘制的物体按照一定的逻辑都绘制到那上面,然后把它翻动到“前缓冲区”上,然后进入下一次循环。这个循环得直到游戏退出了才能结束。有时我们可能需要好几个这样的“后缓冲区”(多缓冲)来组成一个“交换链”(Swap Chain),以求更好的效果。
Devices (设备)
What is a device? 设备是什么?简单的说,就是你的3D卡。 你得创建一个接口来代表设备,然后使用那个接口来绘制东西。
Game Loop (游戏循环)
什么是游戏循环呢?游戏循环是一段代码,在游戏退出之前循环执行的代码。这段代码在每次循环中都要:在屏幕上绘制物体(或场景人物随便什么)、处理游戏的逻辑过程(如:物体的移动、人工智能等等)、处理Windows的消息等等。基本上就是这样了。
Creating Your First Project (创建你的第一个项目)
译者:嗯,这部分我就不用译了吧,这可是基础的东西。不过还是说说初学者容易忽略的一点:一定要把你的DX SDK的Include目录和Lib目录的路径添加到VC的目录设置列表中去,而且不要把Include和Lib的地方放错了,而且还要放在第一位。还要把d3d8.lib添加到项目设置的Lib列表中,否则编译不了。
Okay, that’s enough theory lets get started. Follow the step-by-step guide below to create your first DirectX Graphics project.
In Visual C++ create a new Win32 Application.
File > New
From the Projects tab select Win32 Application
Enter a name for your project such as “DX Project 1”
Select a folder for the location of your source code files
Click Next
Select the empty project option.
Click Finish
Make sure that your project settings are correct.
Project > Settings...
On the Link tab, make sure that "d3d8.lib" is in the list of Object/Library Modules. If it isn’t simply type it in.
Make sure that your search paths are correct.
Tools > Options > Directories Tab
In the "Show directories for" drop-down, select "include files".
If it does not exist already, add the following path: <SDK INSTALL PATH>\include.
Make sure that this path is at the top of the list by clicking on the up arrow button (if needed).
In the "Show directories for" drop-down, select "library files".
If it does not exist already, add the following path: <SDK INSTALL PATH>\lib.
Make sure that this path is at the top of the list by clicking on the up arrow button (if needed).
Add the source code.
File > New
From the Files tab, select C++ Source File
Enter a filename such as “Main.cpp”
Copy the code segment below, and then paste it into your new file.
Build and Run the program.
Press F7 to build your project
Press F5 to run
下面就是本章的例子了,好好研究吧,不难。
#include <d3d8.h>
LPDIRECT3D8 g_pD3D = NULL;
LPDIRECT3DDEVICE8 g_pD3DDevice = NULL;
HRESULT InitialiseD3D(HWND hWnd)
{
//First of all, create the main D3D object. If it is created successfully we
//should get a pointer to an IDirect3D8 interface.
g_pD3D = Direct3DCreate8(D3D_SDK_VERSION);
if(g_pD3D == NULL)
{
return E_FAIL;
}
//Get the current display mode
D3DDISPLAYMODE d3ddm;
if(FAILED(g_pD3D->GetAdapterDisplayMode(D3DADAPTER_DEFAULT, &d3ddm)))
{
return E_FAIL;
}
//Create a structure to hold the settings for our device
D3DPRESENT_PARAMETERS d3dpp;
ZeroMemory(&d3dpp, sizeof(d3dpp));
//Fill the structure.
//We want our program to be windowed, and set the back buffer to a format
//that matches our current display mode
d3dpp.Windowed = TRUE;
d3dpp.SwapEffect = D3DSWAPEFFECT_COPY_VSYNC;
d3dpp.BackBufferFormat = d3ddm.Format;
//Create a Direct3D device.
if(FAILED(g_pD3D->CreateDevice(D3DADAPTER_DEFAULT, D3DDEVTYPE_HAL, hWnd,
D3DCREATE_SOFTWARE_VERTEXPROCESSING, &d3dpp,
&g_pD3DDevice)))
{
return E_FAIL;
}
return S_OK;
}
void Render()
{
if(g_pD3DDevice == NULL)
{
return;
}
//Clear the back buffer to a green colour
g_pD3DDevice->Clear(0, NULL, D3DCLEAR_TARGET, D3DCOLOR_XRGB(0, 255, 0), 1.0f, 0);
//Begin the scene
g_pD3DDevice->BeginScene();
//Rendering of our game objects will go here
//End the scene
g_pD3DDevice->EndScene();
//Filp the back and front buffers so that whatever has been rendered on the
//back buffer will now be visible on screen (front buffer).
g_pD3DDevice->Present(NULL, NULL, NULL, NULL);
}
void CleanUp()
{
if(g_pD3DDevice != NULL)
{
g_pD3DDevice->Release();
g_pD3DDevice = NULL;
}
if(g_pD3D != NULL)
{
g_pD3D->Release();
g_pD3D = NULL;
}
}
void GameLoop()
{
//Enter the game loop
MSG msg;
BOOL fMessage;
PeekMessage(&msg, NULL, 0U, 0U, PM_NOREMOVE);
while(msg.message != WM_QUIT)
{
fMessage = PeekMessage(&msg, NULL, 0U, 0U, PM_REMOVE);
if(fMessage)
{
//Process message
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else
{
//No message to process, so render the current scene
Render();
}
}
}
//The windows message handler
LRESULT WINAPI WinProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
switch(msg)
{
case WM_DESTROY:
PostQuitMessage(0);
return 0;
break;
case WM_KEYUP:
switch (wParam)
{
case VK_ESCAPE:
//User has pressed the escape key, so quit
DestroyWindow(hWnd);
return 0;
break;
}
break;
}
return DefWindowProc(hWnd, msg, wParam, lParam);
}
//Application entry point
INT WINAPI WinMain(HINSTANCE hInst, HINSTANCE, LPSTR, INT)
{
//Register the window class
WNDCLASSEX wc = {sizeof(WNDCLASSEX), CS_CLASSDC, WinProc, 0L, 0L,
GetModuleHandle(NULL), NULL, NULL, NULL, NULL,
"DX Project 1", NULL};
RegisterClassEx(&wc);
//Create the application's window
HWND hWnd = CreateWindow("DX Project 1", "www.andypike.com: Tutorial 1",
WS_OVERLAPPEDWINDOW, 50, 50, 500, 500,
GetDesktopWindow(), NULL, wc.hInstance, NULL);
//Initialize Direct3D
if(SUCCEEDED(InitialiseD3D(hWnd)))
{
//Show our window
ShowWindow(hWnd, SW_SHOWDEFAULT);
UpdateWindow(hWnd);
//Start game running: Enter the game loop
GameLoop();
}
CleanUp();
UnregisterClass("DX Project 1", wc.hInstance);
return 0;
}
你会得到一个绿色背景的窗户(读者:是窗口,白痴),虽然看起来不怎么样,但至少是一个好的开始,不是吗?好地开头,就是成功地半拉!(读者:… …)
(译者:嗯,那个老外用WinXP?我看还是Win2K好… …)
(读者:切~~别在那废话。)
WinMain
此乃Win32程序的入口点,代码会从这里开始执行。这是我们注册、创建、显示窗口的地方。然后,我们要初始化Direct3D并进入游戏循环(Game Loop)。
WinProc
这是应用程序的消息处理过程。Windows发给我们的程序的消息,都要有它处理。注意我们上面的例子,我们处理了两种消息:WM_DESTROY(结束程序) 和 WM_KEYUP(有按键被按下)。其他的消息我们都交给默认消息处理过程DefWindowProc处理了。
g_pD3D
这是指向IDirect3D8接口的指针,我们得通过它来创建Direct3D设备接口。
g_pD3DDevice
这是 IDirect3DDevice8(D3D 设备) 的接口的指针,它实际上代表了你的硬件3D卡。
InitialiseD3D
我们的这个函数是用来初始化Direct3D的。首先,创建IDirect3D8对象,通过这个对象我们得到了当前屏幕的显示模式。然后,根据刚刚我们获取的信息(显示模式),创建了兼容的设备。
译者:因为一些显示卡的显示模式并不相同,所以,我们的程序要在每块显卡上都能执行,就要了解这块显卡的显示模式等等一些信息。然后,根据这些信息,创建我们的D3D设备。
GameLoop
这就是上面提到的“游戏循环”的具体实现了。当初始化结束后,它就开始了。
他会检测有没有Windows的消息,没有的话,就调用Render来绘制屏幕。
Render
首先,我们清除了后缓冲区准备作画。然后,调用了BeginScene函数来告知DX我们要作画了。这时,我们就可以作画了(教程2)。结束了绘制,我们调用EndScene来告知DX我们画完了。最后,我们调用了Present来完成关键的一步:翻动后缓冲区到前缓冲区(屏幕)。这时,用户就能看到我们画的东西了。
CleanUp
我们在此做清洁工作:释放所有的被创建的对象。因为程序要推出了。