游戏编程101

游戏编程101
2011年11月21日
  [b]游戏编程[/b][b]101[/b]
  [b]前言[/b][b][/b]
  我曾经写过一些小的关于游戏初学者的编程指南,现在我决定把这些小文章组织一下,形成一个系列指南,当然了,是针对初学者的。(老鸟就不用看了)我一直怀疑游戏编程是否能给你带来财富,但是世事如棋,努力的工作加上一点想象力,你可能发大财呀!(我并没有想向你要钱的意思)。
  最初,我考虑连同一个小游戏同时讲解,直到游戏完成,后来我放弃了这个想法,决定写一篇广泛一点的文章。只要我有时间,我就会写下去的。
  阅读本篇文章,你学要有一些C和C++的基础知识,原因是我经常混合使用它们,因为有些部分用C表达好一些,而有些部分用C++表达好一些。如果你只熟悉其中的一种,我想不会影响你看懂这篇指南的。如果你真的遇到困难,你可以写信给我,我将竭力帮助你。
  当然了,我如果没能达到我所预期的目的,我也是同你一样的人,难免会出错。如果你发现任何错误,请告诉我,谢谢。
  开始吧!
  [b]非常初级[/b][b][/b]
  实话实说,任何一个希望自己有一个光明前途的游戏程序员,至少要了解DirectX。当然,还有其它的工具,如图形工具OpenGL和最新的音频工具OpenAL,但是近期的工业标准是DirectX,所以我们就用它。你还需要一个编译器,能够编译成Win32可执行文件的。(这里我们将使用Microsoft Visual C++6.0,当然,其它的也可以)和一个DirectX SDK软件包。(我们将使用7.0版本)你可以从Microsoft的主页上下载。
  我也假设你知道怎样编译普通的Win32文件和使用扩展库。使用DirectX前需要一点准备,下面我介绍一下在Visual C++6.0中的配置DirectX的过程:
  首先点击Tool菜单,选择Options,然后点击Directories,在Show Directories for 组合框下拉菜单中选择Include files,增加一个新的目录。将你的DirectX的路径填入。(例如:C:\DXSDK\include)然后将它移到列表的第一位,使编译时第一个寻找它(防止寻找老版本)。然后选择Show Directories for组合框下拉菜单中的Library files,方法同前,只是把\include改成\lib。现在,你已经设置完了DirectX。你仍然需要手动增加一些库文件到你的项目中,但先不急,我将在下一部分讲它。
  我们将着手第一个游戏Pong Clone(不知道怎么翻译,惭愧),你有很多途径去编写游戏,我将介绍我喜欢的一条途径,如果你喜欢其它的,随便你了。我坚持用它是因为我当学徒期间一直用它。
  我的编写游戏过程是:
  1. 设计完整的游戏
  2. 建立所有的游戏资源、游戏引擎和游戏必备的工具。
  3. 在测试平台上开发各个部件。
  4. 组合各个部件为一个完整的游戏
  5. 测试和更正。
  6. 定草稿
  7. 让公众测试
  我喜欢用以上步骤完成游戏的制作;如果你有自己的,最好用自己的。
  提醒注意:第三条可以理解为边开发各个部件边测试各个部件。
  [b]▲第一个设计[/b][b][/b]
  当我最初编写游戏时,我从来没有事先设计过,我认为这是一项乏味的工作,我不喜欢它(现在我也不太喜欢)。因为我认为游戏在我的头脑中,设计或者是不设计,根本就不会影响我编写程序代码。现在,我知道我错了,完全的错了。事先设计好游戏,可以使你全面的考虑好整个游戏,不至于有大的纰漏,更不至于当你写完500行代码后,发现这个结构就不应该这么定义,这个周末的工作被扔进了垃圾箱。尽管我们所要编写的这个Pong游戏是一个十分小的游戏,但我们仍然先在纸上把它勾画出来,而不是只在头脑中勾画。当然,如果你有自己的设计模板,用你自己的好了,萝卜青菜各有所爱吗!我习惯于自己的方式并且它可以使我更舒服的设计自己的游戏。
  ◎ 简介
  这是一个简单的游戏介绍。该游戏运行在Windows平台,需要DirectX插件。进入游戏后,游戏者控制的飞船要反击另一个游戏者或电脑,目的是使球通过目标区域。
  ◎ 最小的说明书
  你可能想忽略设计文档的这一部分,但是它的确是组成完整游戏设计文档的必备部分。
  奔腾166以上CPU
  16M以上显存
  3M以上硬盘空间
  与DirectX完全兼容的显示卡
  Windows 95,98或者2000
  ◎ 游戏规则
  这一部分是必不可少的,即使再小的游戏也需要。大游戏可能要把这部分分解成好几部分。
  本游戏的规则十分直接和容易,游戏的目的是得分。游戏操纵者要使球通过目标线,而对手(另一个游戏者或电脑)不让球通过。优秀者控制的飞船只能在固定的区域移动。【我没有玩过,所以只能翻译成这样,领会精神】
  ◎ 菜单
  这一节可以分成几个部分,当然,小游戏可能写在一个或者两个部分里面就OK了。
  主菜单由四个部分组成:新游戏、最高记录、积分榜、退出游戏。背景你可以放上一张图,任你行。
  #新游戏:弹出另一个菜单,你可以选择难易程度和单人游戏或双人游戏。
  #最高记录:显示最高的得分
  #积分榜:所有的或者前十名的分数
  #退出游戏:问是否真的要退出,选择后退出或者返回游戏
  当你暂停游戏时,会弹出一个菜单,由三个选项组成:继续游戏、重新开始游戏、退回到主菜单。
  #继续游戏:就是继续游戏
  #重新开始游戏:就是重新开始游戏
  #退回到主菜单:可以重新选择难度及其它
  ◎ 图形
  所有的图形都将从SpriteLib库产生。所有的背景图片你可以找一些星空图片代替。
  ◎ 图形引擎
  有时这一部分同设计文档分开。
  这里将制作一个图形引擎Ping,它可以从磁碟导入图片,渲染和平滑移动。
  文字部分将用GDI(Graphics Device Interface)实现。
  ◎ 游戏的操纵
  通常游戏都允许操作者自己设置操纵键,但是这里我们就武断一点,已经定义好了。
  控制飞船相当容易,第一个游戏者只需要控制两个键,UP和Down(就是上下键),另一个游戏者控制A和D键,飞船只能上下移动。(不要失望,我们只是在学习)
  ◎ 人工智能
  若是大一点的项目,我会单独拿出一个文档讨论它的。但这里就不必了。
  本游戏有四个难度级别:容易、一般、难、你疯了。
  电脑将根据选择的难度级别利用不同的计算公式和循环判断来反击游戏者。
  ◎ 进度表(时间表)
  这是编写游戏最可能出现问题的部分。几乎没有人能够精准的为游戏创作制定时间表,但游戏每一部分的生成需要一个时间表。
  这里我就不做时间表了,因为我只能利用业余的时间为大家写文章。
  这就是全部的计划文档,当然了,是按照我的喜好作的,你可以有自己的。
  [b]▲开动吧![/b][b][/b]
  §前言
  我回来了,带来了一些好消息和一些坏消息。坏消息是我们可能不能继续进行上篇文章所说的立即编写程序,原因是我们还需要介绍一些前提的工作,尤其是Win32构架和错误处理,学会了这些后,我想我们能更容易应用DirectX。好消息是文章的结尾处将要给一个完整的源代码。它可能不是很好,但它能工作。(至少我希望如此)
  废话少说,让我们干吧。
  §错误处理
  在我们介绍Win32架构前,我们先应该创立一个错误处理系统。由于用GDI在DirectX里显示文本不是很快,我们用自己的出错文本文件代替。我们还要建立一个必要的退出功能,当文件编译出错时,我们不希望死机。我们将建立Cerror类来处理所有的出错信息。类的原形将是下面的样子:
  class Cerror
  {
  public:
  FILE *fErrorLog;
  Bool bQuit;
  LPSTR lpzMessage;
  CError(LPSTR,bool);
  ~CError();
  ProcessError(DWORD);
  };
  现在我们来为每一个变量做一个简短的解释。FErrorLog是一个指向所有出错信息的指针;bQbuit是一个布尔变量,通过被赋值true或false来决定程序出错时是否退出;lpzMessage是实际的错误信息,系统将分配适当的内存给它;CError是构造函数,它有两个参数,字符串参数和布尔参数,当程序出错时,相对应的出错信息将传递给字符串参数,代表是否退出程序的true或false将传递给布尔参数。~CError()是析构函数,将释放我们所占用的内存。
  对于C语言程序员来说,类就相当于结构的功能,当然,类有更广泛的应用,但我们将不涉及它。
  下面我们将开始错误处理程序的编码。首先我们必须编写构造函数和析构函数。
  CError::CError(LPSTR lpzFileName,bool bQuitOnError)
  {
  fErrorLog = fopen(lpzFileName , “wt”);
  bQuit = bQuitOnError;
  }
  CError::~CError( )
  {
  }
  构造函数的内容很简单,首先以文本模式打开了一个以wt命名的文件,并把文件内容赋值给了fErrorLong,然后把退出标记赋值给了bQuit。
  目前,析构函数是一个空函数。
  下一部分是错误处理的核心。现在,它是非常基础的,不过我们会不断增加内容的。
  CError::ProcessError(DWORD dwError)
  {
  DWORD dwMsgSize;
  Switch(dwError)
  {
  default :
  dwMsgSize = strlen(“Unkown error”);
  lpzMessage = (LPSTR)malloc(dwMsgSize + 1);
  strcpy(lpzMessage,”Unkown error”);
  break;
  }
  if(fErrorLog != NULL)
  {
  fprintf(fErrorLog, lpzMessage);
  }
  if(lpzMessage != NULL)
  {
  free(lpzMessage);
  }
  if(bQuit == true)
  {
  if(fErrorLog != NULL)
  {
  fclose(fErrorLog);
  }
  PostQuitMessage(dwError);
  }
  return 0;
  }
  让我们仔细的看这段程序,我们首先声明了一个变量存放字符串的长度;然后用switch检测程序正在哪个错误上。马上,我们发现只有default(默认)状态,它处理了三件事:1、检查了字符串的长度;2、分配足够的内存空间给它;3、把它拷贝给类的成员变量lpzMessage。然后我们检测是否有必要把消息传送给文件并且显示出来。然后我们释放字符串占用的内存,最后检测是否应该退出程序和关闭文件。
  我想加入下面两项功能:1、无论我们想要加入一个什么样的错误检测,都应该在default之前,我们将稍后做它。2、我们没有作任何检测错误检测功能的本身是否正确,这是你的作业。试着检测一下内存是否分配正确,文件是否被成功打开等等。自己试一试,若有问题可以给我写信 akura@crosswinds.net (英文的),我将尽力帮助你。
  我们现在需要一个类的实例:
  CError ErrorHandling(“errors.log”,true);
  它是做错误处理的。
  现在让我们进入Windows的世界。
  [b]▲[/b][b]Win32[/b][b]的框架[/b][b][/b]
  据我所知,很多游戏程序员都没有系统的学习过Windows编程基础,他们往往从别的程序里拷贝窗口的框架,当然,这并没有什么错。我也并不是想教你Windows API的编程,但我将教你一点建立窗口的必备知识。
  char szClassName [] = "Chapter2";
  char szWinName [] = "Chapter2";
  LRESULT CALLBACK WndProc(HWND,UINT, WPARAM, LPARAM);
  头两个变量是类的名称和窗口的名称,我们将马上用到。第三行实质上是消息处理。
  对于DOS和UNIX的程序员来说,C/C++程序总是从void main()开始,在Windows里,从WinMain开始,并且含有几个参数,你必须需要window.h这个头文件。
  int APIENTRY WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow)
  {
  MSG msg;
  HWND hWnd;
  WNDCLASSEX wcl;
  bool bRunning;
  ZeroMemory (&wcl, sizeof (WNDCLASSEX));
  wcl.cbSize = sizeof (WNDCLASSEX);
  wcl.hInstance = hInst;
  wcl.lpszClassName = szClassName;
  wcl.lpfnWndProc = WndProc;
  wcl.style = 0;
  wcl.lpszMenuName = NULL;
  wcl.cbClsExtra = NULL;
  wcl.cbWndExtra = NULL;
  wcl.hIcon = LoadIcon (NULL, IDI_APPLICATION);
  wcl.hIconSm = LoadIcon (NULL, IDI_APPLICATION);
  wcl.hCursor = LoadCursor (NULL, IDC_ARROW);
  wcl.hbrBackground = (HBRUSH) GetStockObject(BLACK_BRUSH);
  WinMain返回的类型是int APIENTRY。接下来有四个参数(括号里的):第一个是当前的实例句柄;第二个在Windows98、95、NT中已经不用了,通常设置为NULL。第三个是一个指向字符串的指针。反正很少用它,先不理它。第四个参数决定了窗口在初始显示时的状态。
  我们还需要四个变量。第一个是消息变量msg,该变量保存有对于执行代码而言非常重要的信息。每当接收到新的消息时,消息变量的值就被重新装入。第二个变量hWnd指的是实际的程序窗口。第三个变量wcl将掌握窗口类的信息。第四个变量bRunning指明程序是否运行。
  剩下的就是窗口类的信息。就不多说了。【若不懂,参看《Windows图形编程》。译者按】
  if (!RegisterClassEx(&wcl))
  {
  ErrorHandling.ProcessError (ERROR_REGISTER_CLASS);
  return (-1);
  }
  上面这一小段儿是说:进行窗口类注册,若有错,调用错误处理功能。你需要把#define ERROR_REGISTER_CLASS加入到Error.h头文件中,并把下列代码加入到ProcessError的default:前面。
  case ERROR_REGISTER_CLASS :
  dwMsgSize = strlen ("Chapter 2 - Error log file\nCould'nt register class...\n");
  lpzMessage = (LPSTR) malloc (dwMsgSize + 1);
  strcpy (lpzMessage, "Chapter 2 - Error log file\nCould'nt register class...\n");
  break;
  这将增加一个“Couldn’t register class”(不能注册类)的错误处理。
  回到WinMain,我们还需要创建和显示窗口。我们来设置类的名称,窗口的名称和窗口的类型,窗口的坐标和大小(0,0,640,480),父窗口,没有默认得菜单,当前的实例和最后赋值为NULL的参数。最后我们显示窗口。如下:
  hWnd = CreateWindow (szClassName, szWinName, WS_OVERLAPPEDWINDOW,0 , 0, 640, 480,
  HWND_DESKTOP, NULL, hInst, NULL);
  ShowWindow(hWnd, nCmdShow);
  现在我们来到WinMain的最后一个环节,创建一个消息循环。程序在此处不断检测消息或命令的输入,并将相应的消息发送出去,传送给消息处理部分。
  while (bRunning)
  {
  if (PeekMessage(&msg, NULL, 0, 0, PM_REMOVE))
  {
  if (msg.message == WM_QUIT)
  {
  bRunning = false;
  }
  TranslateMessage(&msg);
  DispatchMessage(&msg);
  }
  else
  {
  }
  }
  return 0;
  }
  最后,我们return 0,来让程序知道一切正常。
  完了吗?还没有,我们还需要谈谈信息的处理部分。
  [b]▲信息的处理[/b][b][/b]
  LRESULT CALLBACK WndProc (HWND hWnd, UINT msg, WPARAM wParam, LPARAM lParam)
  {
  switch (msg)
  {
  case WM_DESTROY:
  PostQuitMessage(0);
  return 0;
  default:
  return DefWindowProc (hWnd, msg, wParam, lParam);
  break;
  }
  return 0;
  }
  所有的消息处理多是如此样子,不用太深究参数部分。Switch部分才是重要的。最简单的就是如上:只有一个case WM_DESTROY,它的地作用就是接收到关闭窗口的命令(消息)时,执行PostQuitMessage。所有不是程序需要处理的消息,都通过default下的DefWindowProc 处理了。
  ▲ 结论
  就到这里吧。我希望你能了解我们所讲到的,如果你有问题或建议,写信给我。akura@crosswinds.net (英文的)谢谢你有耐心读完这篇文章。下载源代码请到
  http://www.gamedev.net/reference/programming/features/gp101_2/Chapter2.zip
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值