对前一段时间的工作和学习的一些回顾

前一段时间的工作和学习基本上包括了三件事。第一件事情是编写自己的游戏引擎Saber Core,目前仅写完了基础的场景管理;第二件事情是阅读并翻译《Game Engine Architecture》,目前翻译到第二章;第三件事是实训,实训的内容是在四周内完成一个小型3D游戏,开发工作现在已接近尾声。

实训虽然我有点心不在焉,每天也很懒,但在做事的时候也是有动脑子的。在我的努力下,我们的游戏的绝大部分地方都完成了参数化,也就是可以通过读取外部XML文件来改变游戏的某些属性。我的本意是将所有游戏场景逻辑全部参数化,这样我们就可以像编写魔兽争霸地图那样编写外部文件,然后再让主程序去跑。不过由于时间(4周)、人员(4名开发人员中有两个Java程序员,写出来的C++代码可以用灾难来形容)和现有架构的原因,我放弃了全部参数化的想法。事实上,如果真的实现了全部参数化,那么我们的程序在某种程度上就更像“引擎”而不是游戏了,因为从模型文件到怪物属性,都是可以通过XML配置的。当然啦,游戏逻辑还是写死在C++里面的。嘛~勉勉强强可以算是个TD(塔防)类的游戏引擎了。

另外就是翻译的那本书。我一般直接简称为GEA了。GEA里面其实挺啰嗦的(各种英文从句,各种对前文的重复),不过如果仔细读的话,确实有很多受用的地方,这也是我翻译的乐趣之一。但随着读书章节的深入(我读书的进度自然是比翻译进度快很多的),以及刚好最近又用OGRE来完成了一个小游戏,总感觉之前了路子貌似有点歪了。我指的是几乎从头开始写一个游戏引擎,好像有些不现实。且不说我对图形学也就是个半吊子,一些高级的特效如光阻塞贴图HDL什么的完全没概念,也不知道该如何实现;但就从模块的角度来看,我是不可能所有模块都自己写的。比如音频,我不可能自己去写解码;比如I/O,我还是要用到DirectInput;比如物理模块,我更不可能自己写了。

国内对于游戏方面也有很多书籍,但大部分都在于教你如何使用OpenGL或者DirectX做游戏的图形部分。GEA之所以好,在于全面。GEA更多的是从一个更高层的角度,来观察游戏引擎中的各个模块如何装拼在一起,如何使整个复杂的系统高效而又井然有序。因此我是否应该停下仿照OGRE实现一个图像引擎(其实这才是我现在正在做的事),而改为使用OGRE作为组件,在其上搭建我的游戏引擎呢?这个问题值得考虑。

2.1 版本控制

本文是Game Engine Architecture的一部分
免责声明

版本控制系统是一种工具,允许多个用户共同工作于一组文件。它维护了每个文件的更改历史,因而我们可以跟踪变更,并在需要的时候恢复到历史版本。它允许多个用户同时修改文件 —— 甚至是修改同一个文件,这样每个人就不会因为别人工作没完成而使自己的进度被阻塞。版本控制器得名于它跟踪文件历史版本的能力。有时候它被称作代码控制,因为它主要被计算机程序员用于管理他们的源代码。然而,版本控制器也可以用于其他类型的文件。版本控制系统通常最擅长管理文本文件,原因我们会在接下来说明。但是,许多游戏工作室不仅使用版本控制器管理源代码文件(通常是文本文件),也用其管理纹理、三维模型、动画、音频文件等游戏资产(通常是二进制文件)。

 
2.1.1 为什么要使用版本控制?
当软件是由一个团队中许多工程师共同开发的时候,版本控制器是相当重要的。版本控制器
  • 提供了一个工程师们可以共享源代码的中央代码仓库;
  • 为每个源文件的每次变更保留历史;
  • 提供了一种机制,以允许基于特定代码的版本被标记,并在之后恢复;
  • 允许某个版本的代码从主线版本中分支出来,该功能通常用于制作演示程序,或为旧版本打补丁。
源码控制系统甚至在担任项目中也是非常有用的。尽管它支持多用户的能力在这里没用了,但他的其它能力,比如维护变更的历史、标记版本、为演示程序和补丁创建分支、跟踪缺陷等,依旧是非常有用的。
 
2.1.2 常见的版本控制系统
以下是最常见的版本控制系统,你很可能在你的游戏工程师职业生涯中遇到它们。
  • SCCS和RCS。Source Code Control System(SCCS)和Revision Control System(RCS)是两个最为古老的版本控制系统。它们都采用命令行界面。它们主要流行于UNIX平台。
  • CVS。Concurrent Version System(CVS)是一个基于命令行的重量级专业源代码控制系统。他原本是建立在RCS之上,但现在已实现为一个独立的工具。CVS流行于基于UNIX的系统,但在其他开发平台上(如Windows)也是可用的。它是基于Gnu General Public License(GPL)授权的开源软件。CVSNT(也成为WinCVS)是一个基于并兼容CVS的Windows平台本地实现。
  • Subversion。Subversion是一个开源的版本控制系统,目标是改进并替代CVS。因为它是开源的,所以它免费。这使它成为了单人项目、学生项目和小工作室的绝佳选择。
  • Git。这是一个被用于包括Linux内核在内的许多值得尊敬的项目的开源版本控制系统。在git的开发模型中,程序员对文件进行修改并将修改提交到一个分支中。而后,程序员可以方便而快速地将他修改过的代码与其它分支的代码进行合并。因为git“知道”该如何比较出两个文件之间的差异,并将差异重新应用到一个新的基版本 —— 这个过程git称之为rebase。最终的结果是,当处理许多代码分支的时候,git成为了一个高效而快速的版本控制系统。更多信息可登陆 http://git-scm.com/
  • Perforce。Perforce是一个专业级源代码控制系统,它同时拥有文本和图形界面。Perforce的一个成名之处在于其变更列表的概念。变更列表是所有作为一个逻辑单元而被修改的源文件的集合。变更列表会被自动检入到代码仓库 —— 要么整个变更列表被提交,要么什么都不提交。Perforce被许多游戏公司所使用,包括Naughty DogEA
  • NxN Alienbrain。Alienbrain是一个专门为游戏行业设计的,强大且功能丰富的源代码控制系统。其最成名之初在于它支持非常大的同时含有源代码文本文件和二进制游戏艺术资产的数据库。它还支持对特定人员如程序员、艺术家、制作人等定制用户界面。
  • ClearCase。ClearCase是定位于超大型软件项目的专业级源代码控制系统。它功能强大,且采用了扩展自Windows资源管理器的唯一用户界面。我还没看到多少游戏行业中使用ClearCase的例子,也许是因为它是最昂贵的版本控制系统之一。
  • Microsoft Visual SourceSafe。SourceSafe是一款轻量级源代码控制包,它已成功应用在一些游戏项目中。
2.1.3 Subversion和TortoiseSVN的概述
我选择在本书中着重突出Subversion有着以下的几个原因。首先,它是免费的,而免费的总是好的。在我的经验中,它运行良好且十分可靠。Subversion的中央代码仓库非常易于建立。并且,正如我们将要看到的,如果你不想麻烦自己去建立代码仓库,市面上已经有好些免费的已设立好的代码仓库服务器。Windows和Mac平台上也有许多优秀的Subversion客户端,比如Windows下免费可用的TortoiseSVN。因此,尽管对于大型商业项目来说Subversion并不是最好的选择(对于大型项目来说,我个人更喜欢Perforce),但我发现它特别适合小型个人和教育项目。让我们看看在Windows平台下如何配置和使用Subversion。当我们这样做时,我们会回顾几乎适用于所有版本控制系统的核心概念。
 
Subversion,就像大多数其他的版本控制系统一样,采用了一个客户端-服务器架构。服务器管理着一个中央代码仓库,在其中保存着版本控制器管理的目录层次结构。客户端埒街道服务器并请求操作,如检出目录树的最新版本、提交对一个或多个文件的修改、标记修订、创建分支,等等。我们不会在这里讨论如何设置服务器端,我们假设你已经拥有了一个服务器,相反我们将集中讨论如何设置和使用客户端。你可以通过阅读[37]的第六章来学习如何设置Subversion服务器。但你可能永远也不需要这样做,因为总是可以找到免费的Subversion服务器。例如,谷歌在http://code.google.com/提供免费的Subversion代码主机。
 
2.1.4 在谷歌上设立一个代码仓库
开始使用Subversion的最简单方法是访问http://code.google.com/并设立一个免费的Subversion代码仓库。如果你没有谷歌账号,那么创建并登陆,然后跳转到Developer Resources下的Project Hosting(见图2.1)。点击“Create a new project”,然后输入一个适当且独一无二的项目名称,如“mygoogleusername-code”。如果你喜欢的话,你可以输入一个项目概括和详细描述。你甚至可以提供标签,这样世界各地的其他用户就可以通过搜索找到你的代码仓库。点击“Create Project”按钮,你已经到达了路途的终点。
 
一旦你创建了你的代码仓库,你就可以在Google Code页面上管理它。你可以添加和删除用户,控制选项,并执行一系列高级操作。但现在你所需要做的事情其实是建立一个Subversion客户端并开始使用你的代码仓库。
 
图2.1 Google Code主页。Project Hosting链接。
 
2.1.5 安装TortoiseSVN
TortoiseSVN是一个非常受欢迎的Subversion前端。它扩展了Windows资源管理器的功能,添加了几个非常方便的右键菜单项,以及用覆盖图标来显示受版本控制管理的文件和文件夹的状态。
 
为了获取TortoiseSVN,访问http://tortoisesvn.tigris.org/。从它的下载页下载最新版本。双击你所下载的.msi文件,并按照安装向导的指示安装。
 
一旦TortoiseSVN成功安装,在Windows资源管理器的任何文件夹中右键单击 —— 此时TortoiseSVN的菜单扩展项应该已经可见。为了连接到一个已有的代码仓库(例如你在Google Code上创建的),在你的本地硬盘上创建一个文件夹,然后单击右键并选择“SVN Checkout…”。图2.2所示的对话框将会出现。在“URL of repository”字段中,输入你代码仓库的URL。如果你是用的是Google Code,那么它应该是https://myprojectname.googlecode.com/svn/trunk,myprojectname是你第一次创建项目时设置的名称(例如”mygoogleusername-code”)。
 
如果你忘记了代码仓库的URL,只要像之前一样登录http://code.google.com/,点击屏幕右上角的“Sign in”链接登录,然后点击屏幕右上角的Settings链接。点击“My Profile”选项卡,你将看见你所有项目的列表。你的项目URL是https://myprojectname.googlecode.com/svn/trunk,而myprojectname是你在“My profile”选项卡中看见的项目名称。你现在应该可以看见如图2.3所示的对话框。用户名是你的Google登录名。密码不是你的谷歌登录密码 —— 这是一个自动生成的密码,可以通过登录到你的谷歌账户并点击”Project Hosting”页面的“Settings”链接获得。(参见上面的详细信息。)选中“Save authentication”选项,这样将来你就不需要再次登录。仅当你在使用自己的电脑工作时才选中该项 —— 永远别在许多人使用的机器上保存密码。
 
图2.2 TortoiseSVN初始化检出对话框                            图2.3 TortoiseSVN用户验证对话框
 
一旦你通过了身份验证,TortoiseSVN将会将代码仓库中的所有内容下载(“检出”)到你的本地硬盘。如果你刚刚设置了你的代码仓库,这将会是…什么也没有!你创建的文件夹依然是空的。但现在它已经连接到你谷歌(或者其它服务器位置)的Subversion代码仓库上了。如果你刷新你的WIndows资源管理器窗口(按F5),你现在应该可以看见一个小小的绿白相间的钩标志出现在你的文件夹上。这个图标表示该文件夹已经被连接到一个Subversion代码仓库,并且本地的数据库副本是最新的。
 
2.1.6 文件版本、更新和提交
正如我们所见,任何像Subversion这样的源代码控制系统的一个核心功能,就是通过在服务器上维护一个中央代码仓库或“主”版本代码,使多个程序员可以工作于单一的软件代码上。服务器为每个文件维护其历史版本,如图2.4所示。对于大型多人软件开发项目来说,这个功能是至关重要的。例如,有人犯了错误,将出错的代码检入到了代码库中,这时你就可以轻松地通过回到历史版本来撤销这些变化(并且查看日志,找出谁是罪魁祸首!)。你也可以为任何时间点的代码建立快照,这将允许你处理代码、展示程序或为软件之前的版本打补丁。
 
图2.4 文件版本历史
 
图2.5 编辑一个已被加入到版本控制的本地文件副本
 
每个程序员都会在他或她的机器上得到一份代码的本地副本。在TortoiseSVN中,你通过“检出”代码仓库来获取初始的工作副本,正如前文所述。你需要定期的更新你的本地副本,以反映可能由其它程序员的修改而导致的变更。要这么做你要右键单击一个文件夹,并在弹出菜单中选择“SVN Update”。
 
你可以仅工作于你本地的代码库副本,而不影响团队中的其他程序员(图2.5)。当你做好准备要将你的更改与其他人分享时,你需要将你的变更提交(commit,也称作submitcheck in)到代码仓库。要这样做,你可以通过右键单击你想要提交的文件夹,并在弹出菜单中选择“SVN Commit…”。你会得到一个如图2.6所示的对话框,要求你确认更改。
 
图2.6 TortoiseSVN提交对话框
 
在提交操作过程中,Subversion将生成一份本地版本文件与代码仓库中最新版本文件之间的差异(diff)。差异(diff)的意思就是差别(difference),这主要通过逐行比较两个版本的文件来实现。你可以通过双击TortoiseSVN提交对话框(图2.6)中的任何文件来查看其本地版本和服务器上最新的版本之间的差异。修改过后的文件(任何具有“差异”的文件)将会被提交。这将会使代码仓库中的文件被你的本地文件所替换,并在文件的版本历史中增加一个新的条目。任何没有发生改变的文件(也就是,你的本地副本与代码仓库中的最新版本是相同的)将会在提交时默认忽略。一个提交操作的例子如图2.7所示。
 
图2.7 将本地编辑提交到代码仓库
 
如果你在提交之前创建了新的文件,它们将会被列在提交对话框的“non-versioned”栏中。你可以通过选中它们旁边的复选框来将它们添加到代码仓库。任何你在本地删除的文件将会被标记为“missing” —— 如果你选中它们的复选框,那么它们将会从代码仓库中删除。你可以在提交对话框中输入一些评论。这些评论会被加入到代码仓库的历史记录中,这样,你和你团队中的其他成员就会知道为什么这些文件被检入。
 
2.1.7 多重检出、分支和合并
一些版本控制系统要求检出只能是排他性的(exclusive check-out)。这意味着你在检出之前必须显式标记出你需要修改的文件,并为它们上锁。被你检出的文件仅在你的本地磁盘上可写,并且无法被其他人检出。所有在你硬盘上的其他未被检出的文件都是只读的。一旦你完成了对文件的编辑,你可以将其检入,释放对文件的锁定,并将变更提交到代码仓库以让其他人看见。这个锁定文件以进行排他性编辑的过程可以确保没有两个人可以同时编辑同一个文件。
 
Subversion,CVS,Perforce,和其他许多高质量的版本控制系统还允许多重检出(multiple check-out)。也就是说,你可以在别人编辑某个文件的时候编辑相同的文件。无论用户进行了什么修改,第一个提交的文件版本将会成为代码仓库中的最新版本。任何后续提交操作之前,程序员必须先将他的修改与其他先前执行提交操作的程序员的修改进行合并。
 
由于不止一个的变更(差异)被应用到了相同的文件上,版本控制系统必须要合并变更以生成文件的最终版本。这通常不会是一个大问题,事实上许多冲突可以被版本控制系统自动解决。例如,如果你改动了函数f()而另一个程序员改动了函数g(),那么你们两个改变的应该是文件中不同的部分。在这种情况下,你与他人的变更通常会被自动处理,且不会发生冲突。但是,如果你们两者同时对相同的函数f()进行了修改,那么第二个执行提交操作的程序员将需要进行一个三路合并(three-way merge)(如图2.8所示)。
 
图2.8 由于两位不同的用户进行的本地修改导致的三路合并
 
为了进行一次三路合并,版本控制器必须要足够聪明以跟踪你硬盘上拥有的文件版本是哪一个。这样,当你合并文件时,系统将知道哪个版本是基版本(共同的祖先,如图2.8中的版本4)。
 
Subversion允许多重检出,事实上,它并不需要你显式检出文件。你只需要简单地开始编辑本地文件 —— 任何时候在本地磁盘上的所有文件都是可写的。(顺便说一句,在我看来这也是Subversion无法顺利应用在大型项目的原因之一。为了找到你改变了哪些文件,Subversion必须遍历搜索整个源文件树,而这将导致效率低下。像Perforce这样的版本控制系统会显式跟踪你修改过的文件,这在处理数量巨大的代码时变得更为容易。但对于小项目来说,Subversion的方式就足够了。)
 
当你通过点击右键弹出菜单中的“SVN Commit…”选项来执行一次提交操作时,你可能会接到提示要求将你的更改与其他人做的更改进行合并。但如果没有人在你最近一次更新你的本地副本后改变文件,那么你的更改将会被提交并且不出现任何提示信息。这是一个非常方便的功能,但它也可能是危险的。因此,最好在每次提交之前先仔细检查一下,以防一些不希望被改变的文件被提交。当TortoiseSVN显示其提交文件对话框时,你可以在点击“OK”按钮之前通过双击某个文件以查看你进行的更改。
 
2.1.8 删除文件
当一个文件从代码仓库中被删除,它并不是真正地被删掉了。文件仍然存在于代码仓库中,但其最新版本被简单地标记为“已删除”,这样用户将不会在本地文件目录树中看见它们。你仍然可以通过右键点击被删文件所在的文件夹,在TortoiseSVN的菜单中选择“Show log”,并在其中看见早先版本中被删除的文件。
 
你可以通过回到文件被标记删除之前的一个版本来恢复一个被删除的文件。你只需要简单地重新提交一遍该文件。这将会使文件被删除之前的版本替换掉文件被删除的版本,高效地进行了文件恢复。
 

2 业界的工具

本文是Game Engine Architecture的一部分
免责声明

在我们开始迷人的游戏引擎架构之旅之前,我们要先用一些基本的装备将自己武装起来。在接下来的两章里,我们将会对接下来的旅程中需要用到的软件工程概念和实践进行一个复习。在第二章里,我们将会探索大多数专业游戏工程师所使用的工具。在第三章中,我们将围绕面向对象编程、设计模式和大规模C++编程等关键话题进行回顾。
 
游戏开发是要求最高、领域最广的软件工程之一。所以相信我,在接下来的险恶旅程中,我们会希望能够有精良的装备来保证旅途的顺利。对于某些读者来说,本章以及下一章的内容会非常令人熟悉。然而,我鼓励你们不要完全地跳过这些章节。我希望它们会成为一个愉快的复习。谁知道呢 —— 你甚至可能会捡起一两个你所不知的新技巧。
 

1.7 工具和资产管线

本文是Game Engine Architecture的一部分
免责声明

任何游戏引擎都需要吃掉大量的数据,这些数据以游戏资产、配置文件、脚本等形式存在。图1.31描述了一些现代游戏引擎中常见的游戏资产类型。深灰色的箭头线显示了数据是如何从创建它们的工具流动到游戏引擎之中的。浅灰色的线表示了各种不同类型资产之间的依赖关系。

图1.31 工具和资产管线
 
1.7.1 数字内容创作工具
游戏是天生的多媒体应用。游戏引擎的输入数据形式广泛,从三维网格数据,到纹理位图,到动画数据,到音频文件。所有的这些原始数据必须由艺术家来创作。艺术家创作这些所使用的工具叫做数字内容创作(DCC)应用程序。
 
一个DCC应用程序通常仅针对一种特定类型的数据 —— 尽管一些工具可以同时生成多种类型的数据。例如,Autodesk的Maya和3ds Max普遍被用于创作三维网格,和动画。Adobe的Photoshop与其同类主要着眼于位图(纹理)的创建和编辑。SoundForge是一个受欢迎的音频制作和剪辑工具。某些类型的游戏数据无法使用外部的DCC程序编辑。例如,大多数游戏引擎提供了一个用于布局游戏世界的自定义编辑器。不过,一些引擎确实是在用已存在的工具来进行游戏世界的布局。我曾经见过有的游戏团队使用原始的或带插件的3ds Max和Maya作为世界布局工具。随便去问问一些游戏开发者,他们会告诉你,曾经有一段时间,他们仅使用一个简单的位图编辑器来制定地形高度,或者是直接在一个文本文件中手工编写世界布局。工具不必华丽 —— 游戏团队可以使用任何能完成任务的工具进行工作。这意味着,工具必须要易于使用,并且绝对可靠,如果游戏团队想要在合理的时间内完成一件高度润色的产品的话。
 
1.7.2 资产修整管线
数字内容创作(DCC)程序所使用的数据格式极少适合直接被游戏使用。有两个主要原因。
  1. DCC应用程序所使用的数据格式的内存模型通常比游戏引擎所需要的复杂得多。以Maya为例,Maya存储着一个场景节点的有向无环图(DAG),该图中还存在着极度复杂的网状连接。它保存了所有在这个文件上执行过的历史编辑操作。它包含了场景中所有物体的位置、朝向和缩放,这些信息被分解为平移、旋转、缩放和裁剪部分,以完整的层次结构保存着。一个游戏引擎通常只需要这些信息中极小的一部分用以渲染模型。
  2. DCC应用程序所用的文件格式的装载速度对于实时程序来说常常过于缓慢,更不要说有些文件格式还是封闭专有的。

因此,由DCC生成的数据常常被导出为一个更容易理解的标准化格式、或是一个自定义的格式,以用于游戏之中。

 
一旦数据从DCC应用程序中导出,它通常需要在被送入游戏引擎之前二次加工。如果一个游戏工作室想要在不止一个平台上运行他们的游戏,那么数据的中间文件可能会要针对不同平台进行不同的处理。例如,3D网格数据可能会被导出为一种中间文件格式,如XML或简单的二进制格式。接着它可能会与其它使用相同材质的网格合并,或者是拆分成更小的部分以防止引擎无法一次处理过大的网格。随后网格数据可能会被打包成适合某种硬件平台的内存映像,并加载到特定的平台上。
 
这条从DCC应用程序到游戏引擎的管线有时被称作资产修整管线。这条管线以某种形式存在于每个游戏引擎之中。
 
1.7.3 3D模型/网格数据
你在游戏中所见的几何体通常由两种数据组成。
 
1.7.3.1 画刷几何体
画刷几何体被定义为一组凸包,每个凸包由多个平面组成。画刷通常直接在游戏世界编辑器中创建和编辑。这就是一些人所说的“旧式”创建可渲染几何体的方法,但这种方法目前依旧在使用。
 
优点:
  • 创建起来快速且容易;
  • 容易为游戏设计者所理解 —— 通常被用以“勾勒”出游戏某关卡的原型;
  • 可作为渲染几何体,也可作为碰撞体。
缺点:
  • 低分辨率 —— 难以创建复杂的形状;
  • 无法支持带关节的模型和具有动画的角色。
1.7.3.2 3D模型(网格)
对于更精细的场景元素来说,3D模型(也被称为网格)是比画刷几何体更好的选择。网格是一个由许多三角形和顶点组成的复杂几何体。(网格也可由四边形或更多边数的形状构成。然而由于目前几乎所有的图形硬件都是仅支持渲染栅格化三角形的,因此所有的形状在渲染之前都必须先被转换成三角形。)网格通常拥有一个或多个的材质,材质定义了其视觉表面的性质(色彩、反射、凹凸图、表面贴图等)。在本书中,我将使用术语“网格”表示一个可渲染形状,而使用“模型”来指代由多个网格、动画数据以及其它游戏中使用的元数据复合而成的对象。
 
我们需要编写某种导出器,将数字内容创建(DCC)工具(Maya,Max等)中的数据以能被引擎接受的方式储存在磁盘上。DDC程序提供了许多标准或半标准的导出格式,但没有哪个能完美地适合游戏开发(可能除了COLLADA)。因此,游戏团队常常创建出自定义的文件格式和自定义的导出器来使用。
 
1.7.4 骨骼动画数据
骨骼网格是一种特殊的网格,其中网格被绑定到一个具有层次关系的骨骼上,用以阐述动画。这样的网格有时也被称为皮肤,因为它组成了包围在底层不可见骨骼之外的皮肤。骨骼网格中的每个顶点包含了一系列与之绑定的骨骼关节的索引。顶点通常还包含一组关节权重,表示了每个骨骼关节对该顶点的影响程度。
 
为了渲染骨骼网格,游戏引擎通常需要三种不同类型的数据。
  1. 网格本身,
  2. 骨骼的层次结构(关节名称,上下级关系,以及当骨骼最初被绑定到网格上时候的基本姿势),和
  3. 一个或多个动画片段,描述了骨骼关节应该如何随着时间的推移而运动。

网格和骨骼通常会作为一个单一的数据文件由DCC程序导出。然而,如果多个网格要被绑定到单个骨骼上,那么最好将骨骼导出为单独的文件。动画通常是单独导出的,这允许了动画仅在被使用的时候才被加载到内存中。然而,一些游戏引擎允许将多个动画打包导出为一个文件,有些甚至将网格、骨骼和动画一同打包为一个大文件。

未经优化的骨骼动画由4×3的矩阵采样流定义,采样的频率至少为每秒30帧,并对骨骼中的所有关节进行(通常多于100个)。因此动画数据本质上是内存密集型的。由于这个原因,动画数据几乎总是被储存在高度压缩的格式中。压缩方案由于引擎的不同而不同,有些是专有的。并没有一个标准化的游戏动画数据格式。
 
1.7.5 音频数据
音频剪辑通常由Sound Forge或其他一些音频软件,以各种不同格式和数据采样率导出。音频文件可能是单声道、立体声、5.1、7.1或其他多通道配置。Wave文件(.wav)是很常见的,但其它文件格式如PlayStation的ADPCM文件也非常流行。多个音频剪辑通常被组织成库的形式,这样做易于管理,也易于加载到引擎和流媒体之中。
 
1.7.6 粒子系统数据
 现代游戏使用了复杂的粒子效果。这些都是由专攻视觉特效的艺术家们创作的。第三方工具,如Houdini,允许创作出电影级别的特效。然而,大多数游戏引擎并不能够完整地渲染Houdini创建出来的特效。因此,许多游戏公司自制了粒子特效编辑工具,这些工具只导出引擎实际支持的特效。自定义的工具也可让艺术家在编辑器中预览与游戏中表现效果完全相同的特效。
 
1.7.7 游戏世界数据和世界编辑器
游戏世界是游戏引擎中所有东西的集合。根据我的认识,并没有一款可用的商业(像Maya或3d Max那样的通用软件)游戏世界编辑器。然而,许多商业游戏引擎都很好地提供了自己的世界编辑器。
  • 一些Radiant游戏编辑器的变种被许多基于Quake技术的引擎所使用;
  • 半条命2的Source引擎提供了一个名为Hammer的世界编辑器;
  • UnrealEd是Unreal引擎的世界编辑器。这个强大的工具同时也管理着引擎所使用的所有类型的数据。
编写出一个好的世界编辑器是困难的,但它是任何一个好的游戏引擎中至关重要的一部分。
 
1.7.8 一些构架工具的方法
游戏引擎的工具套件可以以许多种方法来设计。一些工具可能作为独立的软件存在,如图1.32所示。一些工具可能建立在引擎运行时所使用的底层组件之上,如图1.33所示。一些工具可能会集成进游戏本身。例如,基于Quake和基于Unreal的游戏都拥有一个游戏内控制台,这允许了开发人员和“mod”开发者在游戏运行的时候键入调试和配置命令。
 
图1.32 工具独立的架构
 
图1.33 工具与游戏共享一个框架
 
作为一个有趣和独特的例子,Unreal的世界编辑器与资产管理者,UnrealEd,是直接内置在运行时引擎中的。要运行编辑器,你必须要以一个命令行参数”editor”来启动游戏。这个独特的架构风格如图1.34所示。它允许工具拥有对引擎运行时所使用的数据类型的完全访问,并解决了每个数据结构要以两种不同的方式来表现的问题 —— 一个用于运行时引擎,另一个用于编辑器。这同时也意味着在游戏编辑器中运行游戏的速度非常快(因为游戏实际上已经在运行了)。实时的游戏内编辑,一个通常难以实现的特性,将由于游戏编辑器本身是游戏的一部分而变得相对轻松。然而,一个像这样的引擎内编辑器也有他它自己的问题。例如,当引擎崩溃时,工具也会随之崩溃。因此引擎与资产创建工具之间的紧密耦合有可能会降低生产效率。
 
图1.34 Unreal引擎的工具架构
 

1.6 运行时引擎架构

本文是Game Engine Architecture的一部分
免责声明

游戏引擎通常由一套工具以及一个运行时组件组成。我们将首先讨论运行时组件的架构,并在以后的章节中详细探讨工具的架构。
图1.11展示了组成一个典型3D游戏引擎的所有主要运行时组件。是的,它很大!而且这个图甚至还没有考虑所有的工具呢。游戏引擎绝对是大型软件系统。
就像所有其他的软件系统一样,游戏引擎是分层结构的。通常更高的层依赖于更低的层,但反之则不然。当一个较低的层依赖于一个较高的层时,我们称这种情况为循环依赖。循环依赖在任何软件系统中都是需要被避免的,因为它们导致了不受欢迎的系统间耦合,使代码难以测试,并阻止了代码重用。这尤其适用于像游戏引擎这样的大型系统。
图1.11 运行时游戏引擎架构
下面是对图1.11中每部分组件的一个简短概述。本书的剩余部分将把时间花在深度研究每部分组件上,并使读者了解这些组件通常是如何被集成到一个功能整体中的。
1.6.1 目标硬件
目标硬件层,如图1.12所示,代表了游戏将会在其上运行的计算机系统或游戏主机。典型的平台包括了基于微软Windows或Linux操作系统的个人电脑,苹果的麦金塔电脑和iPhone手机,微软的Xbox和Xbox 360,索尼的PlayStation、PlayStation 2、PlayStation Portable(PSP)和PLAYSTATION 3,任天堂的DS、GameCube和Wii。本书的大部分主题是平台无关的,但我们会涉及到一些与PC和游戏主机差别相关的特有设计考虑。
图1.12 硬件层
1.6.2 设备驱动
如图1.13所示,设备驱动是由操作系统或硬件供应商提供的底层软件组件。设备驱动管理着硬件资源,对操作系统和上层引擎屏蔽了五花八门的硬件设备的具体细节,提供了同一的接口。
图1.13 设备驱动层
1.6.3 操作系统
在PC上,操作系统(OS)总是处于运行状态。它协调在单一电脑上运行的多个程序,其中之一就是你的游戏。操作系统层如图1.14所示。操作系统如微软的Windows采用分时的方法在单一硬件上共享地运行多个程序,这被称为先发制人的多任务处理。这意味着一个PC游戏永远不可能获得硬件的完全控制权——他必须与其它程序“和谐共存”。
在游戏机上,操作系统通常只是直接编译在可执行游戏程序中的一层薄薄的库。在游戏机上,游戏通常“拥有”整个机器。然而,由于Xbox 360和PLAYSTATION 3的出现,事情已经不总是这样了。这些游戏机上的操作系统可以中断你正在执行的游戏,或接管某些系统资源,用以显示在线消息,允许玩家暂停游戏并显示PS3的Xross媒体条或Xbox 360的仪表盘。所以,游戏机和PC平台之间的鸿沟正在逐渐地减小(无论是好是坏)。
图1.14 操作系统层
1.6.4 第三方SDK和中间件
大多数游戏引擎利用了一些第三方软件开发工具包(SDK)和中间件辅助开发,如图1.15所示。由SDK提供的基于类的方法和接口一般被称作应用程序编程接口(API)。我们将看看一些例子。

图1.15 第三方SDK层
1.6.4.1 数据结构与算法
如同其它的软件系统一般,游戏在很大程度上依赖于数据结构集合,以及操纵这些数据的算法。在这里有一些提供这种服务的类库的例子。
  • STL。C++标准模板库提供了大量的用以管理数据结构,字符串和基于流的I/O的算法。
  • STLport。这是一个可移植的经过优化的STL实现。
  • Boost。Boost库是一个功能强大的数据结构与算法库,按照STL的风格设计而成。(Boost库的在线文档也是一个学习计算机科学知识的非常好的地方!)
  • Loki。Loki是一个功能强大的泛型编程模板库,它非常擅长于使你伤透脑筋!
游戏开发者在是否应该在他们的游戏中使用如STL这类的模板库这个问题上存在分歧。一些人认为STL的内存分配模式容易导致内存碎片(参见章节5.2.1.4),不利于高性能编程,因此不适用于游戏开发。另一些人则认为STL所带来的威力和便利性远超过了它所带来的问题,并且大多数STL带来的问题可以通过其他的方式避免。我个人认为在PC机上使用STL是没什么问题的,因为PC机上先进的虚拟内存系统使得合理的分配内存方式变得不那么至关重要(即使是这样你也需要非常的小心)。然而大部分游戏机的虚拟内存功能并不强大,或者是完全没有。因此在游戏机上,使用自己写出的具有可预测/或有限制内存分配模式的数据结构也许更加好(如果你在PC中采用这种方式,肯定也是不错的)。
1.6.4.2 图形
大多数的游戏图形渲染引擎是建立在一个硬件接口库之上的,这些库如下所示:
  • Glide是一个为支持老式Voodoo显卡写成的3D图形SDK。这个SDK在由DirectX 8所引领的硬件转换和灯光(硬件T&L)时代来临之前十分流行。
  • OpenGL是一个被广泛使用的可移植3D图形SDK。
  • DirectX是微软的3D图形SDK,也是OpenGL的主要竞争对手。
  • libgcm是应用于索尼PLAYSTATION 3的RSX图形硬件的底层接口。它是索尼提供的比OpenGL更有效率的替代品。
  • Edge是一个强大而高效的渲染和动画引擎。它由Sony的Naught Dog工作室提供,并为大量的第一方或第三方PLAYSTATION 3游戏工作室所使用。
1.6.4.3 碰撞和物理
碰撞检测和刚体动力学(在游戏开发社区中被简称为”物理”)由以下几个家喻户晓的SDK提供。
  • Havok是一个流行的工业级物理和碰撞引擎。
  • PhysX是另一个流行的工业级物理和碰撞引擎,可从NVIDIA的网站免费下载使用。
  • Open Dynamics Engine(ODE)是一个著名的开源物理/碰撞软件包。
1.6.4.4 角色动画
有许多商业动画软件包存在,包括但不限于如下。
  • Granny。由Rad Game Tool出品的受欢迎的Granny工具箱包括了一个强劲的3D模型和动画导出器,它支持玛雅,3D Studio Max等所有主流的3D模型格式。Granny还包括了一个用以读取和操作导出的模型与动画数据的运行时库,以及一个强大的实时动画系统。在我看来,Granny SDK是我所见过的所有动画API(无论是商业或专有)中,设计最好最合理的。尤其是它出色的处理时间的方式。
  • Havok Animation。物理和动画之间的界限由于游戏角色越来越真实而变得越来越模糊。生产了流行的Havok物理SDK的公司决定创建一个免费附赠的动画SDK,这使得跨越物理和动画之间的界限变得比以前更简单。
  • Edge。由Naughty Dog的ICE团队为PS3提供的Edge库包含了一个强大而高效的动画引擎,以及一个高效的几何体处理渲染引擎。
1.6.4.5 人工智能
  • Kynapse。直到最近,人工智能(AI) 都是每个游戏以自己的方式处理的。然而,一家名为Kynogon的公司生产了一个名为Kynapse的中间件SDK。这个SDK提供了一系列的底层AI构建模块,例如寻路,静态和动态对象回避,识别一个空间中的薄弱点(例如,一个开着的窗口,伏兵可以从这个窗口中进入)。Kynapse还提供了AI与动画之间良好的交互接口。
1.6.4.6 生物力学角色模型
  • EndorphinEuphoria。这些动画软件包采用先进的真实人类生物力学模型来创建角色运动动画。
正如我上面提到的,角色动画与物理之间的界限正在逐渐模糊。像Havok Animation这样的程序包尝试将物理和动画以传统的方式嫁接在一起,即通过玛雅这样的工具和物理拓展包一起捕捉人类动作演员的实时运动,并将之转化为动画。但最近一家名为Natural Motion的公司尝试重新定义游戏或其他形式的数字媒体处理角色动画的方式。
该的第一个产品,Endorphin,是一个玛雅插件,允许动画师在角色上运行完整的生物力学仿真,并将自动生成的结果导出为具有和手工创建相同效果的角色动画。生物力学模型把角色的动作归因于重心,角色重量分布,以及其它细节的知识(真实的人类是如何在重力和其他力影响下保持平衡与移动的)。
该公司的第二个产品,Euphoria,是Endorphin的实时版本。它的目的在于提供角色在实时环境中,受到不可预知的力的作用下所产生的物理与生物力学上的精确动作。
1.6.5 平台无关层
大多数的游戏引擎被要求能够在不止一个的硬件平台上运行。例如像EA和动视暴雪这样的公司,总是将它们的游戏定位于支持各种平台,因为这使得它们的游戏能获得最大的目标市场。通常,只有那些第一方工作室才会不将它们的游戏定位于能运行在不同的平台上,就像索尼的Naughty Dog和Insomniac工作室。因此大多数的游戏是架构在一个平台无关层之上的,如图1.16所示。
通过包装和替换常用的C标准函数,系统调用,和其他基本的应用程序编程接口(API),平台无关层保证了跨硬件平台下的接口一致性。这是非常有必要的,因为不同的平台有着大量的差异,即使是像C标准库这样的“标准”库。
图1.16 平台无关层
1.6.6 核心系统
所有的游戏引擎,以及所有的大型、复杂C++应用程序,都需要一大堆有用的软件工具包。我们将把这些划分在“核心系统”标签之下。这里有一些核心系统层通常所提供的功能的例子。
图1.17 核心系统层
  • 断言是用于捕捉逻辑错误和违背程序员最初假设的错误检测代码。断言代码通常会在游戏的最终产品中剥离出来。
  • 内存管理。几乎所有的游戏引擎都会自己实现一个定制版本的内存分配系统,以确保高效的内存分配和回收,以及消除内存碎片所导致的负面影响(参见章节 5.2.1.4)。
  • 数学库。游戏在本质上是数学密集型的。因此,每个游戏引擎都至少有一个数学库。这些库提供了一些数学功能如处理向量和矩阵运算,四元数旋转,三角函数,用于直线、射线、球体、柱体等的几何操作,样条操作,数值积分,解方程系统,以及其他游戏程序猿所需要的功能。
  • 自定义的数据结构与算法。除非引擎设计师决定完全依赖于第三方库(例如STL),否则一套基本的数据结构管理工具(链表,动态数组,二叉树,哈希表等)和算法(搜索,排序等)通常是需要的。这些库通常由手工编码,以最小化或消除动态内存分配,以确保在目标平台上的最佳实时性能。
对于常见的游戏引擎核心系统的详细讨论可参见本书的第二部分。
1.6.7 资源管理器
在每一个游戏引擎中以某种形式出现,资源管理器提供了一个统一的接口(或一套统一的接口)来访问任何类型的游戏资产和其他引擎输入数据。一些引擎以高度一致、集中的方式来进行资源管理(如Unreal的包,和OGRE 3D的资源管理类)。其它的一些引擎以一种特别的方式处理资源,通常让游戏程序员感觉像是在直接操作磁盘系统或压缩包(如Quake的PAK格式文件)。一个典型的资源管理层如图1.18所示。
图1.18 资源管理器
1.6.8 渲染引擎
渲染引擎是任何一个游戏引擎中最复杂,最庞大的组件。渲染机可以以许多不同的方式构建,并不存在一个公认的最优方法。然而,大多数现代渲染引擎在一些基本的设计理念上是共通的,并在很大程度上被它们所基于的3D图形硬件的设计所影响。
一个常见且有效的渲染引擎设计方法是采用一个如下的分层架构。
1.6.8.1 低层渲染机
低层渲染机,如图1.19所示,包含着所有原始的渲染引擎设施。在这个层次,设计主要集中在如何更快更多地渲染一组几何图元,而并不关心一个场景的哪些部分是可见的。该组件被分解成各种子组件,这在接下来将进行讨论。
图1.19 底层渲染引擎
图形设备接口
图形SDK,如DirectX和OpenGL,需要编写一些代码,用以枚举可用的图形设备,初始化它们,设置渲染表面(后缓冲,深度缓冲等),以及进行其它的一些工作。这些工作通常由一个我称之为图形设备接口的组件来处理(尽管每个图形引擎都会使用自己的术语)。
对于PC游戏的引擎,你还需要用代码将渲染机与Windows消息循环集成。你通常会编写一个“消息泵”来处理正在等待的Windows消息,否则你将以尽可能快的速度运行你的渲染循环。这将会使游戏按键轮询循环与屏幕渲染循环绑定。这种耦合是不可取的,但我们可以通过努力来减少这种依赖。我们将会之后的文章中详细详述。
其他渲染机组件
低层渲染机中的其它组件通力合作,以收集需要被提交的几何图元(有时被称之为渲染包,如网格,线列表,点列表,粒子,地形,文字,以及其它一切你想要绘制在屏幕上的东西),并尽可能快地渲染它们。
低层渲染机通常会提供一个抽象视口。这个视口与一些摄像机视矩阵和投影矩阵的参数相关联,如视场(FOV)和远近截平面。低层渲染机同时也通过材质系统动态光照系统来管理图形硬件的状态和游戏的着色器。每个被提交的原始图元都被赋予一个材质,并受到n个动态光源的影响。材质描述了图元所使用的纹理,什么样的设备状态需要被设置生效,以及在渲染该图元时使用要使用什么样的顶点和像素着色器。灯光决定了动态光源的计算结果将如何被应用到图元上。光照和着色是一个复杂的主题,它被许多优秀的计算机图形学书籍所覆盖,其中包括[14][42][1]
1.6.8.2 场景图/裁剪优化
低层渲染机绘制所有提交给它的几何体,并不对这些几何体在场景中是否可见作多关心(除了反面裁剪,以及裁剪超出摄像机平截头体的三角形)。基于某种可见性判别算法,一个更高层的组件通常是需要的,以限制提交给低层渲染机的几何图元的数量。这一层如图1.20所示。
图1.20 一个典型的场景图/空间分割层,用于裁剪优化
在一个非常小的游戏世界中,一个简单的平截头体裁剪(例如,移除所有照相机无法“看见”的物体)也许就是所有需要做的事情。对于更大的游戏世界来说,一个可用于迅速确定潜在可见集(PVS)的更为先进的空间细分数据结构可能会被使用以提高渲染效率。有许多的方法可用以空间细分,包括空间二分(BSP)树,四叉树,八叉树,kd树,和球体层次结构。空间细分有时被称为场景图,虽然从技术上讲,后者是一种并不包含前者的特殊数据结构。门户或遮挡裁剪通常也被应用于渲染引擎的这一层。
理想状况下,低层渲染机应该完全忽略所使用的空间细分或场景图类型。这允许不同的游戏团队复用图元提交代码,并根据自己团队所开发的游戏的需要制作自己的PVS裁决系统。开源引擎OGRE 3D(http://www.ogre3d.org/)是实践这一原则的非常好的例子。OGRE提供了一个即插即用式的场景图架构。游戏开发者可以选择预设的场景图设计,或者可以自己提供一个自定义的场景图实现。
1.6.8.3 视觉特效
现代游戏引擎支持多种视觉特效,如图1.21所示,包括。
  • 粒子系统(用以烟雾,火焰,水流等);
  • 贴花系统(用以弹孔,脚印等);
  • 光线贴图和环境贴图;
  • 动态阴影;
  • 全屏幕后期特效,在整个3D场景被渲染到屏幕缓冲区时应用。
一些全屏幕后期特效的粒子包括
  • 高动态范围(HDR)光照和泛光;
  • 全屏幕反锯齿(FSAA);
  • 色彩校正和色彩偏移效果,包括漂白,饱和和减饱和效果等。
游戏引擎通常拥有一个特效系统组件,该组件管理粒子、贴花、和其他视觉效果呈现的特殊需求。另一方面,光照贴图、环境贴图、以及阴影通常由渲染引擎内部自己处理。全屏幕后期特效要么实现为渲染机整体的一部分,要么实现
为一个操作渲染机输出缓冲区的独立组件。
图1.21 视觉特效
1.6.8.4 前端
大部分游戏都采取某种方式,使得2D图形可以叠在在3D场景之上,用以各种不同的目的。
  • 游戏的头顶状态显示(HUD);
  • 游戏中菜单,控制台,和其他开发工具。这些可能会也可能不会出现在最终发布产品中;
  • 游戏图形用户界面(GUI),以允许玩家操纵他或她的物品栏,配置战斗单元,或执行其他复杂的游戏任务。
该层如图1.22所示。像这样的二维图形通常由一个正投影下的带纹理矩形(一对三角形)实现。它们也可能完全在3D场景中渲染,在这种情况下它们被渲染为方形的布告板,布告板总是面对镜头的。
图1.22 前端图形
我们还把全动态视频(FMV)系统放在这一层。该系统用以播放全屏的早些时候录制的电影记录(要么通过游戏渲染引擎渲染,要么使用其他的渲染包渲染)。
一个相关的系统是游戏过场动画(IGC)系统。这个组件通常允许过场动画以全3D的方式精心安排在游戏之中。例如,当玩家走过一个城市,一段发生在两个关键人物身上的对话可能会被实现为一个游戏电影。IGC中可以包含也可以不包含玩家角色。它们可能会被做成是一个对场景的第三方审视,在此期间玩家无法控制自己的角色。它们也可能会被巧妙地融入游戏,以至于玩家甚至没有意识到到一个IGC正在发生。
1.6.9 性能分析和调试工具
游戏是实时系统。因此,工程师们常常需要测试游戏运行的绩效,以优化其性能。此外,内存资源通常也是稀缺的,因此开发人员也经常使用内存分析工具。性能分析和调试层,如图1.23所示,包含了这些工具。该图同时还包含了游戏中的调试设备,如调试绘图,游戏内菜单或控制台等。该图还包括了记录和回放游戏的工具,以供测试和调试用。
有很多好的通用软件分析工具可用,包括
  • 英特尔的VTune
  • IBM的Quantify and Purify(PurifyPlus工具套件的一部分),
  • 康普科纬迅的Bounds Checker
然而大部分游戏引擎同时集成了一套自定义的性能分析和调试工具。例如,它们可能包括以下的一种或多种功能:
  • 一种手动检测代码的机制,这样特定部分的代码可以被计时;
  • 一个在游戏运行的同时将性能数据显示在屏幕上的工具;
  • 一个用以将性能统计数据导出到文本文件或Excel表格的工具;
  • 一个用来确定有多少内存是被引擎占用,或其他子系统(包括各种屏幕显示)占用的工具;
  • 在游戏中是或游戏期间输出内存使用量,内存使用峰值以及内存泄露统计的能力;
  • 用以打印散布在代码各处的调试语句的工具,拥有开关控制某种特定类别调试信息输出、以及输出信息详细程度的能力;
  • 能够记录游戏事件,并 回放。这个工具很难被正确制作,但一旦制作完成,它可以成为一个非常有价值的错误跟踪工具。
图1.23 性能分析与调试工具
1.6.10 碰撞和物理
碰撞检测在所有游戏中都是非常重要的。没了它,物体会相互重叠,我们也无法以任何合理的方式与虚拟世界互动。一些游戏还包含有一个现实的或半现实的动力学仿真系统。我们常在游戏产业中称之为“物理系统”,然而术语刚体动力学系统其实更为贴切,因为我们通常只关心刚体的运动(运动学)以及导致该运动产生的力和力矩(动力学)。该层由图1.24描绘。
图1.24 碰撞与物理子系统
碰撞和物理通常是紧密耦合的。这是因为一旦碰撞被检测到,我们总是希望以符合物理约束条件的方式解决该碰撞。如今,很少有游戏公司编写自己的碰撞/物理引擎。相反,第三方SDK通常会被集成到引擎之中。
  • Havok是当今产业中的黄金标准。它具有非常丰富的功能,并在各种平台上表现得很好。
  • 英伟达的PhysX是另一个优秀的碰撞和动力学系统。它被集成于虚幻3引擎中,也可免费用于独立PC游戏产品的开发。PhysX最初是设计作为Ageia的最新款物理加速芯片的接口。这个SDK现在由英伟达拥有和分发。英伟达将PhysX调整以运行在其最新的显卡上。
开源的物理和碰撞引擎也是可用的。可能这其中最出名的要数Open Dynamics Engine(ODE)。关于该引擎的更多信息,请参见http://www.ode.org/。I-Collide,V-Collide和RAPID是另一些受欢迎的非商业碰撞检测引擎。这三者都由北卡罗来纳大学(UNC)开发。有关更多信息,请参见http://www.cs.unc.edu/~geom/I_COLLIDE/index.htmlhttp://www.cs.unc.edu/~geom/V_COLLIDE/index.htmlhttp://www.cs.unc.edu/~geom/OBB/OBBT.html
1.6.11 动画
任何拥有一个有机的或半有机的角色(人类,动物,卡通角色,或者甚至是机器人)的游戏都需要拥有动画系统。游戏中一般应用了五种类型的动画:
  • 精灵/纹理动画,
  • 刚体层次动画,
  • 骨骼动画,
  • 顶点动画,以及
  • 变形目标。
骨骼动画允许动画师使用一种相对简单的骨骼系统为3D角色塑性。当骨骼运动时,三维模型的网格顶点也随之运动。尽管变形目标和顶点动画也被一些引擎所使用,骨骼动画是当今最普遍的游戏动画方法。因此,骨骼动画将是本书动画部分的焦点。一个典型的骨骼动画系统如图1.25所示。
图1.25 骨骼动画子系统
你会注意到在图1.11中,骨骼网格渲染组件是渲染机和动画系统之间的桥梁。在这里发生了两者之间的紧密合作,但是他们之间的接口是设计良好的。动画系统为骨骼系统中的每根骨头产生一个姿势,随后这些姿势将会被作为一组矩阵传递给渲染引擎。渲染机根据这些矩阵来变换每一个顶点,以混合生成这些顶点的最终位置。这一过程被称之为蒙皮
布娃娃系统被采用时,动画系统和物理系统之间也会产生一个紧密的耦合。一个布娃娃通常是一个柔软(通常是死的)的动画角色,其身体运动是由物理系统模拟的。物理系统根据刚体之间的力约束来决定布娃娃身体每一部分的位置和朝向。动画系统最终计算出渲染引擎绘制人物所需要的矩阵。
1.6.12 人机交互设备(HID)
所有游戏都需要处理来自玩家的输入。这些输入来自各种人机交互接口设备(HID),包括
  • 键盘和鼠标
  • 游戏手柄,或
  • 其他专门的游戏控制器,像钓鱼竿,跳舞毯,Wii手柄等等。
我们有时称这些组件为玩家I/O组件,因为我们还可以通过HID为玩家提供输出,如游戏手柄上的力反馈/震动和Wii手柄上的音效。一个典型的HID层如图1.26所示。
图1.26 玩家输入/输出系统层,也被称作人机交互接口设备(HID)层
HID引擎组件通常设计为将特定硬件平台上的底层游戏控制器细节与高层游戏控制方式解耦。它将来自硬件的原始数据打包成消息,引导手柄中心点周围的盲区,检测按钮按下弹起事件,解释并平滑来自加速计的输入(例如,来自PLAYSTATION 3无线手柄)。它通常提供一种机制,允许玩家自定义物理控制器与游戏功能逻辑之间的映射关系。它有时还包含一个用于检测和弦(几个键同时按下),序列(几个键按顺序按下),和手势(一系列来自按钮、手柄和加速计的输入)的系统。
1.6.13 音频
音频在任何游戏引擎中都与图形一样重要。不幸的是,音频受到的关注通常比不上渲染、物理、动画、人工智能和游戏性。有例为证:程序员通常关着音响写代码!(事实上,我知道有相当多的程序员甚至连音响和耳机都没有。)尽管如此,没有令人震惊的音效的游戏是不完整的。音频层如图1.27所示。
图1.27 音频子系统
不同的音频引擎在复杂性上有很大差异。Quake和Unreal的音频引擎十分基础,游戏团队常常会用自定义功能来扩充它们,或者直接把它们替换成内部的解决方案。对于DirectX平台(PC和Xbox 360),微软提供了一个优秀的音频工具套件叫做XACT。EA公司开发了一个先进的、高性能的音频引擎,其内部称之为SoundR!OT。结合自身的工作室Naughty Dog,索尼电脑娱乐美国(SCEA)提供了一个强大的3D音频引擎叫做Scream,该引擎已被应用于许多PS3游戏上,包括Naughty Dog的未知:德雷克的财富。然而,即使一个游戏团队使用了一个已存在的音频引擎,他们依旧需要做大量的自定义软件开发、集成工作、微调以在最终的游戏产品中制作出高质量的音频。
1.6.14 在线多人游戏/网络
许多游戏允许多个玩家在同一个虚拟世界中进行游戏。多人游戏有四个基本种类。
  • 单屏幕多人游戏。两个或更多的人机交互设备(游戏手柄,鼠标,键盘等)被连接到同一台街机、电脑、或游戏主机上。多个玩家角色占据着同一个虚拟世界,一个单独的摄像头保证了所有玩家角色同步地出现在画面中。这种风格的多人游戏包括任天堂明星大乱斗乐高星球大战Gauntlet
  • 分屏的多人游戏。多个玩家角色占据着同一个虚拟世界,玩家使用连接到同一台游戏机上的多个HID。然而每个虚拟角色都拥有这自己的摄像机,这将屏幕分成了几个部分,因而每个玩家都可以看到他或她的角色。
  • 联网多人游戏。对台电脑或游戏机通过网络连接在一起,每个玩家操纵一台机器。
  • 大型多人在线游戏(MMOG)。成千上万的玩家可以同时在一个巨大、持久、在线的虚拟世界中游戏,这个虚拟世界由一台威力巨大的服务器支持。
多人游戏网络层如图1.28所示。
图1.28 在线多人游戏子系统
多人游戏在很多方面与它们的单人游戏同行相似。然而,对多玩家的支持会对某些游戏引擎组件的设计产生深刻的影响。游戏世界对象模型,渲染机,人机交互设备系统,玩家控制系统,以及动画系统都会因此收到影响。改造一个已有的单人游戏引擎使其支持多人游戏的特性当然不是不可能的,但这将会是一个艰巨的任务。不过,许多游戏团队都成功地做到了这点。因此,如果允许的话,最好在一开始就让引擎支持多人游戏。
值得注意的是,朝另一个方向前进 —— 将一个多人游戏改造成单人游戏 —— 的工作量通常是微不足道的。事实上,许多游戏引擎把单机模式作为多人游戏的一种特例,一种只存在一位玩家的多人游戏。Quake引擎由于其客户端运行于服务器之上模式而著名,在其中程序将运行于一台PC上,即作为客户机也作为服务器,执行单人游戏战役。
1.6.15  游戏设定基础层
游戏设定这个词指的是发生在游戏中的事件,管理游戏虚拟世界运行的规则,玩家角色的能力(也成为玩家机制)以及世界中其他角色和物体的能力,游戏的目的和玩家的目标。游戏设定通常是使用编写引擎的语言写成,或者使用高级脚本语言写成 —— 有时两者兼有。为了跨过存在于游戏设定代码与我们迄今为止讨论到的低层游戏引擎代码之间的鸿沟,大多数游戏引擎引入了一个我称之为游戏设定基础层(因为缺乏标准的名字)的层。如图1.29所示,这一层提供了一套核心设施,使得上层的特定游戏逻辑可以非常方便地实现。
图1.29 游戏设定基础系统
1.6.15.1 游戏世界和对象模型
游戏设定基础层引入了游戏世界的概念,其中包含了静态和动态的元素。世界中的内容通常是以面向对象的方式建模的(通常,但并非总是使用一个面向对象的编程语言)。在本书中,组成游戏的对象类型集合被称为游戏对象模型。游戏对象模型提供了一个虚拟游戏世界中的实时仿真异构对象集合。
典型的游戏对象类型包括
  • 静态背景几何,如建筑、道路、地形(通常是个特例)等;
  • 动态刚体,如岩石、汽水罐、椅子等等;
  • 玩家角色(PC);
  • 非玩家角色(NPC);
  • 武器;
  • 弹药;
  • 车辆;
  • 灯光(可能会在场景中以动态光源出现,也可能仅用于静态光照);
  • 摄像机;
  • 以及其它。
游戏世界模型与某种软件对象模型紧密联系在一起,这个模型可能最终弥漫至整个引擎。术语软件对象模型是指为实现一个面向对象的软件所使用的一套语言特性、策略和惯例。在游戏引擎的上下文中,软件对象模型将回答以下问题:
  • 你的游戏引擎是以面向对象的方式设计的吗?
  • 你会使用什么语言?C?C++?Java?OCaml?
  • 静态的类层次结构将如何被组织?会是一个巨大的整体?还是大量松散耦合的组件?
  • 你会使用模版和基于策略的设计,或传统的多态?
  • 对象是如何被引用的?老式指针?智能指针?句柄?
  • 如何将对象唯一地标识?仅通过内存地址?通过名字?通过全局唯一标识符(GUID)?
  • 怎样管理游戏对象的生命周期?
  • 不同时间游戏对象的状态如何被模拟?
我们将在书14.2节非常深入地探讨软件对象模型和游戏对象模型。
1.6.15.2 事件系统
游戏对象之间总是需要相互通信的。这可以通过各种各样的方式完成。例如,对象发送消息可能只是简单地调用接收方对象的成员函数。一种事件驱动的架构,正如我们通常能在一个典型的图形用户界面系统中看到的,也是一种常见的内部通信方法。在一个事件驱动的系统中,发送方创建出一个小小的称作事件消息的数据结构,其中包含着消息的类型和其它需要发送的参数数据。通过调用接收方的事件处理函数,发送方将事件传递给接收方。事件也可以储存在一个队列中,等待在未来的某个时间被处理。
1.6.15.3 脚本系统
许多游戏引擎使用脚本语言以使开发特定的游戏规则和内容变得更方便快捷。没有了脚本语言,你就需要在每次做出逻辑或数据结构改动后重新编译和链接。但当一个脚本语言集成到了你的引擎中,你就可以通过修改并重新加载脚本来完成对游戏逻辑和数据的修改。一些引擎允许脚本文件在游戏运行的时候重新加载,另外一些引擎要求必须要重启以重新加载脚本。但无论如何,重新加载脚本所需的时间远远低于重新编译和链接游戏的可执行文件。
1.6.15.4 人工智能基础
传统上,人工智能(AI)落入特定游戏软件的领域 —— 它通常不被认为是游戏引擎自身的一部分。然而最近,许多游戏公司已经认识到一些常有的模式几乎出现在每一个人工智能系统中。因此一些基础的人工智能开始逐渐回到了游戏引擎的范畴当中。
一家名为Kynogon的公司开发了一个叫做Kynapse的商业AI引擎。它被作为一个“AI基础层”,在其之上特定的AI逻辑就可以很容易地开发了。Kynapse提供了一套威力强大的特性,包括
  • 一个节点路径网络或漫游卷,它定义了AI角色可以自由移动而不用担心碰到静态世界几何体的区域;
  • 简化了每个漫游区域的边缘信息;
  • 进入或离开某个区域的知识,以及从哪里敌人可能看见和/或伏击你;
  • 一个基于知名的A*算法的寻路引擎;
  • 到碰撞系统和世界模型的钩子,为了视线跟踪以及其他辨识方法;
  • 一个自定义的世界模型,告诉AI系统所有感兴趣实体(朋友、敌人、障碍)的位置,允许动态躲避移动物体,等等。
Kynapse还提供了一个AI决策层的架构,包括大脑的概念(每个角色一个),代理的概念(每个负责执行某种特定的任务,比如从一点移动到另一点,向敌人开火,寻找敌人,等等),以及动作的概念(允许角色执行某种基本的运动,这通常导致人物骨骼动画的播放)。
1.6.16 游戏特定子系统
在游戏设定基础层以及其它低层引擎组件之上,游戏程序员和设计师合作实现游戏本身的特性。游戏设定系统通常数量众多,千变万化,并常常特定于被开发的游戏。如图1.30所示,这些系统包括玩家角色机制、各种游戏摄像机系统、控制非玩家角色(NPC)的人工智能、武器系统、车辆系统等等。如果有一条清晰的线可以划分游戏和游戏引擎,那么它将存在于游戏特定子系统和游戏设定基础层之间。实际上,这条线从来都是模糊的。有一些特定的游戏类型相关知识总是渗透进入游戏设定基础层,有时甚至延伸到引擎核心本身。
图1.30 游戏特定子系统

吐槽2

1.6章真是巨长的一篇,不过我估计今晚能翻译完吧。

今天在帮组员调试某个模块,他说怪物模型的朝向不对。我在初始化的时候设置了一下朝向,惊人的一幕出现了:其中一半的怪物原地转了个圈,剩下的一半纹丝不动。看到这个情景我真是眼皮一阵乱跳,鼠标滚轮网上滚了滚果然发现了三个重载。好吧孩子们你们有多喜欢重载?我看着大陀长得基本上一模一样的,明显就是Ctrl+C Ctrl+V过去的代码,问:为什么要这样写?难道不知道重复是大罪嘛?答曰:其中一个已经是弃之不用的,只是忘了删。我说:那你不介意我把那个注释掉吧?答曰:哦……哦,应该可以吧。嗯,然后我注释掉了那段代码,编译器果然报错了。

下午看另一个类,越看越觉得各种诡异。组员告知我,这是个单例!我大惊,满世界找getInstance,还真找到一个,不过隐藏得实在是好,让我甚至都不认为它是个getInstance。它长的大约是这样的:

XXManager->getXXManager(Maze* maze);

我有点无力吐槽,说,不是让你们别用单例么?先不说我们这弹丸点大的程序需不需要用到单例,我就说这奇葩的用法……(虽说这错误我也犯过……)

于是我很耐心地说,呐,你这个根本就不是单例,因为你获取实例的函数还要传一个Maze型,如果别人手上么有Maze怎么办呢?所以你应该先写个初始化函数,这般这般,再在这里加个断言,这般这般,然后把这些成员声明为静态,这般这般,很麻烦吧?你还是不要用单例了吧?此时项目经理插话了:单例模式可是使用最广泛的设计模式!好吧好吧,给我五分钟,我怕了你们了。于是我开始大刀阔斧地修了起来。那位童鞋(这个类的作者)看着我把他的类改得面目全非,忍不住说了句:其实为什么不重载一个getXXManager呢?一个需要传参另一个不需要就好啦。

我听的真是一阵头晕目眩差点昏过去。又T-M-D重载,你们脑子里塞的全都是重载吗?但像我这么成熟而又有耐心的人,当然不会爆粗。我很耐心地解释道:呐,如果你写了两个getInstance,那别人怎么知道是要调用需要传Maze的那个还是不用传Maze的那个呢?答曰:如果手头上有Maze就调用需要传Maze的那个。

我!操!这能沟通吗?我当时脑袋肯定是被门板夹了才有如此耐心,还尝试向他解释:你看你单例只有一个,如果我在这边把这个Maze实例传了进去,另一个人在其他地方把另一个Maze实例传了进去,那会发生什么事呢?答曰:在getXXManager中写个if语句,如果已经有Maze了就不许传。

这简直就是#¥%……&*(

最后我说:好吧,你的方法好极了,但看在我已经改了这么多的份上,你就按照我的方法来吧。

近日来的一些小吐槽

minecraft是个好游戏,我是在没日没夜地玩了4天并终于发烧后,将其删除的。

这也是我这么多天没更新博客的原因。为了玩游戏我可是废寝忘食,我甚至在实训的时候也在玩。因此没时间写代码没时间翻译是可以被理解的: )。我突然发现做开发经理还是挺轻松的,也就搭搭环境,设计下架构,在组员遇到难题时指导指导,正因如此我才有时间在上班时玩游戏呀~之前我都是在上班时干自己的事情。

作为开发经理,还需要做的事情就是和策划那边扯扯皮,说说“这个需求我们做不了”啊,“那个需求很难实现”啊之类的。还有就是应付应付项目经理一天三次的“这玩意怎么还不能运行”之类的无聊诘问。项目经理非常希望可以按照需求->设计->UML->编码的过程走一遍,于是我很无奈地和他双手一摊:“你说的这玩意我从来没在实践中用过,因此完全不懂怎么弄,你来教教我们呗~”于是开发经理很愉快地投降了。

实训这么多天我越发觉得Java使人脑残,一次还愤怒地写下了一篇文章“我为什么讨厌java”,不过没有post出来,因为写完后觉得自己图样图森破,也许再过两年看看自己写下的这些话有没有道理吧。嘛,当初在我做设计时两位写Java的童鞋觉得备受冷落,项目经理钦点说要让他们早点加进来。加就加呗。我切了一部分模块过去让他们去设计,他们两个设计的模块接口对不到一起去,还怪我当初没有定好接口。我擦内闹哪样这是,当时我就火起来想要脱口而出“咋不见Linux画类图”。但想我等成熟男士说话之前总要三思,因此我非常无辜地对他们说:“接口文档是吧,你们是想要电子版的还是纸质版的?”

唔,大概就这些吧。接下来要逐步恢复正轨了。玩物丧志,玩物伤身,切记,切记。

顺便一提,minecraft是用java写的,java还真是与我命中相克啊。

1.5 游戏引擎调查

本文是Game Engine Architecture的一部分
免责声明

1.5.1 Quake家族
重返德军总部3D(1992)被公认为是第一款第一人称射击游戏 (FPS)。德克萨斯州的id软件编写了这款运行于PC平台下的游戏。这款游戏将整个游戏业带领到了一个令人兴奋的新方向。Id软件接着创作出了毁灭公爵雷神之锤雷神之锤2,和雷神之锤3。这些游戏的引擎在架构上非常相似,因此我将它们称为Quake(雷神之锤)引擎家族。Quake的技术被用于创作各种类型的游戏甚至是引擎。例如,PC平台上的荣誉勋章的族谱是这样子的:
  • 雷神之锤2(Id);
  • 罪(Ritual);
  • F.A.K.K 2(Ritual);
  • 荣誉勋章:盟军突击(2015 & Dreamworks Interactive);
  • 荣誉勋章:血战太平洋(EA, LosAngeles)。
不同游戏工作室的许多其它基于Quake技术的游戏遵循着基本上相同的路子。事实上,Valve’s的Source引擎(被用于创建半条命等游戏)也算是Quake引擎的远房旁支。
 
雷神之锤以及雷神之锤2的源码都是免费提供的,同时Quake引擎具有相当良好而“干净”的架构(尽管它们有些过时,而且完全是用C写成的)。这些源码是工业级游戏引擎是如何构建出来的非常好的例子。完整的雷神之锤雷神之锤2的源码可由Id的网站上下载 http://idsoftware.com/business/techdownloads
 
如果你自己拥有雷神之锤雷神之锤2游戏,你实际上可以使用微软的Visual Studio编译源码,并在调试模式下运行游戏。这可能令人难以置信地具有教育意义。你可以设置断点,运行游戏,然后使用单步运行模式分析引擎实际上是如何工作的。我强烈推荐下载这两个引擎的源代码,并使用这种方式来分析他们。
 
1.5.2 Unreal家族
Epic公司在1998年与它的传奇游戏作品虚幻系列一同跃上了FPS的舞台。从那时起,Unreal引擎已经成为了Quake引擎在FPS领域的主要竞争对手。Unreal 2引擎(UE2)是虚幻竞技场2004(UT2004)的基础,并被无数次使用于创作各种”mod”,大学项目,以及商业游戏。Unreal 3引擎(UE3)是朝向未来迈进的一步,拥有着丰富的特性以及一些行业中最棒的工具,包括一个方便而功能强大的拥有用户图形界面的着色语言编辑器,以及一个叫做Kismet的同样拥有用户图形界面的用于游戏逻辑编程的工具。最近的许多游戏都是使用UE3开发的,包括Epic公司的广受欢迎的战争机器
 
Unreal引擎著名于其广泛的特性,以及高度内聚的,易于使用的工具集。Unreal引擎是不完美的,大多数开发人员会以各种不同的方式来修改它,以使他们的游戏运行于一个特定的优化的硬件平台之上。然而,Unreal是一件令人难以置信的威力强大的原型开发工具和商业游戏开发平台,Unreal可被用来开发几乎任何种类的3D第一人称或第三人称游戏(更不用说其他类型的游戏了)。
 
Unreal开发者网络(UDN)提供了一套非常丰富的各种版本Unreal引擎的开发文档和其它信息(参见http://udn.epicgames.com/)。一些Unreal 2引擎的文档是免费提供的,所有拥有一份UT2004拷贝的玩家都可以创建自己的”mod”。然而,只有被授权后人们才可接触到UE2文档的剩余部分和UE3文档的全部。不行的是,Unreal的授权费用十分昂贵,以至于所有独立游戏开发者以及大部分的小型游戏工作室都无法企及。但是有许多其他的网站和wiki是关于Unreal的。其中一个非常受欢迎的网站是http://www.beyondunreal.com/ 。
 
1.5.3 半条命Source引擎
Source是引起轰动的半条命2以及他的续集半条命2:第一章半条命2:第二章军团要塞2传送门(这几个游戏被打包作为半条命橙盒版发售)的引擎。Source是一个高质量的,在图形处理能力和工具集上能与Unreal相匹敌的引擎。
 
1.5.4 微软XNA Game Studio
微软的XNA Game Studio是一个易于使用,高度普及的游戏开发平台。XNA旨在鼓励玩家创造自己的游戏,并在在线游戏社区中与他人共享,就像YouTube鼓励创建和分享自制视频一般。
XNA基于微软的C#语言和公共语言运行时(CLR)。主要的开发环境是Visual Studio或它的免费版本,Visual Studio Express。从源码到艺术资源的一切都由Visual Studio来管理。通过XNA,开发人员可以为PC平台或Xbox 360游戏机开发游戏。在支付了适度的费用之后,XNA游戏可以被上传至Xbox Live网络与朋友分享。通过提供优秀的工具与基本上为零的成本,微软已经出色地打开了由普通人来制作游戏的闸门。XNA显然有着一个光明且迷人的前景。
 
1.5.5 其他商业引擎
还有许多其他的商业游戏引擎。虽然独立开发人员可能没有足够的预算用以购买一个引擎,但许多引擎产品都有着很棒的在线文档或wiki,这些都可以成为一般的游戏引擎和游戏编程的非常好的信息来源。例如去看看Terathon软件的C4引擎(http://www.terathon.com/),该公司由Eric Lengyel于2001年创立。C4引擎的文档可以在Terathon的网站上获得,额外的关于C4引擎的细节可参见wiki(http://www.terathon.com/wiki/index.php/Main_Page)。
 
1.5.6 私有的内部引擎
许多公司构建并维护者私有的内部游戏引擎。EA公司的许多RTS游戏都是使用一款名为SAGE的引擎构建的,该引擎由Westwood工作室开发。Naughty Dog’s工作室的古惑狼Jak & Daxter系列,以及最近的Uncharted: Drake’s Fortune都是建立在内部引擎的基础上的。这些引擎分别为Playstation,PlayStation 2和PlayStation 3游戏机平台量身定制。当然啦,最具商业价值的授权引擎如QUake,Source和Unreal引擎最开始都是作为私有内部引擎开发的。
 
1.5.7 开源引擎
开源的3D游戏引擎由业余爱好者和专业游戏开发人员制作,放在网上供免费使用。“开源”这个词一般意味着源代码是可以免费获取的,并且有一套开放的开发机制,使得几乎所有人都可以贡献代码。授权,如果存在的话,一般是基于Gnu公共许可(GPL)或宽松的Gnu公共许可(LGPL)。前者允许任何人使用其代码,只要他们的代码也是免费提供给他人的。后者允许代码被使用在以盈利为目的的应用程序中。许多其他的自由或半自由许可也可被用于开源项目。
 
网络上有着数量可观的开源引擎。有的相当好,有的十分平庸,有得则非常糟糕!由http://user.cs.tu-berlin.de/~ki/engines.html提供的列表会让你感受到引擎数量的庞大。
 
Ogre3D是一个架构良好,易于学习,易于使用的3D渲染引擎。它拥有着一个功能齐全的3D渲染器,包括高级灯光和阴影,良好的角色骨骼动画系统,用于角色头顶状态条和图形界面的二维界面系统,以及一个用于后期全屏效果的(如全屏泛光)系统。Ogre,就如其作者自己承认的,并不是一个完整的游戏引擎。然而它提供了许多游戏引擎必不可少的基础组件。
 
其他的一些知名开源引擎将在这里列出。
  • Panda3D是一个基于脚本的引擎。引擎的主接口是自定义的Python脚本语言。它被设计用于快速和方便地开发3D游戏和虚拟世界的原型。
  • Yake是一个相对较新的,在Ogre基础上构建的功能齐全的游戏引擎。
  • Crystal Space是一个拥有可拓展模块架构的游戏引擎。
  • Torque和Irrlicht也都是非常知名并被广泛使用的引擎。

camera 3 (外一篇随笔)

我之前的设想是camera自身不带任何位置属性,完全通过父节点来赋予camera的位置和朝向信息。这样做的一个很麻烦的地方在于,操纵节点的方法和操纵camera的方法有些许不同。当我们想改变节点的朝向时,我们会调用节点的rotate方法;当我们想要改变camera的朝向时,实际上更常用的是通过调用SetLookAt()函数或者SetLookTo()函数来改变摄像机看向的地点。

悲剧在于,SceneNode缺少一个类似SetLookAt()或者SetUpDirection()这样的函数。如果是在Irrlicht里面,一切都不成问题。因为在Irrlicht中我们可以派生出CameraSceneNode,然后直接为CameraSceneNode增加相应的方法即可。但在组合的模式下,我能想到的方法无非就以下两种。

一是给SceneNode增加SetLookAt()方法。这样做的话,对于不是camera的东西,我们也可以SetLookAt…

二是只给Camera增加SetLookAt()方法,当调用Camera的SetlookAt方法时,改变其父节点的朝向。这样做的麻烦之处在于,如果camera的父节点还挂载了其他物件,那么所有人都会一起改变朝向。

Ogre是这样解决这个问题的。camera拥有自己的position,lookAt,和upDirection。camera仅从父节点继承position属性,所以camera的真正位置是自己的position再加上父节点的position。对于朝向,camera和它的父节点是分离的。目前我采用的就是这种方法,这样做灵活性也提高了。例如,对于FPS游戏,我们可以直接将人物模型Entity和Camera都挂载在同一个SceneNode上,且将Camera的自身位置调整到模型的头部,非常方便。

顺便一提,虽然Ogre中的摄像机坐标系和节点坐标系相对分离,但所有的SceneNode依旧有一个SetLookAt方法……我猜Ogre应该是为了方便吧。


随笔

从上周起我们开始了坑爹的实训。朝九晚五,严格考勤。如果不是周六实验室不开门,坑爹的实训老师希望我们周六也继续实训。简直就是坑爹啊,让我等从来没有在11点之前起过床的人如何适应!

实训内容是用Ogre随便弄个3d游戏出来。我本来是抱着打打酱油的心态,谁知身不由己,变成了组里的负责人之一。除了要想这想那,还要每隔10分钟被人打断一次问各种问题,弄得时间全无 T_T。一天下来又累的要命,基本上回到宿舍已经不想看电脑。这几天除了翻译翻译文章,自己的项目一点没碰。今天早上起来打开工程,发现脑袋一片空白,完全不记得之前自己写过些啥了 T_T……日啊……

老师说使用SVN作为版本控制器,但是半天没搭起来。我等不及了就自己弄了个Git,结果组里除了我之外没有人用过Git……后来好不容易弄懂了Git的基础用法,老师的SVN终于搭起来了,一堆人眼巴巴的看着我说换回SVN……T_T

最初的时候为了快速开发,我提议使用Ogre的Python版本。后来有组员说Python学的蛋都碎了,还有组员抱怨说Python函数不支持重载。我思量着使用Python来开发大一点的面向对象工程确实比较吃力,就辛辛苦苦把C++版本的Ogre配好了说大家用C++吧。过了一天我去看看那个抱怨python不能重载的组员电脑前看看他进度如何,第一个画面就亮瞎了我的狗眼……一个本身做着create事情的函数叫做get也就算了,居然整整齐齐的有8个重载版本!从两个参数到五个参数不等。好家伙,怪不得抱怨python不能重载。我苦口婆心的说,你看,你这八个函数做的事情其实是一样的,你又不是类库作者,你写的函数还是给自己用的,你何苦花一个上午来写这么多重载……他却很认真地跟我说,可能会用到,可能会用到……当然啦,他之前是写Java的,这我一点都不感到奇怪。搞得我专门去问了另外一个写Java的人,你们Java程序员写程序是不是都习惯由Get/Set函数开始写起……

不过令我欣慰的是,第二天我再看到他的代码时,那八个重载已经只剩下四个了,显然他发现自己根本用不着这么多。我相信按照这个速率,再过两天他就会把剩下的三个也删了。

1.4 不同类型引擎之间的区别

本文是Game Engine Architecture的一部分
免责声明

(kid:从现在开始我尽量在翻译中使用游戏和公司的中文名称)
 

游戏引擎通常是类型特定的。被设计用于拳击台上的二人格斗游戏的引擎会和大型多人在线游戏(MMOG)引擎,第一人称射击(FPS)引擎,或者是即时战略游戏(RTS)引擎非常不同。然而,这些不同种类的引擎之间也有大块的重叠部分 —— 所有3D游戏,无论类型,都需要用户进行某种底层输入(鼠标,键盘或游戏手柄),某种形式的三维模型渲染,某种形式的头顶标识(heads-up display,比如文字),一个强大的引擎系统,等等(kid:其实作者挺啰嗦的)。因此,即使Unreal引擎是专门为第一人称射击游戏设计的,它也成功地被大量用于构建各种其它类型的游戏。包括广受欢迎的第三人称射击游戏战争机器,基于角色的动作冒险类游戏格林,一种未来的赛车比赛游戏速度之星

 
让我们来看看一些常见的游戏类别,以及实现他们的技术的一些特别要求。
 
1.4.1 第一人称射击游戏(FPS)
第一人称射击游戏(FPS)的代表作有雷神之锤虚幻竞技场半条命反恐精英,和使命召唤(见图1.2)。历史上这些游戏都与以下性质有关:玩家主要在一个大型但封闭的室内场景中漫游,移动的速度相对较慢。然而,现代的第一人称射击游戏中可能出现各式各样的虚拟环境,包括辽阔的户外区域与狭小的室内区域。现代FPS游戏的移动机制可以包括步行,基于轨道或自由驾驶的车辆,气垫船,船只,和飞机。关于这种类型游戏的概述,请参阅 http://en.wikipedia.org/wiki/First-person_shooter
 
第一人称射击游戏通常是最具技术挑战性的,其复杂程度可能只有第三人称射击/动作/平台游戏和大型多人游戏可以相比。这是因为第一人称射击游戏旨在给玩家提供一种错觉,使其沉浸在一个详细的,超现实的世界之中。游戏行业的很多重大技术创新都在这一类别的游戏中产生,这一点都不令人奇怪。
 
图1.2 使命召唤2 (Xbox 360/PLAYSTATION 3)
 
第一人称射击游戏通常专注于技术,例如
  • 高效地渲染大型3D虚拟场景;
  • 一个合理的镜头控制/瞄准机制;
  • 玩家武器与手臂的高保真动画;
  • 各式各样威力强大的手持武器;
  • 一个不细致的玩家运动和碰撞模型,这经常使游戏中的人物看上去像是在“漂浮”;
  • 具有高保真动画和人工智能的非玩家角色(敌军或盟友);
  • 支持小规模的多人在线游戏功能(最高通常可达64位玩家同时在线),以及无处不在的“死亡竞赛”游戏模式。
第一人称射击游戏所采用的渲染技术几乎总是根据特定需要经过精心调整和高度优化过的。例如室内“地牢爬行”游戏经常采用基于二分空间树或门户系统的渲染系统。户外FPS游戏使用其他种类的渲染优化,例如阻塞裁剪,或将玩家看不见的部分下线。
 
当然,要使玩家能沉浸在超现实的游戏世界中,仅靠经过优化的高质量图形技术是不够的。角色动画,音效和音乐,刚体物理,过场动画,以及无数的其他技术都必须处在尖端位置才行。因此,这个类型的游戏有着行业中最严格而广泛的技术要求。
 
1.4.2 平台游戏和其他第三人称游戏
“平台游戏”指的是那种以玩家在平台之间的跳跃为主要游戏机制的,第三人称的角色动作类游戏。经典的2D平台游戏包括恐慌空间大金刚陷阱!,和超级玛丽。3D时代的平台游戏包括超级玛丽64古惑狼雷曼2刺猬索尼克Jak & Daxter系列(图1.3),瑞奇与叮当系列以及最近的马里奥银河。在这个网址 http://en.wikipedia.org/wiki/Platformer 可以看到对这个类别游戏的深入讨论。
 
图1.3 Jak & Daxter: The Precursor Legacy
 
基于角色的第三人称游戏与第一人称射击游戏有非常多的共同点,但其把更多的重点放在了主角的能力和运动模式上。此外,玩家所控制的角色必须要有高保真的全身动画,而不是像FPS中仅需为“漂浮的手臂”准备低分辨率的动画。值得注意的是,几乎所有的FPS游戏都支持在线多人对战,所以除了第一人视角,玩家的全身像也必须被渲染。不过,FPS游戏中玩家角色的模型细节度常常不比怪物的模型逼真,更不用说和第三人称游戏中的玩家角色模型相比了。

图1.4 战争机器
 
在一个平台游戏中,主角人物通常是卡通风格的,因此并不需要特别真实或者具有高的分辨率。然而,第三人称射击游戏通常具有一个高度真实的人形玩家角色。在两种情况下,玩家角色通常都有一组丰富的动作动画。
 
一些该类游戏特别着重的技术包括
  • 会移动的平台,梯子,绳索,格子墙,和其他有趣的运动模式;
  • 如同解谜般的游戏环境元素
  • 第三人称的“追踪式”摄像机镜头,由玩家通过游戏手柄(在游戏机上)或鼠标(在电脑上——注意,虽然许多流行的第三人称射击游戏是在PC平台上运行,绝大部分的平台游戏只存在于游戏机平台上)操作;
  • 一个复杂的摄像机碰撞系统用以确保摄像机不会被背景几何体或前景对象遮挡。
1.4.3 格斗游戏
 
格斗游戏的通常形式是两位人类玩家各自操控自己的游戏角色,在一个类似拳击台的环境中不断向对方发起攻击。这类游戏的代表作有刀魂铁拳(图1.5)。维基百科的 http://en.wikipedia.org/wiki/Fighting_game 页面提供了这类游戏的综述。
 
传统的格斗类游戏将它们的技术努力放在
  • 一组丰富的战斗动画;
  • 精确的命中检测;
  • 一个能适应复杂的按钮和手柄按键组合输入的用户输入系统;
  • 人群,或者是相对静止的背景画面。
因为这些游戏中的三维世界非常小,相机位置也时常集中在动作的部分,历史上的这些游戏很少或基本上没有对世界场景进行裁剪的必要。它们同样也不会采用先进的三维音效传播模型。

图1.5 铁拳3 (PlayStation)
 
先进的格斗游戏例如EA的搏击之夜3(图1.6)提升了技术并增加了以下特性
  • 高清的人物角色图形,包括真实的皮肤着色器与地下散射和汗水效果;
  • 高保真的角色动画;
  • 基于物理的游戏角色衣物布料与头发模拟。
值得注意的是一些格斗游戏,如天堂之剑,的游戏场景是一个规模巨大的虚拟世界,而不是一个限定的竞技场。事实上,许多人认为这是一种单独的游戏类别,有时候这种游戏被称为混战。这种格斗游戏需要更多的类似第一人称射击 或即时战略游戏所用的技术。
 
图1.6 搏击之夜3 (PLAYSTATION 3)
 
1.4.4 赛车游戏
赛车类游戏包括了所有主要任务为在某种赛道上,驾驶一辆汽车或其他什么的载具进行比赛的游戏。这类游戏可分出许多子类。仿真类的赛车游戏(“sims”)旨在提供一个尽可能真实的驾驶经验(例如,GT赛车)。街机赛车游戏更注重夸张的游戏效果而非现实主义(例如,旧金山大赛车Cruisin’ USA雷霆快艇)。一个相对较新的分支探索了街头赛车中个性化自己的车辆的亚文化(例如,极品飞车极品醉车)。卡丁车竞赛是另一个子分支,受人欢迎的平台游戏角色或电视角色被重塑为某种怪诞车辆的司机(例如,马里奥赛车, Jak XFreaky Flyers)。“赛车”游戏并不总需要与竞赛有关。例如,一些卡丁车游戏,提供了一些游戏模式,包括玩家彼此之间相互射击,收集金币,或从事其他各式各样的不计时任务。想参与这一类别游戏的讨论,请参见 http://en.wikipedia.org/wiki/Racing_game 。
 
赛车游戏通常是非常线性的,这与老式FPS游戏十分相似。然而,赛车游戏的移动速度通常比FPS游戏快的多。因此,赛车游戏将大部分重点放在各种赛道上,如基于走廊的赛道,环形赛道,有时赛道还具有各种替代路线或秘密捷径。赛车游戏通常将所有游戏画面细节集中在车辆,赛道和即时环境的渲染上。然而,卡丁车赛游戏也要投入显著的渲染和动画带宽用以渲染驾驶赛车的角色。图1.7显示了著名的GT系列的最新一部作品,GT赛车5的游戏截图。
 
图1.7 GT赛车5 (PLAYSTATION 3)
 
一些典型的赛车游戏所具有的技术属性包括
  • 在渲染远景时使用各种“技巧”,比如使用带贴图的二维薄片渲染远处的山和树木;
  • 跑道通常被分解为相对简单的称作“扇区”的二维区域。这种数据结构将用于优化渲染和决定可见性,帮助非玩家控制的车辆的人工智能寻路,以及解决其他的许多技术问题;
  • 相机通常是以第三人称视角跟随在车辆后面的,或者有时是在驾驶室内以第一人称视角出现;
  • 当赛道包含了隧道和其他的一些“紧凑”的环境,为保证相机不会撞上背景几何体,有许多工作要做。
1.4.5 即时战略游戏(RTS)
现代即时战略游戏(RTS)的鼻祖是沙丘II:王朝的建立(1992)。该类别的其他游戏包括魔兽争霸命令与征服帝国时代,和星际争霸。在这个类型的游戏中,玩家需要在一块巨大的地图上部署兵工厂和战斗单位,并尝试以优势兵力淹没对手。游戏世界通常是显示在一个自上而下的倾斜视角下的。对于该类别游戏的更多讨论,请参见 http://en.wikipedia.org/wiki/Real-time_strategy
 
RTS游戏的玩家通常被限制不可以大幅度改变视角以观察更远的地方。这个限制允许了开发人员可以使用许多手段来优化RTS游戏的渲染引擎。
 
图1.8 帝国时代
 
这一类别的老式游戏使用一种基于网格的建筑格局,并使用正投影来大大简化渲染机。例如,图1.8显示了经典RTS游戏帝国时代的屏幕截图。
 
现代RTS游戏有时会使用透视投影以及一个真正的3D世界,但它们仍然使用网格布局系统来确保单位和背景元素如建筑可以正确地与其他单位对齐。一个流行的例子,命令与征服3,在图1.9中显示。
 
图1.9 命令与征服3
 
其他一些常见于RTS游戏实践的技术包括
  • 每个单位都是相对低分辨率的,因此游戏可以支持数量众多的单位出现在同一个屏幕上;
  • 游戏的设计和运行通常是基于高度场地形画布之上的;
  • 玩家通常被允许在地形上建造或部署单位;
  • 典型的用户交互式通过鼠标单击以及区域选择选中单位,加上包含命令,设备,单位类型,建筑类型等等的菜单或工具栏。
1.4.6 大型多人在线游戏(MMOG)
大型多人在线游戏的典型例子有无冬之夜无尽的任务魔兽世界星球大战等等。MMOG被定义为任何支持大量玩家同时在线(从数千到数十万)的游戏,通常所有玩家在一个非常巨大的,持久的虚拟世界中进行游戏(即,游戏内部世界的状态将持续很长时间,远超出任何一名玩家的游戏会话)。在其他方面,该类游戏的游戏体验往往类似于它们的小规模多人同行。该类游戏的子类别包括大型多人在线角色扮演类游戏(MMORPG),大型多人在线即时战略游戏(MMORTS),和大型多人在线第一人称射击游戏(MMOFPS)。对于该类游戏的其他讨论,请参见http://en.wikipedia.org/wiki/MMOG 。图1.10显示了巨流行的游戏魔兽世界的屏幕截图。
 
图1.10 魔兽世界
 
一个强劲的服务器是所有MMOG的心脏。这些服务器维护游戏世界的授权状态,管理用户登入和登出,提供文字聊天或语音聊天(VoIP),等等。几乎所有的MMOG都会要求用户按某种规则缴纳费用以继续游戏,某些游戏可能还会提供游戏世界与现实世界之间的小额交易。因此,中央服务器的最重要作用也许是处理各种计费和小额交易,这是游戏开发者的最主要收入来源。由于MMOG要支持巨大的场景和大量的同时在线用户,这类游戏中图形分辨率几乎总是低于其非大型多人游戏同行。
 
1.4.7 其他类型的游戏
当然,还有许多其它种类的游戏是我们不准备在这里覆盖讨论的。一些例子包括
  • 运动类游戏(足球,垒球,曲棍球,高尔夫,等等);
  • 角色扮演类游戏(RPG);
  • 上帝游戏,例如Populus黑与白
  • 环境/社交模拟类游戏,比如模拟城市模拟人生
  • 益智类游戏,如俄罗斯方块;
  • 以及其他的。
我们已经看到,每种类别的游戏都有自己的特定技术需求。这就解释了为什么不同种类游戏的游戏引擎之间会有所不同。然而,不同类别的引擎之间依旧有大块的技术重叠,尤其当两个引擎时来自同一硬件平台的时候。随着越来越强大的硬件的出现,由于优化而导致的不同类别引擎之间的差异正在逐渐蒸发。因此,重用同一个引擎来开发不同种类的游戏,甚至是不同硬件平台的游戏,将成为可能。