目录: 零、基础篇的目的 一、游戏的动画原理 二、基于动画原理的提高 三、游戏的基石: 窗口 的建立 四、游戏制作利器: 引擎 的选择
文章内容:
零、基础篇的目的
有了一个目标之后,往往人们比较有信心和动力。所以,我重新修改了一下教程的安排,在这儿插入了“零”篇。
在基础篇里,我们将逐步学习关于游戏制作的基本知识,为后续的开发工作奠定基础。那么,在基础篇结束的时候,我们可以弄出个什么东西呢?我们看图:
|
这是我写的一个演示程序的截图,暂时定为我们基础篇教程的目标。
该演示程序包含了一些基本要素,如: Windows 程序的基本框架、DirectDraw 的基本使用方法、精灵的使用和 一些简单的关于 DirectInput 的东西。但是,这个程序没有对障碍和遮挡进行处理,也就是说,人物会走到任何地方去。因为一些关于地图方面的知识,我将会放到提高篇里。
注: 本范例在以下环境中通过:VC6.0、DirectX 7.0/8.1 SDK、Win2000/WinXP
附件: 说明 [本范例演示程序下载]
一、游戏的动画原理
其实现在网上关于游戏编程的技术文章越来越多了,但是我发现关于最基本的了解游戏的文章还是比较少的。大多数文章是以 DirectX 作为开始教学起始的,因此,我觉得花点时间写下这篇文章还是值得的。今天,我就开始介绍一些关于游戏编程的基础东西,以便大家能够真正的开始了解游戏,从而能够很快的转入游戏开发。由于是针对新手的,如果您已经转入程序编写阶段,您完全可以抛开了。我将采用一种不同于网络上现有的教学方法,来讲一下游戏程序开发的奥秘:)
大家可以看到游戏中主角连贯的出招动作、华丽的场景、震撼的战斗效果,这一切似乎很难让人想象程序是怎么实现的。也许您在上课无聊的时候尝试过在课本的角上画上几个人物动作的分解图,然后一遍又一遍地翻着它,觉得很好玩。其实您已经在无形之中接触了游戏动画的基本原理。其实游戏动画的步骤可以想象成这样:
手中拿着两张纸,把一张放在后面。其实这个就是 DirectDraw 的两个成员。我们先把一个分解动作画在背后的那张纸上。那么,我们当前看到的就是一个“白屏”而已。然后,我们“快速地”将后面的那张纸拿到前面来。呵呵,你现在看到的是第一个分解动作了吧!那么,怎么“快速地”呢?不要紧张,这些问题都被 DirectDraw 完美的解决了,别急,今后会详细的讲解的。现在,当前的两张纸已经交换了,而且也看到了动作(一个静态的而已),那么后面的“白纸”怎么办呢?我们先拿“橡皮”将纸擦一遍,然后,将第二个分解图画上,接下来?呵呵,自己干吧,应该明白了吧。经过再次的交换,我们已经在屏幕上看到第二个动作了。我们继续把后面的纸擦干净,再画第三个动作,再交换,继续下去......由于我们的“快速地”动作相当快,所以感觉不到有任何问题。
或许有人会问:为什么不直接在第一张纸上进行“擦->画->擦->画”的动作呢?这个就是为了我们平常所说的“闪屏”问题而进行的解决方案。由于直接进行动作,速度相对较慢,有时用户会在屏幕上看到一闪一闪的现象。我们用“两张纸”的话,就完美的解决了这个问题。(啊?还闪屏?呵呵,你小子把显示器坏了的问题都怪我啊·#¥%……*)
二、基于动画原理的提高
既然上面的游戏的“内幕”已经掌握,那么我们来看看在上一节中涉及的“武器”和基本知识。或许本篇所涉及的东西是基于理论的多数,但是,这将为理解在后面即将写的程序部分会打下很好的基础的。所以咬咬牙,看完吧!(啊?没有牙了啊?大家应该鼓掌吧!连牙都没有长齐的“3、4点种的太阳”都开始学习了,你们还有理由吗?恩?是大娘啊?那更应该值得学习了!跑题:)
首先要介绍的第一位主角是 Windows 编程中的必要元素: RECT 。是英语 rectangle 的简写,意思是矩形。它有什么用呢?我们在上面不是讲到了动作的分解动作吗?我们看右图:
这个图就是一个简单的行走动作分解图,复杂的可能有10帧左右哦:)那么怎么在程序中实现自动在纸上画出正确的图片呢?(其实我一直在考虑是否将这部分内容加上,因为实在太基本了。但是每个人都这么想的话,基础的谁来教呢?算了,让别人的口水淹死我吧!)假设您已经有点 C++ 语言的基础了。这个教我教的话,说不过去吧:)请看下面的代码:
#define m_Width 32 // 每个动作的 宽度 #define m_Height 48 // 每个动作的 高度 |
|
void ShowThePic() { static RECT rect; // 矩形对象,用于精确定位所要的当前动作 static int CurrentFrame = 0; // 当前动作的编号 static int Direction = 0; // 当前的方向 rect.top = Direction; rect.bottom = (Direction+1)*m_Height; // 对当前矩形的大小定义,数学的问题哦 rect.left = CurrentFrame; rect.right = (CurrentFrame+1)*m_Width; // 根据英文的意思也可以知道在给谁赋值 BltPicToScreen(); // 一个伪函数,作用是将当前矩形内的图形复制到屏幕上。 CurrentFrame++; if( CurrrentFrame==3 ) CurrentFrame = 0; // 这个步骤能够保证动作的循环 }
不知道这么个函数你能否看懂。之所以要采用 static 静态变量,是因为我们这个函数程序要循环运行。如果直接写个 int 的话,每次执行都会被赋回原值 0,那么图片就不会变了。
上面这段代码其实并不是那么理想。因为程序自己在那儿一个劲地运行,完全没有我们控制的份儿。别急!来看下面这份修改过的伪代码:
#define m_Width 32 // 每个动作的 宽度 #define m_Height 48 // 每个动作的 高度
void ShowThePic() { static RECT rect; // 矩形对象,用于精确定位所要的当前动作 static int CurrentFrame = 0; // 当前动作的编号 static int Direction = 0; // 当前的方向 rect.top = Direction; rect.bottom = (Direction+1)*m_Height; //对当前矩形的大小定义,数学的问题哦 rect.left = CurrentFrame; rect.right = (CurrentFrame+1)*m_Width; //根据英文的意思也可以知道在给谁赋值 BltPicToScreen(); // 一个伪函数,作用是将当前矩形内的图形复制到屏幕上。
if( LeftArrowDown() ) // 如果 左箭头 被按下 { Direction = 1; // 赋值方向为 1 CurrentFrame++; }
if( RightArrowDown() ) { Direction = 3; CurrentFrame++; }
if( UpArrowDown() ) { Direction = 2; CurrentFrame++; }
if( DownArrowDown() ) { Direction = 0; CurrentFrame++; }
if(CurrrentFrame==3) CurrentFrame = 0; // 这个步骤能够保证动作的循环 }
经过这么一修改,问题再次得到解决。上面的代码就能够响应用户的操作了。当然,你这么直接在程序里输入这些代码是不行的:)因为是“伪代码”。你得根据实际情况,自己相应地做些修改,才能使程序运行!
(题外话:不晓得这么进行教学,你是否能够一点一点的积累到知识?这种教程是不是合适?请到论坛内发表意见,我真的很想能够摸索出大家接受的方法,有利教学)
欢迎回到教程中来!有人或许会问,为什么这么麻烦要把图形放在这么个图片里头,不一个动作一个图啊?呵呵,想想,那要多少图片啊,很难于管理的。况且,这样并不能避免使用这种常用手法,因为 RECT 已经是一个成员。游戏里不能不用他的!
哇,口水干了。(其实是手累了:)我们下回再见吧。
下回预告: 少年侦探柯南为了查清楚事情的真相,他......(?还真的预告啊?) 不好意思,习惯性用语了:)在下回,我会讲一下,如何将今天的代码整合到程序中去。
三、游戏的基石: 窗口 的建立
Welcome back!很高兴再次与你相遇教程。废话不多说,继续。
上次的代码片段,说实在,拿在手里没有用。为啥?因为不能运行的啊:)我们这次就按照上次的安排,讲如何将显示图片的代码片段整合到程序中,让它能够具体的体现出来。
我将在这儿讨论 Windows 编程,而不是 MFC 。关于 MFC 和 Windows 的不同和各自的优点,我们不多涉及了。 MFC 就是微软的一个封装开发库,极大程度的降低了 Windows 开发的复杂。但是,我们还是继续 Windows 编程,呵呵。
我们来看,一个基本的 Windows 程序是一个窗口对吧:)我们所玩的游戏,其实也是有窗口的,只不过是看不见的,而且被 DirectDraw 掩盖了而已。那么,如何创建一个窗口呢?我们来看:
BOOL Init(HINSTANCE hInst, int nCmdShow) { HWND hWnd; // 窗口的句柄,就是一个储存窗口的对象 WNDCLASS WndClass; // 用于注册窗口的对象
WndClass.style = CS_HREDRAW | CS_VREDRAW; // 定义窗口的类型 WndClass.lpfnWndProc = WinProc; // 指定了窗口消息的处理函数 ** 关键! WndClass.cbClsExtra = 0; // 没有特定意义 WndClass.cbWndExtra = 0; // 没有特定意义 WndClass.hInstance = hInst; // 窗口的实例 WndClass.hIcon = LoadIcon(hInst, IDI_APPLICATION); // 指定窗口的图标 WndClass.hCursor = LoadCursor(hInst, IDC_ARROW); // 指定程序的指针 WndClass.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); // 指定背景色为黑色 WndClass.lpszMenuName = NULL; // 指定菜单为无,游戏不需要 WndClass.lpszClassName = "GDIM_GAME_ENGINE"; //这个程序注册的名字 RegisterClass(&WndClass); // 注册程序
hWnd = CreateWindowEx(WS_EX_TOPMOST, "GDIM_GAME_ENGINE", "GDIM_GAME", WS_POPUP, 112, 84, SCREEN_WIDTH, SCREEN_HEIGHT, NULL, NULL, hInst, NULL); // 这段代码就是实现了一个窗口的创建 // 我们可以通过来检测是否创建成功。如果失败将返回一个 FALSE 的值。 if(!hWnd) return FALSE; ShowWindow(hWnd, nCmdShow); // 显示窗口 UpdateWindow(hWnd); // 更新窗口 }
好了,到这儿呢,我们就成功的创建了一个窗口。我们回头看我注了“**”的地方,关于这个函数,我们得写个同名函数来处理 Windows 的消息。呵呵,不好意思,再看一个函数:
BOOL bActive = FALSE; // 用于判断程序是否运行的变量
long PASCAL WinProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam) { switch(message) { case WM_ACTIVATE: bActive = TRUE; // 当程序被激活时,赋值 TRUE break; case WM_SETCURSOR: SetCursor(NULL); // 设定鼠标为不显示 return TRUE; case WM_KEYDOWN: switch(wParam) { case VK_ESCAPE: // 处理按下 ESC 键的反应 PostMessage(hWnd, WM_CLOSE, 0, 0); // 发送一个关闭窗口的命令 break; } break; case WM_DESTROY: // 在窗口即将销毁时的反应 PostQuitMessage(0); // 发送一个结束的消息,必须的! break; } return DefWindowProc(hWnd, message, wParam, lParam); // 一些没有像上面一样具体定义的消息的处理 }
这个函数就是用来处理 Windows 的消息的,是一个标准程序必要的。
再来最后一个重要的函数。不好意思哦,实在是不想这么写,但是想想,在这个函数结束后,你就可以实现一个具体的窗口了哦:)
int PASCAL WinMain(HINSTANCE hInst, HINSTANCE hPrevInst, LPSTR lpCmdLine, int nCmdShow) { MSG msg; // 定义一个消息的对象
if(!Init(hInst, nCmdShow)) return FALSE; // 还记得上面的那个函数吗?
while(1) // 程序的循环 { if(PeekMessage(&msg, NULL, 0, 0, PM_NOREMOVE)) // 不要特别注意的 { if(!GetMessage(&msg, NULL, 0, 0 )) return msg.wParam; TranslateMessage(&msg); DispatchMessage(&msg); } else if(bActive) // 就是上面的“程序是否激活”的变量判断 { ShowThePic(); // 显示人物图片的函数,上节的内容,就是整合在这儿的! } else WaitMessage(); // 没有消息时,就等消息:) } }
其实到这儿,一个简单的程序已经可以出炉了:)编译,运行,你将看到一个黑色的窗口。这个窗口不同于一般的窗口,它没有平常的标题栏,这个才是适合我们游戏的。我在这儿给出完整的代码,但是,无法将上节的内容以程序的形式表现出来,因为涉及到了 DirectDraw 。我不想过多在教程中涉及未讲的知识,因为那样会影响对现有知识的理解。
下章教程,我将开始介绍 DirectDraw 了,以便能尽快将显示的内容整合,同时我会保持教程和代码的详尽。
注: 本范例在以下环境中通过:VC6.0、DirectX 7.0/8.1 SDK、Win2000/WinXP
附件: 说明 [本篇的代码下载]
四、游戏制作利器: 引擎 的选择
真的很高兴能够继续我们的教程。大家也一定等了许久了,怪我太懒。
上次,我们讲到了窗口的建立,并且附带了源代码,不知道大家有没有编译和运行。不是当老师的唠叨哦,大家一定要试试的,因为电脑这个东西,尤其是编程,要不断地实践才能掌握的。
今天,我们来点轻松的,不涉及代码,我们来一次“纸上谈兵”。我今天是要大家选择适合自己的游戏引擎。曾经有很多网友问我:“引擎 ”到底是什么东西?我也回答过许多遍了,但是,我从没有嫌烦过,因为我也是这么过来的。之所以取名叫“引擎”,他就像汽车之类的 Automobile 一样,得有一个家伙驱使他工作。而游戏引擎呢,他就负责接受用户输入,交付自己的内部工作机构,处理,并最终以声音、图像等形式表现出来。
现在大家在网络上可以看到、并且下载到许多免费的游戏引擎,其中有2D的,也有时下流行的3D。国内有许多网站也在制作各自的引擎,有个别的的确有很高的效率,而且使用很方便,比如 云风 的 “风魂游戏开发库”。这些引擎都是将我们今后会碰到的 DirectDraw、DirectInput、DirectPlay 等等 DirectX 部件整合,封装,使其便于使用。像我其实自己也在使用别人的引擎来开发自己的游戏制作库,那么,你该选择怎么样的引擎呢?我觉得这个完全取决于你对游戏开发的耐心及专研。
现在,DirectX 已经发展到了 9.0 ,而且,在 8.1 的开发库中,我们很高兴的看到,微软也已经封装了以前繁琐的步骤,因此,DirectX 8.1b 的开发库,不失为一个比较好的基层函数库。为什么说他还只是一个基层的呢?因为他还没有像著名的 CDX 那样有比较高级的效果封装在里头。CDX 是我比较喜欢的封装库,他不仅使用简便,而且可以实现使用频率比较高的功能,比如 Alpha 混合,虽然他不像其他的引擎来得功能强大,但是,他用于自己的研究和扩充是很好的,通过使用 CDX ,你对 DirectX 可以有个比较理性的认识。
到这儿,大家应该发现了,我们以前公布的教程目录和今天的对不上号,因为我发现现在其实有好多网站都有关于 DirectDraw 编程的辅导,而且,资源丰富。我相信,大家通过好好的分析这些代码,然后,大量的实践,就会打造你比较坚固的 DirectDraw 编程的基础了。
我们今后的教程往哪个方向走,我正在考虑,我也很想看到大家的好建议,作为一个游戏编程入门者,你最想看到怎么样的文章?哪怕是一个不明白的地方,都可以提出来,我可以做专题来给大家讲解一下。DirectDraw 方面的,专题在近期是不会出现了,还请大家见谅!
最后,告诉大家一个可能算是比较好的消息,我们的教程游戏,还在更新和完善之中哦...
待续……
|