3D游戏编程入门经典

更新时间:2023-08-12 23:04:01 阅读量: 初中教育 文档下载

说明:文章内容仅供预览,部分内容可能不全。下载后的文档,内容与下面显示的完全一致。下载之前请确认下面内容是否您想要的,是否完整无缺。

3D游戏编程入门经典

无忧书籍网

如果熟悉了如何利用clr(公共语言运行库)编写代码后,在面临选择开发语言时,您可能已经知道了您的选择。在visual studio .net产品的最新版本中,当编写托管代码时,可以使用4种语言:c#、visual basic .net、managed c++和j#。此外还可以使用从visualstudio .net产品之外的第三方销售商处获得的其他语言,例如cobol或者fortran。

尽管本书中将讨论的概念可以很容易地移植到任何完全兼容cls(通用语言规范)的语言,但实际的代码将仅包含所提到的前两种语言:即c#和visual basic. net。本书中将仅使用c#代码。您可以从/downpage中下载本书配套的安装文件,获取相关代码。

在本章中,您将学习到:

● 定义.net

● 托管代码

● 使用visual studio .net ide

● 在命令行中编译托管代码

● 开发人员

● 游戏开发过程

● 工具

1.1 什么是.net? 自从microsoft公司宣布并发行.net之后,人们一直在尝试指出这种新“事物”到底是什么。根据microsoft公司的市场活动,人们知道它将对计算产生革命性作用。这是一个很宏远的目标,现在断言它是否能够完成目标还太早。但是,它正在一步步地向此目标努力。

当人们讨论.net时,无法确定他们正在讨论.net的哪个部分。microsoft公司发行的其他“产品”或“思想”都不具有如此多的不同形式。紧随.net名字的是众多的产品、服务,甚至是概念,因此指出.net实际上是什么,是非常困难的。

当本书中讨论.net时,它指从.net frameworksdk中可获得的新的开发语言和运行库。该sdk包含.net运行库。而.net运行库包含运行为.net环境编写的应用程序所需要的所有东西。可以认为.net运行库由几部分组成。clr的部件驻留在gac(global assembly cache)中。也包括microsoft .net语言的编译器(c#、vb .net、vj#等等)。可以在图1-1中看到gac。

图1-1 gac

人们对运行.net代码的最常见误解之一是,代码是“解释执行的”,像java代码或者老的visual basic运行库一样。事实上,为.net编写的代码在执行前首先被编译。当编译.net应用程序时,它被编译为一种中间语言(il,intermediary language)。这种il实际上存储在可执行文件中或者已经创建的库中。

il可能在两个位置中的某一处被编译为本机代码(native code)。在安装代码时,可以执行一个称为ngen(native generation,即本机生成器)的进程。它将il直接编译为本机代码,并将所编译的本机代码存储在gac中的特定位置—— 本机程序集缓存(native assembly cache)中。假设在安装时没有编译代码,则代码在第一次执行前必须被编译。在应用程序启动期间,.net运行库中一种称为jit(just in time)编译器的特殊功能在后台执行编译工作。

在后一种情形中,因为发生在后台的编译工作,应用程序的启动时间将受到影响。当启动时间对应用程序非常重要时(例如正在编写游戏时),确保在安装阶段包含ngen步骤是比较明智的。但是,在这期间无法进行某些优化,而如果利用jit编译代码,则可以进行这些优化,因此如果启动时间不是很重要,则可以让.net运行库处理它所能够做的工作。

在本书中将经常提到托管代码。在全书中使用的api被称为managed的directx,.net语言常被称为托管语言。术语“托管”来源于.net运行库具有一个内置的内存管理器这一事实。

在“过去”(只是几年前),使用c和c++编写代码的开发人员不得不自己进行内存管理。当不再需要已分配的内存空间时,必须将其释放,除非希望该内存被“泄漏”,内存泄漏将带来严重的性能问题。更糟糕的是因为直接处理指针,而它很容易破坏项目正在使用的内存。在很多情况下,这将导致很长时间的故障调试,因为通常实际看到出错的地方并不是内存初始被破坏的地方。

人们认为c和c++语言难于掌握,主要是因为具有很多这种类型的问题。许多开发人员不愿意尝试c和c++,也是因为这个原因,他们尝试使用其他没有这些令人头痛问题的高级语言,例如visual basic。尽管这些新语言具有易用易学的优点,但也具有一些缺点。它们的性能无法与c和c++语言相比,在大多数情况下显得特别慢。另外,因为底层操作系统是使用c++开发的,所以这些语言难以实现c++的所有功能。尽管可以使用它们处理很多非常好的工作,但是如果想要获得操作系统的所有性能和优势,只能依靠自己。

与.net运行库的第一个版本相比,.net的大多数内容都已经改变了。microsoft公司几乎完全重新设计了一种新的api,竭力确保开发人员关心的问题都会被解决。这种新的运行库必须易学易用,快速高效,并且不存在令人头痛的内存管理问题。在本书中,将看到.net在这些方面的好处。

提示:

本书设定在visual studio .net ide中编写代码。这不是使用.net编写游戏的需求,也不是使用.net本身的需求,它是本书所选择的ide。图1-2显示了visual studio .net 2003 ide。

该ide提供了编写.net应用程序所需要的所有工具。它不仅包括编写代码所需要的编辑器,而且还有其他大量功能,使得.net应用程序的开发变得容易。它的设计使得您能够方便地创建丰富的内容,如windows应用程序。它也具有一个内置的编译器和调试器,并且无缝集成了所有功能。本书设定使用这种ide进行开发。

图1-2 visual studio .net ide

熟悉这种ide的最好方式是使用它编写一个简单应用程序。典型的计算机编程是编写一个简单的hello world应用程序,该应用程序只是在屏幕上输出hello word文本。老实说,这非常令人厌烦,因此您应当尝试编写更复杂的程序。但也不需要奇特的程序,因为这只是对ide的一个介绍,当然在程序中包括一些用户特**互将更好。在这里将编写一个应用程序,询问用户的姓名和出生年份,然后输出用户的当前年龄。

1.3.1 c#代码 现在启动visual studio .net 2003 ide。首次启动时,应当看到图1-2所示的默认启动页面。单击该页中的new project按钮,启动一个新的项目。如果没有显示这个页面,也可以单击file | new project菜单项,或者按下ctrl+shift+n快捷键。这将产生一个new project对话框,如图1-3所示。

应当首先尝试c#代码,因此在new project对话框中,从左边的列表框中选择visual c# projects项,从右边的列表框中选择console application项。为项目命名,然后单击ok按钮创建该项目。这将创建一个新的控制台应用程序,当前它什么都不执行。使用程序清单1.1中的代码替换自动生成的代码。

图1-3 new project对话框

程序清单1.1 简单的c#控制台应用程序

using system;

class consoleapp

{

static void main()

{

console.write("hello world c#!please enter your name:");

string name = console.readline();

console.write("hello {0}, please enter the year you were born:", name);

int year = int.maxvalue;

while(year == int.maxvalue)

{

try

{

year = int.parse(console.readline());

}

catch (formatexception)

{

console.write("you did not enter a valid number. ");

console.writeline("please enter an integer, such as 1975. ");

}

}

console.writeline("you must be approximately {0} years old! ",

datetime.now.year-year);

console.writeline("press the key to exit the application");

console.read();

}

}

如果您已经熟悉了c、c++或者java,则c#语言的语法与这些语言非常相似。尽管c#的底层运行库仍然是clr,但是它的语法源于这些语言,熟悉它们的开发人员很容易进行移植。该代码根本不复杂,它在询问用户的姓名和出生年份之前,首先使用console类输出一个简单的消息(该消息包含hello world)。注意,该应用程序接着询问出生的年份,直到用户输入一个有效的数值。它最后利用一个简单的公式输出用户的当前年龄。图1-4显示了该应用程序的运行过程。

图1-4 第一个应用程序

1.3.2 vb .net代码 如果读者更熟悉visual basic语法,很可能能够理解前文中的大多数代码。这两种语言的语法差别不是很大,但还是应当尝试编写一个visual basic .net版本的应用程序。再次启动一个新的项目,并采用在c#代码中所使用的相同指令,其中不同之处是项目的类型。应当选择visual basic projects项目,创建该项目后,使用程序清单1.2中的代码替换自动生成的代码。

程序清单1.2 简单的vb .net控制台应用程序

module module1

sub main()

console.write("hello world c#!" + vbcr + vblf + "please enter your

name:")

dim name as string = console.readline()

console.write("hello {0}, please enter the year you were born:", name)

dim year as integer = integer.maxvalue

while year = integer.maxvalue

try

year = integer.parse(console.readline())

catch fe as formatexception

console.write("you did not enter a valid number. ")

console.writeline("please enter an integer, such as 1975. ")

end try

end while

console.writeline("you must be approximately {0} years old! ",

datetime.now.year - year)

console.writeline("press the key to exit the application")

console.read()

end sub ‘main

end module ‘ module1

如您所见,从c#到vb .net的代码移植并不复杂。因为这两种语言运行于相同的运行库之上,所以这两段代码仅存在微小的语法差别。也应当注意到,这两段代码产生了相同的结果。这是.net的一种强大功能,它允许开发人员使用他最熟悉的语言编写代码。如果使用了编程语言x,您不必担心不能利用编程语言z中的功能y。因为每一种托管语言使用了相同的底层运行库,它们都具有相似的功能集。

到目前为止,使用visual studio .net ide是开发.net应用程序最容易的方法,但它并不是仅有的方法。.net运行库将c#、vb .net和vj#的编译器随其运行库一起发布,因此只要有一个文本编译器和.net运行库,就能够创建任何.net应用程序。

提示:

为了实际开发并使用.net应用程序,需要.net运行库。如果没有安装,您可以从microsoft的网站/net处下载。

打开一个文本编辑器(例如notepad),输入前面某一个程序清单中的代码。保存该代码,并打开一个命令提示符。在使用.net运行库中的编译器之前,需要设置路径,将其指向底层.net framework的位置。在命令窗口中,输入下面的命令更新路径:

set path=%path%;c:\windows\\framework\v1.1.4322

警告:

如果windows文件夹不是在c:\windows处,当更新路径变量时,确保输入了正确的windows文件夹位置。

命令行编译器位于上文所列出的文件夹中,与一些组成.net运行库本身的程序集相同。c#的编译器是csc.exe,vb .net的编译器是vbc.exe。这些可执行文件具有相似的命令行参数集,可以使用单个参数/?来显示这些编译器的所有参数。

假设使用了该代码的c#版本,并将文件保存为class1.cs,可以利用如下命令将应用程序编译为名为app.exe的可执行程序。首先定位到代码文件所在的文件夹,然后运行下面的命令:

csc /out:app.exe /target:exe class1.cs

输出应当类似于下面的内容:

microsoft (r) visual c# .net compiler version 7.10.3052.4

for microsoft (r) .net framework version 1.1.4322

copyright (c) microsoft corporation 2001-2002. all rights reserved.

现在运行该应用程序,它将产生与使用ide作为开发环境时相同的结果。

在2001年,视频游戏的收入首次超过了电影工业的票房收入。该产业欣欣向荣,并需大量优秀的开发人员。如果一个公司能够在某个顶级游戏中花费上百万美元,那他们很自然地会对游戏开发团队非常挑剔。本章将讨论游戏开发的过程和制作这些伟大游戏的团队。

在最近几年中,作为directx组的一员,我非常高兴能够与大量的游戏开发人员一起讨论问题。尽管每一个开发人员都是独特的,但是他们具有相似的特征。其中最显然的特征之一是他们都绝对喜爱游戏。当然,这是可以预料的。游戏开发是一项艰苦的工作,它时间长、任务紧,压力大。那些不具有献身游戏精神的人应当去编写没有这些压力的软件。

尽管目前对于一个游戏工作室来说,宣布在开发游戏中的花费是不常见的,但是许多游戏在开发期间花费了上百万美元,这是公司发行游戏的真实投资。许多游戏需要多年的开发,这需要对游戏本身和开发人员、艺术家、设计人员以及任何参与制作的人员具有绝对的信心。很多时候,发行商屈服于压力,并在游戏准备好之前就发行。另外一种情形就像duke nukem forever(3d realms所发行的一款游戏)该游戏的开发时间超过了5年,发行商直到它最终完成才发行。

回到视频游戏开发的早期(古老的拱廊和atari家庭系统),游戏通常由一个或两个人编写,编写时间是几周而不是几年。由此可以看到当今的视频游戏世界的巨大进步。回到那时您可以制作一个非常简单的游戏,例如galaga,它只有一个级别,玩家只能反复地在该级别中进行游戏。它的图形简单,但非常有趣并且令人着迷。它的编写困难吗?任何曾经看到该游戏的开发人员应当认识到它非常容易编写。

相反,当今的游戏功能非常巨大,必须设计、开发庞大的世界,并且创建一些艺术资产。大多数游戏包含多个级别和非常复杂的玩法,而不是早期游戏中所看到的向前向后移动并开火。所有这些先进的特性需要越来越多的开发时间,并且由于当今游戏玩家期望这些先进的特征,所以任何大预算的项目似乎都不可能在短短的几周内完成。

当今的大游戏也对游戏开发人员的繁荣添加了一层复杂性,因为捕获大众注意的游戏不再可能由个人完成。实际上个人(甚至是一个小组)也不可能将所编写的游戏放到货架上出售,因为计算机软件商店销售游戏的开销是非常高的。

大多数游戏开发初学者将他们编写的游戏作为共享软件发行,甚至是免费软件。有大量的网站为共享游戏开发作贡献,该团体由其他许多游戏开发人员组成,他们提供有价值的反馈、测试和论坛,在论坛中可以展示新思想以及炫耀您的最新创作。初学的游戏开发人员不可能通过创建这些小的共享游戏变得富有,但却使得他们能够开发重要的“样片(demo reel)”,在大预算项目中申请一个游戏开发位置时,可以使用这些样片。

正如引言中提到的一样,本书将引导您学习开发一个高性能、完全3d的游戏所需要的技能,使得您在某一天能够开发自己的样片。在这之前,您需要询问自己:准备好成为一个游戏开发人员了吗?并询问自己以下几个问题:

● 您喜欢玩视频游戏吗?

● 您是否曾经沉迷于游戏而忘记了时间?

● 您常常在思考这些世界是如何创建的吗?

● 您是否相信自己具有能够转换为下一个伟大视频游戏的构想?

● 您对当今存在的所有相同的老游戏感到厌烦了吗?您有关于重新定义流行视频游戏的构想吗?

● 您希望我闭嘴,并马上告诉您如何编写自己的视频游戏吗?

如果您对这些问题中的一个或多个回答了“是”,则表示您非常渴望成为一名游戏开发人员。或许您已经具有某些构想,但不具备将头脑中的构想通过代码转化为实际游戏能力。本书将通过实际的游戏编程快速教会您这些技能。

所有的游戏开始时都只是一个构想。如果没有构想,能编写什么呢?如果只是随机的编码,能够希望它在未来成为一个伟大的作品吗?尽管这对抽象画家的工作可能有效,但软件开发人员不能在没有计划的情况下做任何事情。但是,只有构想,并不意味着可以开始编写代码。

我曾经看到(并且将不断看到)的最大的错误是,开发人员提前进行代码的编写。除非代码非常简单,不用动任何脑筋,那么您可以立即开始编写代码,否则在只有一个构想的情况下就开始编写代码,决不是好的做法。在没有计划的情况下开始编写代码,所带来的只是更大的工作量、更多的反复编码以及更长的开发时间。

在编写代码之前,应当开发一个设计规范。该文档应当包含关于如何设计代码、对象如何交互和对象可能具有的各种属性的特定信息。如果没有这一步就直接编码,在大多数情况下将导致解决错误的问题。即使您能够快速的解决这些问题,但是如果没有解决正确的问题,工作就是毫无意义的。结果将会花费更多的时间为真正的问题寻找解决方案。

您也应当花一些时间将构想记录到纸上(或者计算机文件中)。如果不想让您关于下一个伟大的计算机游戏的构想非常含糊,以至于设计规范无法描述需要求解哪些问题,那么您最好确保您的构想是生动的。将构想展示给朋友,让他们提问题,并确保这些问题能够在您的文档中得到回答。如果您的构想类似于“编写一个令人恐怖的第一人射手游戏”,那么说明您还没有很好地思考您的构想。玩家具有什么类型的武器?可以获得什么类型的游戏模式?支持多人游戏吗?在采取进一步的行动之前,您需要很好地思考您的构想。

如果您正在尝试为发行商开发游戏,那么也需要开发一个游戏建议。尽管将这一建议写入文档是非常重要的,但在大多数情形下,更需要一个足够好的演示片段。如果只是基于一个构想,发行商通常不会将项目交给您(除非您在游戏开发方面已经具有很大名气,如果是这样,您将不需要学习本书)。

编写伟大的游戏并不只是编写好的代码。当然这并不表示代码的编写不重要,在创建这些游戏时,还需要考虑其他大量因素。所有游戏开发都需要一个好的工具集。

当今的大多数游戏(即使是严格的2d游戏)都有大量的艺术成份,这些艺术成份是采用3d模型软件包开发的。这些工具对于参与游戏开发的艺术家来说是无价的,它们使得浮现在脑海里的构想成为现实。尽管在本书中并不讨论如何创建所需要的艺术,但会提到使用的工具。当今所使用的两种非常流行的数字内容生成工具是maya和3d studio max,分别如图1-5和图1-6所示。

但是,创建艺术并不只是3d建模。即使一个模型由上百万个多边形组成,如果没有纹理的话,看上去也是很乏味的。很多工具可以用于创建纹理,例如photoshop(如图1-7所示)和paint(在安装windows时附带安装了paint,如图1-8所示)。

图1-5 使用maya建模一个宇宙飞船

图1-6 使用3d studio max建模一个妖怪

图1-7 使用photoshop创建纹理

图1-8 使用paint创建纹理

开发和艺术加工工具都是非常好的工具,您将在游戏开发中使用到它们。还有一些其他类型的工具也非常重要,但很多人甚至没有考虑到它们,例如一个优秀的源码控制管理工具。

假设您的游戏具有声音,那么您很可能需要用于处理声音文件的工具。可以使用windows附带的sound recorder(如图1-9所示),也可以使用比较复杂的wavestudio,它是creative lab的音频软件的一部分(如图1-10所示)。

图1-9 使用sound recorder

图1-10 使用creative lab的wavestudio

利用所有这些工具为游戏创建内容,您可能需要一个能够为整个团队存储内容的工具。很可能有大量的信息需要存储,以及定期备份。最好是只备份新更改和添加的记录。大多数源码控制管理工具至少提供其中某个功能。visual studio 中的visual source safe是一个简单的源码控制管理工具,它适用于小的团队。该工具不包含大型团队所需要的许多强大功能,例如允许多人操作一个文件和自动合并相互冲突的改变等功能,但是对于一个小团队中的小项目来说,它通常能够满足需要。还有大量其他的源码控制工具(例如perforce),因此您需要做一个小的调查,找出哪种工具适合您和您的小组。当然,使用任何一种工具都比没有工具强。

本章主要介绍了.net的基础知识。相信您已经对.net和托管代码有大致的了解,并且采

用两种语言编写了一个简单的应用程序。您也学习了如何在集成开发环境之外编译这些应用程序。至此,已经介绍了如何开始进行游戏开发的相关基础,并且介绍了游戏的开发人员、开发过程以及游戏开发所需要的某些商业工具。

第2章中将介绍游戏开发相关的内容。

通过第1章的简短介绍后,现在开始本书的正题:3d游戏编程。显然,这个主题非常复杂,虽然您的第一个游戏无法发行,但是它将帮助您了解游戏开发基本原则。

将游戏构想转换为实际的程序,是您所能够获得的最好体验。将大脑中的构想转换为其他人能够欣赏的实际内容,是所有开发人员的目标,但当这些构想与游戏相关时,回报似乎更大,而您肯定期望更大的回报。

在本章中,将学习:

● 提出游戏构想

● 2d和3d编程差别

● 细化规范

2.1 提出游戏构想 所有的事物都起始于构想,游戏也不例外。游戏构想可以源于您思考的任何方面。或许您看到您所喜爱的其他游戏,但是您认为如果在某些方面作一些变化,它将会更好。或许您做了一个梦,它能够转换为一个极好的游戏。或许您意识到两个不相关的游戏能够组合在一起,从而创建一个极佳的游戏。无论您的灵感源于哪里,在您开始制造游戏之前,都需要这样的灵感。

不幸的是,由于还没有人发明一本能够阅读思想的书,在本书的示例游戏中您只能相信我的思想。本书之所以选择这些游戏,原因有很多,主要考虑到开发的难度。但是,每一个例子都分属不同的类型,它们覆盖了游戏开发过程中遇到的大量不同主题。

第一种类型的游戏是拼图游戏(puzzle game)。您很可能看到过或玩过拼图游戏。tetris是一个典型的拼图游戏,几乎每个人都曾经听过,甚至因为玩这款游戏而废寝忘食。似乎每个人也都编写过tetris游戏,因此您很可能不愿意选择它作为您即将编写的拼图游戏。另外tetris是一个2d游戏(尽管已经存在3d类型的tetris游戏),因此,您也很可能希望跳过它。

取而代之的是,您应当编写稍微具有特色的游戏。该游戏基于一个面板。该面板由一系列的立方体组成,每个立方体处于一种未确定模式,并且每个立方体至少与一个其他立方体直接相邻。棋盘是满足这些标准的一个面板例子。其中每个立方体具有一种特定的颜色,当玩家走到某个立方体上时,它将变为另外一种预定颜色。在某一级别中,当每个立方体处于正确的颜色时,该级别结束。

细化提议 正如第1章所介绍的一样,一旦游戏的基本构想已经建立,您应当花费一些时间讨论游戏的各种提议,以确保您的思考足够全面。对于这个游戏,下面是有助于细化提议的一个列表:

● 称为blockers的拼图游戏

● 只支持单个玩家

● 完全3d环境

● 得分基于完成一个级别所使用的时间

● 每个级别由一系列相邻的立方体组成,例如一个棋盘

● 每个立方体一种颜色

● 在某个级别中,当每个立方体颜色相同,并且是该级别预定的“终止”颜色时,该级别结束

● 每个级别有一个最大时间限制

● 每个立方体有一个颜色列表—— 最少包括两种颜色,最多包括六种颜色

● 玩家通过在立方体上“跳动”来进行游戏,跳动时将立方体的颜色改变为列表中下一种颜色

● 起始级别中,每个立方体的颜色列表有两种颜色

● 在更高级别中,每个立方体的颜色列表将具有更多的颜色,因此游戏的难度增加

● 如果玩家能够完成这些级别,则允许颜色列表返回初始状态,来增加游戏的难度

● 如果玩家不能在最大时间限制内完成游戏,则游戏结束

这个列表描述了游戏的每个特征吗?很可能没有,但是它回答了在策划游戏之前需要回答的主要问题。策划阶段并不是对游戏的每个功能进行细化,在您开始开发之前理解这一点是非常重要的,但是更需要您思考游戏需要的特征。在不考虑实际需要的功能的情况下就提前投入并编码,将导致后面的工作更困难,以至于您可能需要补充某些遗失的内容,或者因为鲁莽的计划而导致工作完全错误。

根据计划,现在您可以开始设计游戏中使用的对象模型。对象图并不复杂。您具有神秘的游戏引擎,它保存了关于玩家、当前级别和绘图设备(rendering device)的信息。绘图设备用于将游戏的图形显示在显示器上。玩家需要某种类型的可视化表示,绘图设备将处理这些。

该游戏的大多数地方需要当前级别的信息。当前时间非常重要,因为它用于确定玩家的分数和游戏结束时间(如超时,游戏即结束),因此当前级别必须访问它。

实际的级别存储在文件系统中,位于应用程序的媒体目录中。因为当前级别需要加载一个已经存在的级别,所以它也需要访问这些信息。级别需要记录的信息是组成级别的立方体列表。每一个级别需要至少两个立方体,但是可以添加更多的立方体以满足更高的级别。

该游戏并不容易编写,但是它并不需要创建多个对象确保满足游戏的目标。该游戏也非常好玩,只要更难的级别不会难到让玩家无法完成。一个游戏决不能够让玩家感到苦恼。如果游戏不好玩,则没有人会玩,该游戏也不可能成功。

认识到这个游戏(实际上任何游戏都是这样)不需要创造为完全3d的世界,是非常重要的。考虑到您的显示器很可能是一个矩形的平板,最终3d图形将显示在一个2d平面上。创建一个2d组图(sprite)的集合,该集合将覆盖每一种可能的场景,但是这种情况下所需要的艺术资产是巨大的。查看下面的例子。

假设已经安装了directx sdk summer 2004 update,请加载directx sample browser(图2-1),确保managed选项是左边被选中的惟一项。单击direct3d标题,向下寻找empty project项,并单击install project链接,遵循向导的步骤创建项目(将该项目命名为“teapot”)。项目创建后,将其加载到ide中。

图2-1 directx sample browser

这将创建一个新的“空”项目(它实际上生成了某些用户界面控件,但是现在,那些都被忽略)。但是,该项目中还不存在任何3d,由于这个练习的目的是演示为什么希望编写3d游戏,所以最好添加一些内容。

在这个项目中添加少量的代码,生成一个慢速旋转的茶壶。您要添加几行代码,使得该应用程序能够进行图形绘制,在本章中不对这些代码功能进行解释。在本书的后面将给出大量的篇幅解释这些代码,但对于目前的演示来说,它们不是必需的。假设您将项目命名为“teapot”,打开代码文件teapot.cs并将下面两个变量添加到这个类文件中:

private mesh teapotmesh = null; // mesh for rendering the teapot

private material teapotmaterial; // material for rendering the teapot

现在创建茶壶和用于绘制场景的材质,找到该类中的oncreatedevice方法,将下面的代码添加到该方法的结尾处:

// create the teapot mesh and material

teapotmesh = mesh.teapot(device);

teapotmaterial = new material();

teapotmaterial.diffusecolor = new colorvalue(1.0f, 1.0f, 1.0f, 1.0f);

为了使茶壶更真实,还需要光照效果(这些会在本书的后面详细解释)。找到类中的onresedevice方法,将下面的代码添加到该方法的结尾处:

// setup lights

device.lights[0].diffusecolor = new colorvalue(1.0f, 1.0f, 1.0f, 1.0f);

device.lights[0].direction = new vector3(0,-1,0);

device.lights[0].type = lighttype.directional;

device.lights[0].enabled = true;

找到类中的onframerender方法并在beginscene后面添加如下代码:

device.transform.view = camera.viewmatrix;

device.transform.projection = camera.projectionmatrix;

device.transform.world = matrix.rotationx ((float)apptime);

device.material = teapotmaterial;

teapotmesh.drawsubset(0);

运行该应用程序,生成一个茶壶。在3d绘图世界中,茶壶有一段相当有名的历史。对于绘图来说,它是第一个可利用的“免费”模型。在那时,当前一些复杂的模型包都不存在,这是因为创建实际的3d模型非常复杂。任何免费的东西都会受欢迎。茶壶具有大量的属性,使它成为一个很好的测试模型:具有曲面表面,能够遮挡自己,并且很容易识别。

您创建的这个应用程序生成了茶壶,并慢慢地旋转,使得您能够看到各个角度,如图2-2所示。

图2-2 一个旋转的3d茶壶

当该应用程序运行时,可以看到茶壶慢速地旋转。意识到该应用程序仅需要的媒体是茶壶模型是非常重要的。实际上这个模型不需要任何媒体,因为使用了mesh类(将在后续章节中讨论mesh类)中的一个方法,它创建了茶壶。因此在该应用程序中以最小的媒体开销获得了一个非常漂亮的茶壶。

现在将这与在2d世界中绘制一个茶壶相比较。使用directx向导创建一个新的项目。

如果您安装了本书下载站点()中的安装文件,将注意到一个media文件夹,它包含了您将编写的每一个例子的媒体。找到2dteapot.bmp文件,在2d环境中生成一个茶壶需要用到该文件。2d和3d世界的最大差别是对媒体的需求。这个位图文件仅显示了茶壶的一个角度,相反3d版本可以从任何角度显示茶壶。为了在2d版本中以任意角度显示茶壶,需要茶壶每一个可能位置的独立媒体。假设对于旋转的每一度都需要一幅图像(总共360幅图像)。现在假设您希望围绕着任意轴(x,y,z)旋转茶壶,则大约需要46,656,000幅茶壶的不同角度的图像。假设一个图形加强游戏(例如unreal tournament)只能利用2d组图绘图,则您将需要整张dvd的内容来显示这个茶壶,并且需要多名艺术家花费多年时间来创建如此巨大的东西。

如果您拥有一个能够创建高细节度的3d模型艺术家,则您显然能够更自由地在场景中创建“有限的”的媒体。单个模型可用多种方法进行渲染,例如不同的亮度、缩放度、位置和旋转角度,而在2d中这是不可能实现的。尽管这种能力不能自由获得。

3d应用所带来的自由度提供了大量的处理能力,这种能力非常巨大,以至于基于它组建了整个工业。尽管在图形商业领域中有大量的公司,但领导者是nvidia和ati。最近在图形卡方面出现了大量的革新,它们发展得的如此快速,比某些通用的cpu还要快。

现在的图形卡(这些图形卡是directx 9兼容的,即它们至少支持shader model 2.0)能够以每秒上百万的速度生成三角形。其中shader将在本书的后面讨论。这些三角形是创建3d模型的基本多边形。如图2-3所示为仅使用三角形绘制的茶壶。

图2-3 茶壶的基本框架

如您所见,整个茶壶由数百个三角形组成。之所以使用三角形,是因为它们的顶点共面,并且是具有这种特性的惟一多边形,这使得绘制3d图形的计算更加容易。可以利用大量的三角形模拟任意3d形状。

那么,这个游戏需要在3d中编写吗?显然不需要,您可以完全利用组图编写代码,而不用绘制任何3d内容,但是这又有什么乐趣呢?大量的“自学”类型的游戏开发书籍介绍了如何绘制2d内容,但只有极少数介绍3d世界,而恰恰是3d世界使得当今的游戏如此大型。尽管2d游戏开发还没有消失(可能永远都不会完全消失),但是如果您希望在游戏产业中谋求一份工作,则必须学会3d编程。

将大量的繁忙工作放置到一边,您可以从游戏一个非常重要的方面着手—— 游戏规范。在开始编写代码之前,一定要花费一些时间思考您所需要解决的问题。实际上,每个初级游戏开发人员在开始第一个游戏时,都是提前投入编写代码。到后面当意识到快速方法不能满足您的意图时,您将需要做更多的工作。

那么,就这个游戏而言,需要解决什么问题?显然您需要某种类型的游戏引擎,它将是操作的大脑。需要一个玩家对象、一个图形设备和一种保存级别信息的方式。演示开发规范的常用方式是利用uml(统一建模语言)语言,如图2-4所示。

图2-4 游戏对象的一个简单uml框图

如果您熟悉uml,则会熟悉该图。否则您也应当理解这个文档的意思。首先,它将问题划分为多个组成游戏的逻辑部件。在这个例子中,这些对象是重要的游戏引擎、玩家、级别、块的列表和每一个块。每一个对象的公共属性和方法列举在该对象的框中,因此您能够迅速了解每一个对象的大小和范围。

除了框图之外,还需要为游戏作些什么?您显然需要一个中心区域来控制所有的内容。在本例中,这个中心区域是游戏引擎。请注意该uml文档,其中游戏引擎维护图形生成设备和代码(其中代码通过initializegraphics方法隐含提出)。游戏引擎需要知道的主要内容是:

● 玩家对象

● 当前级别

● 游戏是否结束?

● 如果结束,玩家是否通过了该级别?

● 如果结束,玩家是否赢得了游戏?

游戏引擎也需要存储其他的信息,例如绘图设备的信息以及游戏的对象,但这些将在私有方法中完成,图2-4中的uml没有显示该方面的内容。下一个对象是玩家,它非常简单。玩家所需要的信息只是它的当前位置和在场景中生成它自身的功能。在游戏引擎中,玩家比起开发对象来说,是更抽象的概念。这里的对象主要用于控制如何可视化地显示玩家。

游戏引擎中的其他内容都源于levels对象。实际上,该对象非常简单,因为它只需维护一些其他的对象,主要是blocks集合。每一块存储在该级别中控制它自己所需要的所有信息,包括可能的颜色列表,和颜色是否将变换。

得到基本规范之后,您可以开始编码了。您可以确信规范的大多数内容从现在到完成时不会发生变化,现在是您开始编码的最好时机。

在本章中,介绍了一些关于编写游戏的新规则,使您了解了游戏的构想、对象设计和规范。在第3章中,将开始编写初始化图形设备的代码。

现在游戏构想已经完成,对象模型也有了足够的信息,并且列出了游戏的规范,您已经做好了充足的准备转入您期望的内容:编写游戏的代码。通常,在游戏开发之前,您需要编写许多“样板”代码,这些代码需要完成的任务包括枚举系统中的设备并选择最适合的设备绘制场景;创建并维护这些设备。因为directx sdk summer 2004 update中包含了一个样本框架,您可以在代码中直接使用。该样本框架能够为您处理大部分上面提到的任务,它能够简化您的工作并节约大量的时间。

本书中的例子使用了这个框架。

在本章中,您将了解:

● 如何创建项目

● 如何使用样本框架枚举设备

3.1 创建项目 提示:

在本书的剩余部分中,设定您在所有的开发中使用了visual studio .net 2003。如果您不想使用这种环境,可以回到第1章“游戏的开发和托管代码”,查看关于在命令行中编译代码的内容,它允许您使用任何文本编辑器或者集成开发环境。

启动visual studio .net 2003程序,单击起始页的new project按钮。如果您不使用起始页面,则单击file | new | project菜单项,或者使用快捷键ctrl+shift+n。选择visual c# projects部分中的windows application项(参考第1章中的图1.3)。将该项目命名为blockers,它将是游戏的名字。

在查看自动生成的代码之前,首先将样本框架的代码文件添加到您的项目中。通常,我将这些文件放置到一个独立的文件夹中。鼠标右键单击solution explorer中的项目,选择add菜单中的new folder项。将该新文件夹命名为framework。鼠标右键单击framework文件夹,选择add菜单中的add existing item项。查看directx sdk文件夹,您将发现样本框架文件位于samples\managed\common文件夹中。选择所有文件并将它们添加到您的项目中。

添加样本框架之后,您可以删除自动生成的代码。这些代码主要用于生成windows forms

应用程序,因此它与您将编写的游戏代码没有任何关系。用程序清单3.1中的代码替换已存在的代码和类(名字为form1)。

程序清单3.1 空框架

using system;

using system.configuration;

using microsoft.directx;

using microsoft.directx.direct3d;

using microsoft.samples.directx.utilitytoolkit;

public class gameengine : idevicecreation

{

///

/// entry point to the program.initializes everything and goes into a

/// message processing loop. idle time is used to render the scene.

///

static int main()

{

using(framework sampleframework = new framework())

{

return sampleframework.exitcode;

}

}

}

对于这段新代码,应当强调三点。首先,除了static main方法之外,所有的内容都删除了,

而且static main方法也被修改了。剩余的代码是windows窗体设计器的支持代码。因为您在该应用程序中不使用这个设计器,所以这些代码是不相关的,可以删除。第二,这段代码无法通过编译,因为游戏引擎类应该完成的两个界面还没有实现。第三,这段代码没有任何功能。

在您开始解决后面两个问题时,需要添加一些引用。您将在该项目中绘制独特的3d图形,所有需要为完成绘制工作的程序集添加引用。本书使用managed directx程序集来完成该工作,单击project | add | reference菜单项,弹出如图3-1所示的对话框。

如果已经安装了directx 9的summer 2004 sdk更新(您应当安装,因为本书中的代码需要),您将注意到每个managed directx程序集都可能有多个版本。选择最新的版本(版本1.0.2902.0)。对于该项目,您需要在引用中添加三个不同的程序集:

● microsoft.directx

● microsoft.directx.direct3d

● microsoft.directx.direct3dx

图3-1 add reference对话框

directx程序集包含了一些数学构造,用于组成绘图所需要的计算。其他的两个程序集分别包含了direct3d和d3dx的功能。添加引用后,查看程序清单3.1中的using子句,确保命名空间被引用。这一步确保了您不需要完全限定类型。例如,如果不添加using子句,为了声明一个direct3d设备的变量,您需要按如下格式:

microsoft.directx.direct3d.device device = null;

这些using子句减少了大量的录入工作(没有人愿意为声明每一个变量录入所有的字符)。您已经添加了using子句,所以可以采用如下方式声明相同的设备:

private device device = null;

如您所见,采用这种方式声明设备更加简洁。完成这些任务之后,您可以开始修改应用程序中的一些编译错误,并准备编写第一个3d游戏。您已经实现的接口只有idevicecreation,它能够让您控制设备的枚举和创建。

您可能会思考:“枚举设备?我只有一个显示器!”先进的图形卡支持多个显示器,即使您只有一个,您仍然需要从许多不同的模式中选择一个。显示的格式可以不同 (您可能已经在windows桌面的设置中看到这种不同,选项包括16位颜色或者32位颜色) 。全屏模式的宽度和高度可以设为不同的值,甚至可以控制屏幕的刷新率。总之,需要考虑大量的事情。

为了修改应用程序中的编译错误,添加程序清单3.2中的代码。

程序清单3.2 实现接口

///

/// called during device initialization, this code checks the device for a

/// minimum set of capabilities and rejects those that don’t pass by

/// returning false.

///

public bool isdeviceacceptable(caps caps, format adapterformat,

format backbufferformat, bool windowed)

{

// skip back buffer formats that don’t support alpha blending

if (!manager.checkdeviceformat(caps.adapterordinal, caps.devicetype,

adapterformat, usage.querypostpixelshaderblending,

resourcetype.textures, backbufferformat))

return false;

// skip any device that doesn’t support at least a single light

if (caps.maxactivelights == 0)

return false;

return true;

}

///

/// this callback function is called immediately before a device is created

/// to allow the application to modify the device settings. the supplied

/// settings parameter contains the settings that the framework has

/// selected for the new device, and the application can make any desired

/// changes directly to this structure. note however that the sample

/// framework will not correct invalid device settings so care must

/// be taken to return valid device settings; otherwise, creating the

/// device will fail.

///

public void modifydevicesettings(devicesettings settings, caps caps)

{

// this application is designed to work on a pure device by not using

// any get methods,so create a pure device if supported and using hwvp.

if ( (caps.devicecaps.supportspuredevice) &&

((settings.behaviorflags & createflags.hardwarevertex

processing) != 0 ) )

settings.behaviorflags |= createflags.puredevice;

}

查看声明的第一个方法,isdeviceacceptable方法。当样本框架忙于枚举系统中的设备时,它为所发现的每一个组合调用了该方法。注意该方法是如何返回一个布尔值的。通过这个返回值可以向样本框架指出该设备是否满足需求。然而,在您查看第一个方法中的代码之前,注意已经声明的第二个方法,modifydevicesettings。在创建设备之前样本框架会调用该方法,使您能够选择您需要的任何选项。仔细选择这些选项,因为这可能造成设备创建失败。

现在,回到第一个方法。首先查看它的参数。第一个参数的类型是caps,是capabilities的缩写。该结构用于保存关于特定设备的信息,这些信息将帮助您确定该设备是否是您希望使用的设备类型。后面两个参数是该设备的专用格式:一个用于反向缓冲区,另一个用于设备的格式。

本文来源:https://www.bwwdw.com/article/dajj.html

Top