SpaceWarrior - 2D DirectDraw 游戏 - 第一部分






2.64/5 (17投票s)
2007 年 1 月 3 日
5分钟阅读

37645
一篇关于创建简单的 2D DirectDraw 游戏(入门)的文章。
引言
很久以前,我决定制作一款现代电脑游戏,于是立刻开始编码。但当时存在一个问题。要制作“哪种类型的游戏”?我的游戏“故事”是什么?在哪里可以找到我的游戏的“图形”?它将是“2D 还是 3D”游戏?最后,我“如何”才能完全创建游戏呢?
我的第一件作品是基于 **GDI** 的横版卷轴游戏。但是,真正的游戏开发者永远不会使用 Windows GDI 来编程电脑游戏。为什么?它并不是一个很难学习和使用的东西。任何人都可以使用键盘按键或鼠标让一个简单的精灵在屏幕上移动。那么问题出在哪里呢?
Windows GDI 对于游戏编程来说太慢了。但 BitBlt()
工作得很快,对吧?制作游戏还需要什么?
嗯,基本上,你可以只用 GDI 来制作游戏。但是当 GDI 必须访问你电脑上的显卡时,它会与 Windows DDI(设备驱动程序接口)一起工作。此外,使用 MCI 进行声音播放还会进一步降低系统的性能。但是,你仍然可以勉强制作一个简单的 2D 卷轴游戏。如果你想制作类似《帝国时代》的策略游戏呢?或者任何类型的 3D 游戏呢?用 GDI 就行不通了。你必须寻找另一个替代品。
然后,我意识到我需要 **DirectX** 及其组件。但是,我对它一无所知。每个人都说要自己学习它非常困难。我深知,微软的文档虽然涵盖了一切,但并不是最好的学习方式。
然后,我在 Google 上搜索“电脑游戏书籍”。出现了许多书籍,但其中一本与众不同。它的名字对全世界的电脑游戏开发者来说都非常熟悉——安德烈·拉莫特(Andre LaMothe)的《Tricks of the Windows Game Programming Gurus》(《Windows 游戏编程大师的技巧》)。
这本书大约有 1000 页,涵盖了游戏编程任务的每一个部分。而这个列表中并没有一个任务……所以,在这里我将为你简要回顾这本书:里面有什么,如何使用它,下一步去哪里……
本书
阅读本书将直接引你进入第一个游戏编程问题:“要创建哪种类型的游戏”?所以,你必须决定第一个游戏要创建的类型。它必须是一个简单的游戏,只是为了学习游戏开发过程的概念。好的,我说,它将是一个 2D 射击游戏。3D 游戏仍然超出了我的能力范围,所以我不会在这里浪费你的时间。
游戏的故事是什么?同样,要简单一些,比如你要穿越的小行星带,在消灭敌人并反击敌人的同时前进。
游戏图形?嗯,要么你窃取别人的作品,要么你自己制作。使用什么工具?一些 3D 建模软件和绘画程序就足够了。
游戏中的声音?尝试使用一些声音编辑软件来修改现有的声音或创建你自己的声音。
现在,我们已经有了将这些部分组合在一起以获得游戏所需的所有元素,对吧?差不多了,但我们仍然需要一个粘合剂将这些东西固定在一起,否则每个部分都会走向自己的毁灭之路。在这里,这本书开始混合出一些东西,将我们带出这种困境。
每个游戏是如何运作的,又是如何组织的?每个游戏都有开始、中间部分和结束。其中两部分可以为你准备好,因为它们涵盖了 DirectX 组件的加载和卸载。所以,这意味着我们的主要游戏内容在中间部分,对吧?绝对正确。那么,这个中间部分包含什么呢?这是你必须发现的一个百万美元的秘密。让我们从这本书的一点帮助开始。
在开始构建游戏之前,我们需要知道什么?精通一门编程语言就足够了,我个人偏好 C++。我们还需要一些关于 Windows 系统如何组织的常识:窗口、控件、菜单、资源等。在我们的游戏中,我们只需要一个窗口——主游戏窗口,一些上面提到的游戏资源(声音和图形),以及一个游戏故事。我们不需要标准控件或自定义控件,不需要菜单,不需要其他任何东西,只需要一颗良好的意愿和一些额外的时间。
主游戏窗口
在这里,我们将创建我们非常需要的主游戏窗口。这里不会提到 MFC,所以如果你不喜欢 Win32 SDK,那么你可以寻找其他处理该主题的游戏文章。顺便说一句,你不需要 MFC 来制作游戏。让我们在 MS Visual Studio 中创建一个空的 **Win32 项目**,并添加一个名为 main.cpp 的新文件,内容如下:
// Some usual definitions and headers #define WIN_32_LEAN_AND_MEAN #include "windows.h" #include "windowsx.h" // Declarations we use #define WINDOW_CLASS_NAME "MyWindow_Class" // Macros we use #define KEYDOWN(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 1 : 0) #define KEYUP(vk_code) ((GetAsyncKeyState(vk_code) & 0x8000) ? 0 : 1) // Some global variables that we need HWND g_hMainWnd = NULL; HINSTANCE g_hInstance = NULL; // Some global functions we need also LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam); int Game_Init(void* pParms=NULL, int numParms=0); int Game_Main(void* pParms=NULL, int numParms=0); int Game_Shutdown(void* pParms=NULL, int numParms=0); // Now comes the application entry point int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow) { // Some local variables WNDCLASSEX winClass; HWND hWnd; MSG msg; // Fill window class structure which describes the main game window winClass.cbSize = sizeof(WNDCLASSEX); winClass.style = CS_DBLCLKS | CS_OWNDC | CS_HREDRAW | CS_VREDRAW; winClass.lpfnWndProc = WindowProc; winClass.cbClsExtra = 0; winClass.cbWndExtra = 0; winClass.hInstance = hInstance; winClass.hIcon = LoadIcon(NULL, IDI_APPLICATION); winClass.hCursor = LoadCursor(NULL, IDC_ARROW); winClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); winClass.lpszMenuName = NULL; winClass.lpszClassName = WINDOW_CLASS_NAME; winClass.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // The main game window will be black with default Windows icon and cursor // Save application instance handle g_hInstance = hInstance; // Register window class if (!RegisterClassEx(&winClass)) { return (0); } // Create the main game window if (!(hWnd = CreateWindowEx(NULL, WINDOW_CLASS_NAME, "Main Game Window", WS_POPUP | WS_VISIBLE, 0, 0, 400, 300, NULL, NULL, hInstance, NULL))) { return (0); } // Save main game window handle g_hMainWnd = hWnd; // Initialize game here if (!Game_Init()) { return (0); } // Enter main event loop while (TRUE) { // Test for a window message if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE)) { // Test for quit message if (msg.message == WM_QUIT) break; // Translate any accelerator keys TranslateMessage(&msg); // Send message to window procedure DispatchMessage(&msg); } // Main game processing here if (!Game_Main()) { break; } } // Shutdown game here if (!Game_Shutdown()) { return (0); } // Return to Windows return (msg.message); }
所以,你在这里看到了我们游戏的所有三个部分。只有 Game_Main()
对我和你来说才有趣,因为其他两个(Game_Init()
和 Game_Shutdown()
)将来可能会改变。我们将在这一单个函数调用中编写所有的游戏逻辑。为什么?因为我们不需要更多了。
消息循环
这部分很简单。上面我们提供了名为 WindowProc()
的主游戏窗口消息过滤器函数。它在这里:
LRESULT CALLBACK WindowProc(HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam) { // Check the message switch (msg) { case WM_DESTROY: { // Send quit message PostQuitMessage(0); } break; default: { // Default message processing return DefWindowProc(hWnd, msg, wParam, lParam); } break; } return (0); }
我们可以在此函数的正文中处理用户的所有键盘或鼠标输入,我们可以销毁主游戏窗口,或者创建另一个窗口,暂停我们的游戏等。直到这一部分,所有 Win32 应用程序都是完全相同的。现在是游戏部分。
游戏部分
我们上面声明了三个游戏函数:Game_Init()
、Game_Main()
和 Game_Shutdown()
。它们在这里:
int Game_Init(void* pParms, int numParms) { // We will init the DirectX sub-system here return (1); } int Game_Main(void* pParms, int numParms) { // We will write our game logic here // Test for ESC key pressed if (KEYDOWN(VK_ESCAPE)) { // Destroy main game window PostMessage(g_hMainWnd, WM_CLOSE, 0, 0); } return (1); } int Game_Shutdown(void* pParms, int numParms) { // We will shutdown the DirectX sub-system here return (1); }
这就是创建主游戏窗口所需要的一切。你只需要游戏元素……
结论
你可以构建本文提供的示例并运行它。你应该看到一个 400x300 像素的黑色窗口,按键盘上的 ESC 键可以关闭它。