作者:clayman
clayman_joe@yahoo.com.cn
仅供个人学习之用,勿用于任何商业用途,转载请注明作者^_^
3D游戏编程,显然,这是一个很复杂的主题。首先,让我们来学习一些编写任何游戏都会用到的基础知识。把你的创意转变为真实的游戏是一种很有趣的经历。把你的想法雕琢为一个人们可以获得乐趣的游戏,是每个开发者的目标。
所有的一切都来源于创意,游戏也是如此。游戏创意可以来自于生活中所有地方。也许你看到了另一个很好玩的游戏,但总觉得稍做修改它将会更好玩。也许你昨晚的梦就能创造一个完美的游戏。无论你的灵感来自于哪里,都必须先有了灵感再编写游戏。
当然,对于我们即将要编写的游戏来说,你将使用我的创意。选择这几个游戏是有很多原因的,但主要原因还是和难度有关。无论如何,我们将会尽可能的覆盖所有你可能遇到的游戏类型。
第一种类型是益智游戏(puzzle game)。经典的Tetris(俄罗斯方块)就是这类游戏的代表。几乎所有学习游戏编程的人都克隆过这个游戏,因此,跳过它,编写一些特别一点的东西。游戏中将有一个可以在板子上跳动的角色。板子由一系列的立方体构成,类似一个棋盘。每个立方体都有一种特定的颜色,当角色跳到这些立方体时,他将会变为另一种指定的颜色。当所有方块都和这一关制定的颜色相同时,这一关就算赢了。
设计细节
当游戏创意浮现出来之后,就应该花一些时间把它写为游戏方案。并确保对所有可能遇到的问题都有解决方法。对于这个游戏来说,我们列出了一下条目作为方案的细节。
l 名字叫做Blockers的益智游戏
l 单人游戏
l 全3D环境
l 基于完成每一关的时间来计算得分
l 每一关都由一系列相连的立方体构成,就像建立在棋盘上一样
l 每个立方体都是单独的实心颜色
l 当每一个立方体的颜色都和本关预定的结束颜色相同时,管卡结束
l 每个立方体都有一个颜色列表,这个列表最多包含6种颜色,最少2种
l 玩家通过调到另一个立方体来移动,同时,所跳到的立方体变为列表中的下一个颜色
l 游戏开始时,颜色列表中只有2种颜色
l 高级关卡通过增加颜色列表中的颜色来增减难度
l 如果玩家通关了,难度将回到一开始的状态
l 如果玩家不能在最大的制定时间完成本关,则游戏结束。
这是否是一份毫无遗漏的文档了呢?也许不是吧。但他回答了大部分我们将如何编写游戏的问题。明白在开发之前,你不可能把所有性
都设计好了是很重要的,随着开发和思考的深入,游戏将会需要越来越多的特性,但是,无论如何都不能贸然在没有考虑到底想完成些什么特性时就开始编码,这会导致更大的混乱。
有了方案,就可以开始设计游戏中的对象模型了。你将有一个神奇的游戏引擎,由他来维护玩家信息、当前关卡、以及渲染的设备。渲染的设备用来完成所有美妙的绘图工作。玩家总是需要一些直观的表示,渲染设备会处理好这个工作。
大多数的游戏都需要当前关卡的信息。当前的时间是最重要的,因为最终的得分和是否结束游戏都是由他来决定,因此,当前的管卡必须可以访问时间。
实际的关卡将被储为文件,保存在程序的media目录中。当前的关卡必须是所存在关卡中的一个,所以关卡必须能访问这些文件。实际上,关卡只要追踪组成每一关的立方体列表就可以了。每一关最少有两个立方体,当然高级别的管卡中将添加大量立方体。
虽然编写游戏不会很容易,但你看,要达到目标,所创建的对象并不多。另外,为了让游戏有具有可玩性,只要让高级关卡不会难到使玩家有挫败感就可以了。没有比让玩家苦恼的游戏更糟的东西。如果游戏不好玩,那么就不会有人玩它,就不会成为成功的游戏。
为什么需要3D游戏
你需要认识到这个游戏(实际上是所有游戏)根本不“需要”作为全三维的场景来渲染。考虑到所有显示器都是平面的,再好看的3D场景都要映射为2D的图片。完全可以把游戏中所有可能的场景设置为一组2D的精灵(sprite),当然,这样需要更多的美工操作。
假设你安装了DirectX SDK的任何版本,打开DirectX Sample Broeser,确保只选择了左边的C#选项,点击左上的Direct3D标题,找到Empty Project项目,并安装它。之后,在VS中打开项目。
这样我们就创建了一个“空白”项目。(实际上它还绘制了一些UI控件,但先忽略他们)。但是,这里还没有任何3D 的物体,这一部分的内容是告诉你为什么我们需要编写3D游戏,因此,来绘制一些东西吧。
添加几行代码来显示一个缓慢旋转的茶壶。添加如下变量
private Mesh teapotMesh = null;
private Material teapotMaterial;
接下来创建茶壶和材质,在 OnCreateDevice 方法的最后添加如下代码:
teapotMesh = Mesh.Teapot(e.Device);
teapotMaterial = new Material();
teapotMaterial.DiffuseColor = new ColorValue( 1.0f, 1.0f, 1.0f, 1.0f);
为了让茶壶更好看,接下来添加灯光,在 OnResetDevice 方法中添加代码:
e.Device.Lights[0].DiffuseColor = new ColorValue( 1.0f, 1.0f, 1.0f, 1.0f);
e.Device.Lights[0].Direction = new Vector3(0,-1,0);
e.Device.Lights[0].Type = LightType.Directional;
e.Device.Lights[0].Enabled = true;
准备工作到这里就结束了。找到 OnFrameRender 方法,在 BeginScene 方法之后添加代码:
e.Device.Transform.View = camera.ViewMatrix;
e.Device.Transform.Projection = camera.ProjectionMatrix;
e.Device.Transform.World = Matrix.RotationX((float)appTime);
e.Device.Material = teapotMaterial;
teapotMesh.DrawSubset(0);
运行程序,可以看到一个渲染的茶壶。茶壶在3D世界里有一段悠久的历史。在那个建模软件还不发达的时代,获得一个真实的3D模型相当困难,而茶壶就是最早供人们免费使用的模型之一。另外,茶壶有许多不错的特性适合于用来做测试:它有圆滑的曲面,可以显示光影渐变的效果,并且容易识别。
需要注意的是,虽然茶壶是这个程序里唯一的模型,但我们并没有使用任何额外资源来省城模型,而是使用了Mesh类的一个静态方法。我们使用了最少的资源得到了一个不错的茶壶。
对于3D版本来说,我们可以从任何角度观察茶壶。但是假设我们需要在2D世界里实现同样的效果呢?我们需要为茶壶旋转的每一个角度保留一张位图(就是360张图),如果还想让茶壶沿任意轴旋转,那么就需要4664600张不同的位图。那么对于虚幻竞技场这样的游戏来说,就算他什么都不干,只绘制2D精灵,也至少需要一张DVD来保存数据,同时还要有一大群艺术家花上几年时间来创建这些东西。
如果你的艺术家可以创建高质量的3D模型,显然,你就可以使用相对很少的资源更加自由的创建场景 。可以用许多不同方法来渲染一个简单的模型,比如不同的灯光、缩放比例、位置、角度等等,而这一切使用2D来实现都是不切实际的。
3D程序的这种自由性需要强大的处理能力来实现,为了满足这种处理能力,甚至形成了一整个产业。nVidia和ATI就是这个产业中的领导者。现代的图形卡有了飞速的创新,甚至在处理能力上超越了CPU。
现在的图形卡(支持DirectX9的图形卡意味着他至少支持shader model2.0)每秒能渲染数百万三角形。不要担心,我们稍候会讨论关于着色器(shader)的内容。如果你想知道为什么需要那么多三角形,那么你需要知道三角形是构成3D模型的基本多边型。以下就是线框模式之下的茶壶模型。
你看,整个茶壶都是由三角形构成。为什么使用三角形呢?因为它是唯一能够保证共面的闭合多边形,这样,在渲染时更容易计算。任何3D图形都可以用三角形模拟出来。
至此,我们的游戏需要是3D的吗?当然不需要,你可以完全使用精灵来编写游戏,但这还有什么乐趣呢?大部分游戏编成的书籍都只涉及2D部分,但3D世界才是真正让游戏精彩的地方。我们即将开始三维之旅。
编写文档
接下来,我们将来到编写游戏最重要的一个环节,编写文档。在一行代码都没有编写的情况下,我实在无法强迫你花一点时间来考虑可能遇到的问题。事实上,我遇到的所有年青游戏开发者在开发第一个游戏时都是直接开始编码。慢慢你才会明白快速的开始编码会带来很多麻烦。对于这个游戏,需要解决什么问题?当然,需要一个游戏引擎作为游戏的大脑。一个玩家对象,一个用于渲染的设备,以及一个维护关卡信息的方法。最常见的现实开发文档就是UML。如下图所示:
如果你熟悉UML,那么各熟悉之间的关系应该是很明显的。如果不熟悉,就需要花点时间来看看这篇文档在说什么。首先,他把问题分成了组成游戏的几个逻辑组件。这里,四个对象分别是游戏引擎、玩家、关卡、方块列表,以及单独的方块。每个对象的属性和方法都列在了对象盒里,这样就可以快速的对每个对象有一个总揽。
把图放到一边,我们到底需要做些什么呢?显然,需要有一个控制中心来控制所有操作。在这里,就是游戏引擎。注意看UML图,游戏引擎包含了用于渲染的device(从InitializeGraphics方法可以看出来)。游戏引擎需要知道以下东西:
l 玩家对象
l 当前关卡
l 游戏是否结束
l 如果是,那么玩家是否赢了当前关卡
l 如果是,那么玩家是由通关了
游戏引擎还需要保存其他信息,比如渲染的设备,以及一些其他对象,但这些都是私有方法,因此,不现示在UML中。接下来的对象是玩家,这是一个相当简单的类,我们只需要知道它的位置,同时,让他可以渲染它自己就可以了。在我们的引擎里,玩家更像一个抽象的概念而不是实际对象。这个对象主要是为了控制任何现实玩家。
游戏引擎所需的其它信息都来自于levels对象。实际上这也是一个很简单的对象,他也只包含了几个其他对象,比较重要的就是blocks集合。每一个方块包含了在不同层次如何控制它自己的信息,包括可能的颜色列表,以及是否需要翻颜色。
好了,有了这最最简单的文档,可以开始编码了。