李华明Himi 原创,转载务必在明显处注明:
转载自【黑米GameDev街区】 原文连接: http://www.himigame.com/iphone-object/406.html
css
为了解决“如何在IPHONE上建立一个游戏”这个大问题,咱们须要首先解决诸如“如何显示图像”与“如何播放声音”等一系列小问题。这些问题关系到建立部分游戏引擎。就像人类的身体同样,游戏引擎的每一个部分虽然不一样,可是却都不可或缺。所以,首先从游戏引擎剖析开始本章。咱们将会讨论一个游戏引擎的全部主要部分,包括应用程序框架、状态机、图像引擎、物理引擎、声音引擎、玩家输入和游戏逻辑。html
写一个好玩的游戏是一项牵扯到不少代码的大任务。很是有必要从一开始就对项目进行良好的,有组织的设计,而不是随着进度的进行而处处杂乱添加代码。就像建造房屋同样,建筑师为整幢房屋勾画蓝图,建筑工人以此来建造。可是,许多对游戏编程不熟悉的编程人员会从根据导读建造出房屋的一部分,并随着学习的进行为其添加房间,这无疑将会致使很差的结果。程序员
图2-1 游戏引擎的功能结构编程
图2-1显示了一个适用于大部分游戏的游戏引擎结构。为了理解一个游戏引擎的全部部分和它们是如何工做在一块儿的,咱们能够先为整个游戏作设计,而后再建立咱们的应用程序。在如下的几个小节中,咱们的讲解内容将会涵盖图2-1的每一个部分。缓存
应用程序框架包含使应用程序工做的必须代码,包括建立一个应用程序实例和初期化其余子系统。当应用程序运行时,会首先建立一个框架类,并接管建立和销毁状态机、图像引擎和声音引擎。若是咱们的游戏足够复杂以致于它须要一个物理引擎,框架也会管理它。安全
框架必须适应于咱们所选择的平台的独特性,包括相应任何的系统事件(如关机与睡眠),以及管理载入与载出资源以使其余的代码只须要集中与游戏。并发
框架会提供主循环,它是一切互动程序后的驱动力量。在循环中的每一次迭代过程当中,程序会检查和处理接受到的事件,运行游戏逻辑中的更新并在必要时将内容描画到屏幕上。(参见图2-2)app
图2-2 主循环序列框架
主循环如何实现依赖于你使用的系统。对于一个基本的控制台程序,它多是一个简单的while循环中调用各个函数:iphone
注意到这里的sleep函数。它使得代码休眠一小段时间不致于占用所有的CPU。
有些系统彻底不想让用户代码那些写,它们使用了回调系统以强制程序员常规的释放CPU。这样,当应用程序执行后,程序员注册一些函数给系统在每次循环中回调:
一旦程序执行后,根据必要状况,那些函数会间隔性的被调用。IPHONE是最接近后面这个例子。你能够在下一章和IPHONE SDK中看到它。
一个好的视频游戏不只有一组动做来维持游戏:它会提供一个主菜单容许玩家来设定选项和开始一个新游戏或者继续上次的游戏;制做群屏将会显示全部辛勤制做这款游戏的人员的名字;并且若是你的游戏没有用户指南,应该一个帮助区域会给用户一些提示告诉他们应该作什么。
以上任何一种场合都是一种游戏状态,而且表明中一段独立的应用程序代码片断。例如,用户在主菜单调用的函数与导航与用户在制做群屏调用的是彻底不一样的,因此程序逻辑也是不一样的。特别的是,在主菜单,你可能会放一张图片和一些菜单,而且等待用户选择哪一个选项,而在制做群屏,你将会把游戏制做人员的名字描绘在屏幕上,而且等待用户输入,将游戏状态从制做群屏改成主菜单。最后,在游戏中状态,将会渲染实际的游戏并等待用户的输入以与游戏逻辑进行交互。
以上的全部游戏状态都负责相应用户输入、将内容渲染到屏幕、并为该游戏状态提供相对应的应用程序逻辑的任务。你可能注意到了这些任务都来自于以前讨论的主循环中,这是由于它们就是一样的任务。可是,每一个状态都会以它们本身的方式来实现这些任务,这也就是为何要保持他们独立。你没必要在主菜单代码中寻找处理游戏中的事件的代码。
状态管理器是一个状态机,这意味着它跟踪着如今的游戏状态。当应用程序执行后,状态机会建立基本的状态信息。它接着建立各类状态须要的信息,并在离开每种状态时销毁暂时存储的信息。
状态机维护着大量不一样对象的状态。一个明显的状态是用户所在屏幕的状态(主菜单、游戏中等)。可是若是你有一个有着人工智能的对象在屏幕上时,状态机也能够用来管理它的“睡眠”、“攻击”、“死亡”状态。
什么是正确的游戏状态管理器结构?让咱们看看一些状态机并决定哪一种最适合咱们。
有许多实现状态机的方式,最基本的是一个简单的switch语句:
改变状态时全部须要作的事情就是改变myState变量的值并返回到循环的开始处。可是,正如你看到的,当咱们加入愈来愈多的状态时,代码块会变得愈来愈大。并且更糟的是,为了使程序按咱们预期的执行,咱们须要在程序进入或离开某个状态时执行整个任务块,初始化该状态特定的变量,载入新的资源(好比图片)和释放前一个状态载入的资源。在这个简单的switch语句中,咱们须要加入更多的程序块并保证不会漏掉任何一个。
以上是一些简单重复的劳动,可是咱们的状态管理器须要更好的解决方案。下面一种更好的实现方式是使用函数指针:
如今,即便咱们处理再多状态,主循环也足够小并且简单。可是,这种解决方案依然不能帮助咱们很好的解决初始化与释放状态。由于每种游戏状态不只包含代码,还有各自的资源,因此更恰当的作法是将游戏状态做为对象的属性来考虑。所以,接下来,咱们将会看看面向对象(OOP)的实现。
咱们首先建立一个表示游戏状态的类:
接着,咱们改变咱们的状态管理器以使用这个类:
最后,咱们建立一个指定具体游戏状态的类:
当游戏状态以类来表示时,每一个游戏状态均可以存储它特有的变量在该类中。该类也能够它的构造函数中载入任何资源并在析构函数中释放这些资源。
并且,这个系统保持着咱们的代码有很好的组织结构,由于咱们须要将游戏状态代码分别放在各个文件中。若是你在查找主菜单代码,你只须要打开State_MainMenu类。并且OOP解决方案使得代码更容易重用。
这个看起来是最适合咱们须要的,因此咱们决定使用它来做为咱们的状态管理器。
图像引擎负责视觉输出,包括用户借以交互的图形用户界面(GUI)对象,2D精灵动画或3D模型动画,并渲染的背景与特效。
虽然渲染2D与3D图片的技术不尽相同,但他们都完成相同的一组图形任务,包括纹理和动画,它们的复杂度是递增的。
对于显示图片,纹理是中心。2D时,一个平面图片是以像素为单位显示在屏幕上,而在3D时,一组三角行(或者被称为网格)在数学魔法做用下产平生面图片并显示在屏幕上。这之后,一切都变得复杂。
当进行屏幕描绘时,基本单位是像素。每一个像素均可以被分解为红、绿、蓝色值和咱们立刻要讨论的Alpha值。
纹理是一组关于渲染一组像素的数据。它包含每一个像素的颜色数据。
图片是一个更高层的概念,它并不是与一组特殊的像素与纹理相关联。当一我的看到一组像素,他的大脑会将它们组合成一幅图片,例如,若是像素以正确的顺序表示,他可能会看到一幅长颈鹿的画像。
保持以上这些概念独立是很是必要的。纹理可能包含构成长颈鹿图片的像素:它可能包含足够的像素来构成一只长颈鹿的多幅图片,或者仅包含构成一幅长颈鹿图片的像素。纹理自己只是一组像素的集合,它并不固有的知道它包含的是一幅图片。
在任一刻,你的游戏会有几个或者多个物体渲染在屏幕上,其中一些会与另一个重叠。问题是,如何知道哪一个物体的哪一个像素应该被渲染出来呢?
若是在最上层的纹理(在其余纹理以后被描画)是彻底不透明的,它的像素将会被显示。可是,对于游戏物体,多是非矩形图形和部分透明物体,结果会致使两种纹理的结合。
2D图片中最经常使用的混合方式是彻底透明。假如咱们想画一幅考拉(图2-3)在爬在桉树顶上(图2-4)的的图片。考拉的图片以矩形纹理的方式存储在内存中,可是咱们不想画出整个矩形,咱们只想画出考拉身体的像素。咱们必须决定纹理中的每一个像素是否应该显示。
图2-3 考拉纹理
图2-4 桉树纹理
有些图片系统经过添加一层遮罩来达到目的。想象咱们在内存中有一幅与考拉纹理大小同样的另一份纹理,它只包含白色和黑色的像素。遮罩中每一个白色的像素表明考拉应该被描画出来的一个像素,遮罩中的黑色像素则表明不该该被描画的像素。若是当咱们将考拉描画到屏幕上时,有这样的一个遮罩层,咱们就能够检查考拉对应的像素并仅将须要描画的像素表示出来。若是每一个像素有容许有一组范围值而不是二进制黑/白值,那么它还能够支持部分透明(参见图2-5)。
图2-5 考拉遮罩纹理
为纹理而准备的存储容量大到足以支持每一个像素都有一个范围值。典型的是,一个Alpha值占一个字节,即容许0-255之间的值。经过合并两个像素能够表现出有趣的视觉效果。这种效果一般用于部分透明化,例如部分或彻底看透物体(图2-6)。
图2-6 部分透明的绿色矩形
咱们能够为每一个像素来设定Alpha以决定它们如何被混合。若是一个像素容许的范围值为0-255,Alpha的范围值也一样应当为0-255。尽管红色值为0表示当描画时不该该使用红色,但Alpha值为0则表示该像素根本不该该被描画。一样,128的红色值表示描画时应该使用最大红色值的一半,128的Alpha值表示当与另一个像素混合时,应该使用该像素的一半颜色值。
当混合物体时,正确的排列物体顺序是很是重要的。由于每一个混合渲染动做都只会渲染源物体与目标物体,首先被描画的物体不会与后描画的物体发生混合。尽管这在2D图片中很容易控制,可是在3D图片中变得很是复杂。
在2D图片中,大部分的纹理都会被直接渲染到目标上而不须要旋转。这是由于标准硬件不具有旋转功能,因此旋转必须在软件中计算完成。这是一个很慢的过程,并且容易产商低质量的图片。
一般,游戏开发人员会经过预先渲染好物体在各个方向的图片,并当物体某个方向上时,为其在屏幕上描画正确的图片来避免以上问题的发生。
在3D图片中,旋转的计算方式与照明相同,是硬件渲染处理过程当中的一部分。
因为某些在后面章节解释的缘由,纹理的另一个重要方面是剪贴。尽管咱们目前的例子都是将源纹理直接描画到目标纹理上,可是常常会出现的状况是,须要将部分源纹理描画到目标纹理的有限的一部分上。
例如,若是你的源纹理是在一个文件中含有多幅图片,裁剪容许你仅将但愿描画的部分渲染出来。
剪贴一样容许你进行描画限制到目标纹理的一小部分上。它能够帮你经过纹理映射以3D方式渲染物体,将纹理铺盖到三角形组成的任意形状的网格上。例如,一个纹理能够表示衣服或动物的毛皮,并且当3D角色穿着它移动的死后可能产生褶皱。这时候的纹理一般被称做皮肤。
经过渲染连续的图片,咱们能够确保玩家看到一个移动的物体,尽管他所作的仅仅是在一样的像素上,但这些像素在快速的改变颜色。这就是动画的基本概念。2D动画很简单,但3D动画一般牵扯到更多的物体与动做,所以更复杂。
除了讨论动画技巧,这一节还会讨论主要的优化类型可使得咱们的图像引擎有效的和可靠的完成复杂的不可能以原始方式来完成的图形任务。一些主要的优化技巧包括淘汰、纹理排序、使用智能纹理文件、资源管理和细节级别渲染。
在2D图像中,若是咱们要渲染马儿奔驰的完整场景,咱们能够先建立出马儿的奔驰各个姿态的图片。这种图片成为一帧。当一帧接一帧的渲染到屏幕上时,马儿动起来了(见图2-7)。这和电影建立动画的方式很是类似,电影也是经过展现连续的帧来达到移动效果。
图2-7 斯坦福德的马的动做
为了将这些帧保存在一块儿,咱们将它们放在同一个纹理中,称为精灵。经过前面章节咱们描述的裁剪方法,将只包含当前帧内容的部分渲染到屏幕上。
你能够将每一帧渲染屡次直到渲染该序列的下一帧。这取决于你但愿你的动画播放的多快,以及提供了多少帧图片。事实上,经过渲染的帧速和顺序,你能够创造出多种特效。
与2D动画中每次重画时都维护一幅用来渲染的图片--精灵不一样,3D动画是经过实际的计算的计算运动的几何效果。正如咱们以前描述的,全部的3D物体都由包含一个或多个三角形构成,被称做网格。有多种可使网格动起来的方法,这些技术与游戏发展与图形硬件有关。这些技术后的基本概念都是:关键帧。
关键帧与咱们以前讨论的2D动画中的帧有些许不一样。2维动画的美术人员画出每一帧并保存在纹理中。可是在3D中,只要咱们保存了最特殊的几帧,咱们就能够经过数学计算获得其余帧。
最开始的使用网格动画的游戏实际上存储了网格的多个拷贝,每个拷贝都是都在不一样的关键帧方向上。例如,若是咱们在3D中渲染马儿,咱们应该为上面精灵的每个关键帧都建立网格。在time(1),第一帧被描画出来,在time(2),第二针被描述出来。
在主要关键帧之间,使用一种叫作“插值”的技术方法。由于咱们知道time(1)的关键帧和time(2)的关键帧有着相同数量的三角形,可是方向稍有区别,咱们能够建立当前时间点的临时的,融合了前面两个网格的网格。因此在时间time(1.5),临时网格看起来正好介于time(1)与time(2)之间,而在time(1.8),看起来更偏向于time(2)。
以上技术效率低下的缘由是很明显的。它仅在只有少许的三角形和少许的关键帧时才是可接受的,可是现代图像要求有高解析度与生动细节的动画。幸运的是,有更好的存储关键帧数据的方法。
这就技术叫作“骨骼动画”(skeletal animation, or bone rigging)。仍是以马儿为例,你可能注意到了大多数的三角形都是成组的移动,好比头部组、尾部组和四肢组。若是你将它们都当作是骨头关联的,那么将这些骨头组合起来就造成了骨骼。
骨骼是由一组能够适用于网格的骨头组成的。当一组骨骼在不一样方向连续的表示出来的时候,就造成了动画。每一帧动画都使用的是相同的网格,可是都会有骨头从前一方位移动到下一个方位的细小的动做变化。
经过仅存储在某一个方位的网格,而后在每一关键帧时都利用它,咱们能够建立一个临时的网格并将其渲染到屏幕上。经过在两个关键帧之间插值,咱们能够以更小的成原本建立相同的动画。
动画控制器对象在抽象低层次的任务很是有用,如选择哪一帧来渲染,渲染多长时间,决定下一帧代替前一帧等。它也起到链接游戏逻辑与图像引擎等动画相关部分的做用。
在顶层,游戏逻辑只关心将设某些东西,如播放跑动的动画,和设定它的速度为可能应该每秒跑动数个单位距离。控制器对象知道哪一个帧序列对应的跑动动画以及这些帧播放的速度,因此,游戏逻辑没必要知道这些。
另一个与动画控制器类似的有用对象是粒子系统管理器。当须要描画高度支离破碎的元素,如火焰、云朵粒子、火苗尾巴等时可使用粒子系统。虽然粒子系统中的每一个对象都有有限的细节与动画,它们组合起来却能造成富有娱乐性的视觉效果。
最好的增长每秒钟描画到屏幕上的次数的方法是在每次迭代中都减小描画在屏幕上的数目的总量。你的场景可能同时拥有成百上千的物体,可是若是你只须要描述其中的一小部分,你仍然能够将屏幕渲染得很快。
淘汰是从描画路径上移除没必要要的物体。你能够在多层次上同时进行淘汰。例如,在一个高层次,一个用户在一间关闭了门的房间里面是看不到隔壁房间的物体的,因此你没必要描画出隔壁其余物体。
在一个低层次,3D图像引擎会常常移除部分你让它们描画的网格。例如,在任意合适的给定时间点,半数的网格几何体在摄影机背面,你从摄像机中看不到这些网格,看到的只是摄影机前方的网格,所以,当网格被渲染时,全部的在摄影机背后的网格都会被忽略。这叫作背面淘汰。
每次当一个物体被渲染到屏幕上时,图形硬件都会将纹理源文件载入到内存中。这是被称做上下文交换(context switching)的一部分。
若是要将三幅图片描画到屏幕上,而其中两幅图片共用同一个纹理资源,有两种办法来处理纹理排序:高效的方法是连续的渲染两幅共享资源的图片,这样只须要以此上下文交换,而低效的方法则须要两次上下文交换。你不该该将第三幅图片放在共享纹理的两幅图片之间描画。
在渲染处理过程当中,经过排列共享纹理的物体能够减小上下文交换的次数,从而提升渲染速度。
在一开始就计划好纹理组织结构能够帮助你以最优化方式排列你的纹理。假设你准备在你的游戏中描画几何体,一个主角和一些生物。
若是前两个关卡是草地,接下来的关卡是沙漠,你能够将全部的树木、草、灌木、岩石以及花儿的图片来放到一块儿来渲染前两关,并将沙子图片放在另一个纹理文件中用来渲染第三关。一样的,你能够将玩家虚拟人偶放到一个纹理中。若是全部的生物在全部关卡中都用到了,最优的方式多是将它们放在一个纹理文件中。可是,若是第一关有吼猴与鼯鼠,而第二关只有森林鼠与苏里南蛤蟆,你能够将第一次前两种动物放在一个纹理中,将后两种放在一个纹理中。
大部分的视频游戏在一个时间点只会渲染它们全部图片内容的一小部分。将全部纹理同时载入内存是很是低效的。
幸运的是,游戏设计一般决定了哪些资源在游戏的各个章节是可见的。经过保留必须的纹理为载入状态并卸载不使用的纹理,能够最有效的利用有限的内存资源。
仍是使用前一节的例子,当游戏引擎载入第一关时,资源管理代码必须确保 吼猴与鼯鼠的纹理被载入到内存中。当程序进行到下一关时,资源管理代码会卸载那些纹理,由于它已经知道它们不会在第二关被使用。
另一个优化技巧,尤为是对3D图像,叫作细节层次。考虑当一个物体远离摄像机时,它看起来很是小,并且大部分细节都丢失了。你能够描画一个一样大小,却仅拥有简单网格的物体,或者甚至一张平面贴图。
经过保留不一样细节层次的物体的副本在内存中,图像引擎能够根据与摄像机的距离决定使用哪一个副本。
物理引擎是游戏引擎中负责距离、移动与其它游戏物理相关的部分。不是全部的游戏引擎都须要物理引擎。可是全部的图形游戏都在某种程度上有物理相关代码。
不相信吗?用“井字游戏”(tic-tac-toe)来举 个例子。确实是一个很是简单的游戏,可是即便这个游戏也有物理部分。当玩家选择一个正方形用来标记的时候,咱们必须检查选择的正方形是否有效。若是是,咱们就将打上标记并判断玩家是否获胜。这就是物理引擎所完成的两项基本任务的例子:侦测与解决。
在你脑海中保持这两方面的独立性很是重要。在游戏代码中,侦测是独立于断定的。不是全部的物体与其它物体会以相同的方式发生碰撞,进而不是被侦测到的全部碰撞都会以相同的方式来解决。
例如,让咱们假想一个游戏:O’Reilly野生冒险。假如玩家的虚拟人偶发如今本身无心间来到了O’Reilly野生保护区,它必须避免奇怪和危险的动物并回到安全的地方。
这个游戏中会发生如下几种物理交互:
1.玩家与地图的碰撞
2.动物与地图的碰撞
3.玩家与动物的碰撞
4.玩家与目的地的碰撞
第一种,玩家与地图的碰撞,很是简单。咱们检测玩家的身体区域边界与关卡中的墙。若是玩家将与墙发生碰撞,咱们稍微位移一下玩家,以使其不会与墙发生碰撞。
第二种交互稍微复杂一点。咱们使用一样的侦测方法:检测动物的身体区域与关卡中的墙。可是咱们的解决方法有一些不一样,由于动物不是玩家控制,而是由电脑控制。咱们解决状况1时,咱们稍微位移一下玩家,以使其不会进入到墙里面。这是用来提醒玩家他正在撞向墙并且必须改变方向。
若是咱们对状况2作一样的解决,AI不会认识到它正撞向墙,并且会继续走向墙里面。所以,咱们分两步解决这种状况,首先稍微位移一下动物以使其不会与墙发生碰撞,而后通知AI动物撞上了一面墙。这样,游戏逻辑会控制动物改变移动方向。
第三种状况,咱们首先检查玩家身体区域边界与动物身体区域。一旦咱们侦测到了他们将发生碰撞,可能会发生不一样的结果。若是这个动物是眼镜猴,他可能会逃跑;若是这个动物是毒蛇或者狮子,它可能会攻击玩家;骆驼可能会忽略玩家;蝗虫可能会被踩扁。
最后,第四种状况是一种新的状况。目的地与地图、玩家与动物是不一样的,由于它没有图形表示。它是一个隐含的触发区域,当玩家踏入它
时,它会发出一个事件。幸运是,尽管它没有图形表示,它仍然具备物理表示。因此,咱们依然能够侦测玩家的身体区域边界与目的地的区域边界。若是咱们发现玩家到达了目标,就通知游戏逻辑,使其进入“玩家胜利”的游戏状态。
二维碰撞侦测是一个相对简单的处理过程。大部分均可以总结为以下的一段侦测常规:矩形对矩形、矩形包含点、圆对矩形、圆包含点与圆对圆(有可能也须要检查线段,不过那一般能够被避免)。
因为这些常规可能被每秒钟使用屡次,所以确保它尽量高效是很是重要的。为了达到这个目的,咱们将会进行一系列低成本的测试来证实两个物体碰撞以前是没有碰撞在一块儿的:
尽管这样,当物体碰撞时,仍是会有更多的代码被执行。大部分时候,物体都不会相互碰撞,咱们针对于此进行了优化,由于它更有效率。
在咱们继续以前,让咱们看一个很是重要的概念:游戏对象的图形表示独立于它的物理表示。计算机每秒钟只能提供有限的计算总量,进行实时地物理模拟很是困难。正由于如此,游戏物理代码有一个悠长而骄傲的传统,那就是只要精确到让玩家以为游戏正确便可。
好比,在咱们上面提到的游戏中,咱们玩家的虚拟人偶即将撞上一头犀牛(很明显,要么咱们的玩家不太专心,要们就是被一只生气的母老虎追赶)。咱们的玩家在跑动过程当中,会有不少四肢动做,犀牛也是同样,它伸出它头上的牛角。
尽管玩家在跑动过程当中,图形表示须要出玩家的手和脚,可是在物理表示中,咱们真的须要知道四肢的具体坐标吗?咱们真的须要去检查玩家的双手、双脚以及头是否与犀牛的头、脚、及牛角发生了碰撞(还有别忘记了尾巴!)?固然不,由于咱们的游戏是按照咱们的须要简单的用矩形表示玩家和用矩形表示犀牛。
三维碰撞侦测要比二维困难不少。你必须很熟悉三维数学计算,好比线形代数。并且,即便数学要求不高,3D游戏也拥有更复杂的游戏场景。幸运的是,你能够依赖合适的技术来帮助减小计算次数。固然,仍是要精确到让玩家以为游戏正确便可。
就像咱们以前讨论的,一个物体的图像表示不一样于它的物理表示。可是,有时咱们须要确保他们之间的差异越小越好。想象一下第一人称射击游戏,咱们不只要知道一个玩家知否射中了另一个玩家,还要知道他是否取得了一个爆头。很明显,一个简单的盒子边界不能知足需求,不过咱们也没法提供对每一颗子弹都检查路径,判断其穿过的每个虚拟人偶的每一个三角形的检查。
咱们使用一样的3D侦测,就像咱们在2D中使用的侦测同样:在执行精确的与昂贵的测试以前,先执行一系列低成本的否认测试。以子弹为例,咱们首先测试子弹有没有划过哪一个玩家的边界框。若是没有击中他们,咱们退出碰撞检查。对于击中了的边界框,找到起始击中点,并对更个中的全部三角形都作更多详细的测试。这是图形优化中的细节层次的物理版本。若是在最近的玩家身上的检查失败了,咱们将会在下个玩家身上作详细检查。用这种方式,咱们能够高效并准确的更复杂的场景,好比一颗子弹从一个玩家的双腿间飞过,击中了他身后的另一位玩家。
当碰撞被侦测到时,就要解决它。首先要考虑的是,什么样的低层次的或高层次的动做必须发生。低层次动做是指物理代码能够解决的,如调整玩家的位置以使其在地面上而不会摔到地面下。高层次的动做是指发送到游戏引擎的游戏逻辑部分的信号。这些信号可让游戏逻辑知道一个动物什么时候否跑进了一堵墙里或者一个玩家是否到达了目的地。
有些碰撞响应须要同时多重高层次的和低层次的响应。例如,在弹钢珠游戏中,一个钢珠打到了防撞杆上,它应该正确的弹离防撞杆(低层次),也会同时向游戏逻辑发送一个信号,使得防撞杆产生动画效果、声音效果以及增长玩家的得分(高层次)。
当测试低层次的碰撞解决的代码时,要特别注意细节。最影响游戏感受的重要元素之一就是物理代码。虚拟人偶能够快速的响应玩家的输入吗?赛车游戏能真实地模拟出悬挂系统与变速系统吗?当玩家发生大炮的时候,屏幕会抖动吗?
根本上,这是由设计人员的工做来让游戏感受很好,可是这须要程序员的工做来实现它。不要惧怕写一些特殊的测试用例以获得正确的结果。
例如,测试时经常使用的一项技术是为对象设置“flag”。当一个对象与另一个对象“ground”接触时,你能够为这个对象设置一个“grounded” flag为true。当grounded flag为true时,能够认为这个物体在休息,不须要对其施加剧力影响,或侦测它与地图的碰撞。这不但能够帮助你防止在一大堆静止物体上运行碰撞侦测,也能够避免在某些物理模拟钟,物体在地面附近产生抖动现象。
声音是游戏开发中常常忽略的一的环节,可是当你知道声音构成了人类玩视频游戏的1/3的感受时,你确定会很困窘。在合适的时机播放正确的声音使得程序员仅做很小的工做就能够为游戏增长分数。
基本的音效特征包括载入和卸载声音样本、播放声音、中止播放、暂停播放以及循环播放。典型的,全部的声音均可以以一样的音量播放,可是你可能但愿玩家调整以使全部的声音都符合他们的喜爱。
在播放音效以前,你须要从文件或者缓存中载入声音样本。IPHONE API支持AAC与MP3格式的高品质的声音,也支持PCM和IMA4的小样品,以及其余一些格式。
一旦载入了声音样本,API提供了函数以开始和中止播放样本。大多数API还提供了暂停和继续功能,还有一些容许你从特定点开始播放。虽然还有更多高级特性,可是基本上以上就是程序员所有所须要的了。
由于声音播放依赖于硬件,你在同一时间能够听到的声音是有限的。每种声音播放时都使用一个声道。在IPHONE中,全部的MP3和AAC样本共用相同的硬件声道,并且只有一个是可用的,多声道支持PCM和IMA4。
这意味着同一时间,只有一个MP3/AAC格式的样本能够被播放。典型的,但多个PCM/IMA4样本能够同时播放多个样本(同时与MP3/AAC播放)。
游戏中的大部分声音均可以分为两大类:环境音(典型的,背景音乐)和音效(SFX)。有时,用环境音代替音乐,但他们都共享一样的特色。
音乐通常是重复播放一个相对长的样本或者引出下一段音乐样本。一般状况下,只有一首音乐在同一时间播放,使得MP3/AAC格式限制变成了一个不成问题的问题。
声效要短的多,并且须要许多不一样的样本,在同一时间重叠播放。PCM/IMA4格式很好的知足了这个需求。
因为PCM/IMA4也只有有限的声道数,所以若是你打算同时播放不少的SFX样本,其中一些可能不能被播放。因此,为SFX音效设定优先级以确保某些音效必定能播放就显得很重要了。
例如,让咱们讨论以前提到的咱们的玩家虚拟人偶走进了一个尽是愤怒的犀牛的房间。每头犀牛都会播放愤怒的鼻息声,可能还会接着播放撞人的声音,而玩家虚拟人偶则会发出惧怕的哭叫声。咱们会给与玩家虚拟人偶声效更高的优先级,以使其不会被犀牛的撞人声效所淹没。
幸运的是,目前的IPHONE支持至少32声道,因此通常不太可能须要在这个平台上去刻意安排优先级。
IPHONE支持内置的对讲机与听筒做为输出设备。建议不要同时使用它们。当玩家将听筒插入到IPHONE中时,音频会自动改成从听筒播放。
因为IPHONE是移动设备,有可能玩家在路上在的时候,听筒会掉落。一个好的设计选择是当听筒被移除的时候暂停游戏以给玩家足够的时间让他从新接入耳机。此时,你也能够选择同时提供中止播放声音。
最重要的是,应该从玩家的用户角度来考虑声效。玩你的游戏并非用户使用IPHONE的惟一标准,因此不要让你的声效的优先级打扰到用户,不然玩家会将其关掉。
游戏引擎的玩家输入部分集中于来接收自于操做系统的低层次的事件,而后将其转化为高层次的事件,这样,游戏逻辑代码能够在PC游戏中使用它。低层次的事件能够是鼠标和键盘事件。对于控制台游戏,他们可能产生于控制器的手、触发器与按钮。在咱们的例子里面,IPHONE会处理触摸与旋转事件。
触摸屏接口的设计方式与其余大多数移动手机、控制台与PC平台的接口设计方式有着根本的区别。在用户触摸屏幕与应用程序接收到该事件之间有延迟(尽管IPHONE已经在将缩短延迟方面作的足够好了),可是真正的问题是,无论什么时候用户作出触摸动做,他的手指都会遮盖住部分屏幕,大大下降了游戏画面的可见性。
你能够经过提供一个图形按钮给用户来点击(回到了按钮点击系统,代价是屏幕空间),或者提供一个聪明的隐喻来解决这个问题。好比,若是玩家点击了屏幕上的一块空间,你可让虚拟人偶朝那个方向走。这样能够省去用户的连续点击输入。
尽管用户接口设计是游戏设计人员的责任,可是编程人员须要告诉设计人员这个平台能够作什么和不能作什么。IPHONE支持如下触摸事件:
你可能会问什么状况下会触发“触摸取消”事件。当某个事件将你的应用程序挂起在“触摸开始”事件与“触摸结束”事件之间时,程序会收到“触摸取消”事件通知你不会收到其余的事件(如触摸结束)。
为了处理多点触摸,包含一个UITouch对象list的UIEvent对象被发送到你的应用程序中。若是只有一个指头触摸到屏幕,你只会接收到一个UITouch对象;若是两个指头触摸到屏幕,你会接收到两个UITouch对象等等。并且IPHONE能够追踪正在发生或最近发生的连续的5次轻击事件(一个触摸开始事件后紧跟一个触摸结束事件)。
不幸的是,经过以上事件来判断用户是单击、双击、扫过或者压缩动做会可能变得比较麻烦。虽然不是很困难,可是在一开始并非很容易正确处理。看如下例子:
到目前为止,用户触摸了屏幕一次,你的代码能够执行相应的单击处理逻辑。可是稍等!
用户第二次轻击了屏幕。若是你已经在收到触摸结束的事件时进行了处理,可能错误的处理了用户实际上的双击处理。
咱们应该如何正确处理这种状况呢?解决方法是将第一次触摸结束事件推迟为定时回调。当第一次接收到触摸结束事件时,咱们设置一个回调。若是咱们在回调以前接收到了第二次触摸结束事件,咱们能够断定用户进行了双击,并取消回调,执行双击处理。咱们接收到了回调,咱们认为用户没有进行双击并应该进行单击处理。
这里有表达两种状况的例子:
此次,玩家进行了双击并且代码进行了正确的处理。
如今,玩家进行了单击并且代码也进行了正确的处理。
注意,你没必要为那些仅期待单击事件的接口加上这些处理。
侦测诸如扫过的动做会更麻烦一点,但也更容易正确处理。代码中必须为每次触摸保存起始点与终点,并算出用户划的线的方向是向上、向下、向左、仍是向右。还要判断他的手划过的是否足够快。
一旦断定了用户执行的物理动做,你的代码必须能将它们转换为游戏逻辑组件可使用的形式。具体怎么作须要依赖于你的游戏的上下文,可是这里有几种典型的形式:
游戏逻辑是游戏引擎中是你的游戏独一无二的部分。游戏逻辑记录着玩家状态、AI状态、断定何时达到目的地、并生成全部的游戏规则。给出两个类似的游戏,他们的图像引擎与物理引擎可能只有细微差异,可是它们的游戏逻辑可能会有很大差别。
游戏逻辑与物理引擎紧密配合,在一些没有物理引擎的小游戏中,游戏逻辑负责处理全部物理相关内容。可是,当游戏引擎中有游戏引擎的时候,须要确保二者的独立。达到此目的的最好方式就是经过物理引擎向游戏逻辑发送高层次的游戏事件。
游戏逻辑代码应该尽量仅处理高层次问题。它不该该处理当用户触摸屏幕时须要以什么顺序将什么描画到屏幕上,或者两个矩形是否相交等问题。它应该处理玩家但愿向前移动,何时一个新的游戏物体应当被建立/移除以及当两个物体相互碰撞后应该作什么。
为了维持概念上的距离,处理低层次概念(诸如用户输入与物理引擎等)的代码应当建立高层次的消息并发送给游戏逻辑代码去处理。这不只能保持代码的独立性与模块化,还会对调试有所帮助。经过查看高层次消息传递的日志,你能够断定是没有正确处理消息(游戏逻辑代码的问题),仍是没有在正确的时机传送消息(低层次代码问题)。
一个很是基本的传递高层次消息的技术是写一个String并传递它。假如玩家按下了上箭头键,它的虚拟人偶必须向上移动。
虽然上面的代码对程序员来讲通俗易懂,但对于电脑来讲却并不高效。它须要更多的内存与处理,远比实际须要的多。咱们应该用提示来替代用户输入方法。比起一个字符串,它使用一个"type"和"value"。因为可能的事件都是结构化的和有限的,所以咱们可使用整数和枚举类型来咱们消息中的事件信息。
首先,咱们定义一个枚举类型来标识事件类型:
接着咱们再建立一个枚举类型来标识事件的值:
如今咱们定义一个结构体来存储咱们的消息数据:
如今,咱们就能够像上一个例子代码同样,用一个对象来传递咱们的消息:
这看起来做了更多的工做,但它运行起来会更有效率。前一个(坏的)例子用了20个字节来传递消息(20个字符各占一个字节,别忘了终止符)。第二个例子只用了4个字节来传递一样的消息。可是更要的是,当sendGameLogicMessage()处理方法的时候,它只须要分析两个switch语句就能够找到正确的响应,而前一个例子则组要从字符串进行解析,速度很慢。
游戏逻辑的另一个职责就是管理AI代理。两类典型的游戏须要用到AI系统:一种是玩家与电脑竞赛;另一种是在游戏世界中有半自主系统的敌人。在这两种状况下,AI代理为游戏世界中的物体的动做接受输入并提供输出。
在第一种类型游戏里,AI被称做专家系统。它被期待用来模拟理解游戏规则的人的行为动做,并能够采起具备不一样难度的策略来挑战玩家。AI具备与玩家相似的输入与输出,能够近似的模拟玩家的行为。因为人类比如今的AI代理更擅长处理复杂信息,有时为专家系统提供的输入信息要多于给玩家的,以使AI系统看起来更智能。
例如,在即时战略游戏(RTS)中,战争迷雾用来限制玩家的视野,但AI敌人能够看见地图上全部的单位。尽管这样提升AI对抗更高智慧玩家的能力,可是若是优点变的太大,会让人以为AI在做弊。记住,游戏的重要点是让玩家得到乐趣,而不是让AI击败他们。
在第二种类型的游戏中,可能有许多AI代理。每个都独立,其不是很是智能。在某些状况下,AI代理会直接面对玩家,而有些多是中立状态,甚至还有一些是前面两种状态的结合。
有些代理多是彻底愚笨的,提供特定的、有限的行为并且并不关心游戏世界中发生的事情。在走廊里面来来回回走动的敌人就是一个例子。有些多是稍微有些愚笨,只有一个输入和一个输出,好比玩家能够打开和关闭的门。还有一些可能很是复杂,甚至懂得将它们的行为组合在一块儿。为AI代理选择恰当的输入容许你模仿“意识”和增长现实性。
不论AI代理有多么简单,通常都会它们使用状态机。例如,第一个例子中的彻底愚笨的物体必须记录它在朝哪一个方向走动;稍微愚笨的物体须要记录它是开的状态仍是关的状态。更复杂的物体须要记录“中立”与“进攻性之间的”动做状态,如巡逻、对抗与攻击。
将游戏视做具备主要游戏状态的模拟是很是重要的。不要将现实世界时间与游戏时间混淆。若是玩家决定休息会儿,游戏必须能够暂停。以后,游戏必须能够平滑的继续,就像任何事情都没有发生同样。因为IPHONE是移动设备,保存与继续游戏状态变得尤为重要。
IPHONE上,在一个时间点只容许一个应用程序运行,用户也但愿这些应用程序可以很快载入。同时,他们但愿可以继续他们在切换应用程序以前所作的事情。这意味着咱们须要具备在设备上保存游戏状态,并尽量快的继续游戏状态的能力。对于开发游戏,一项任务是要求保持如今的关卡并能够从新载入它使玩家即便在从新启动应用程序后也能够继续游戏。你须要选择保存哪些数据,并以一种小巧的、稳定的格式将其写到磁盘上。这种结构化的数据存储被称为序列化。
根据游戏类型的不一样,这可能比听起来要困难的多。对于一个解谜游戏,你将仅须要记录玩家在哪一个关卡、以及如今记分板看起来是什么样的。可是在动做类游戏中,除了记录玩家虚拟人偶以外,你可能还须要记录关卡中的每一个物体的位置。在一个特定时间点,这可能变得难以管理,特别是当但愿它可以很快完成。对于这种状况,你能够在游戏设计阶段采起一些措施以确保成功。
首先,你必须决定什么东西是在保存游戏状态时必须保存的。火焰粒子系统中的每根小火苗的位置并不重要,可是在粒子系统的位置在大型游戏中可能很重要。若是它们能从关卡数据中得到,那么游戏中每一个敌人的状态可能并不重要。用这种方式进一步考虑,若是你能够简单的让玩家的虚拟人偶从check point开始的话,那玩家虚拟人偶的确切状态与位置也可能不须要保存。
基于帧的逻辑是指基于单独的帧的改变来更新游戏物体。基于时间的逻辑虽然更复杂但却与实际游戏状态更紧密,是随着时间的流逝而更新游戏物体。
不熟悉游戏开发的程序员老是犯了将基于帧的逻辑与基于时间的逻辑混合的错误。 它们在定义上的区别是微妙的,不过若是处理不得当,会形成很是明显的BUG。
好比,让咱们以玩家移动为例。新手程序员可能写出这样的代码:
每当玩家按下按键,虚拟人偶像前移动一点。这是基于帧的逻辑,由于每次移动的变化都会潜在的伴随着新的帧。事实上,在这个的例子中,每次玩家输入事件都会发生移动。这或多或少有点像主循环的迭代。移动的可视化影响只有在主循环的下次迭代中才会反映,因此任何迭代中间的虚拟人偶移动都会浪费计算。让咱们作一下改进:
如今咱们知道,在键被按下的过程当中,每次游戏循环中都只会被赋予一次速度。可是,这仍然是基于帧的逻辑。
基于帧的逻辑的问题是,帧变化不会老是以相同的时间间隔发生。若是在游戏循环中,渲染或者游戏逻辑会比一般耗费更多的时间,它可能会被推迟到下一次循环中。因此,有时你须要有60帧每秒(fps),有时,你只须要30fps。因为移动是适用于帧的,有时你只会以一般的一半速度来移动。
你能够用基于时间的逻辑来准确的表达移动。经过记录自从上次帧更新的时间,你能够适用部分移动速度。用这种方式,你能够以每秒为单位来标识移动速度,而没必要关心当前帧速率是多少,玩家虚拟人偶的速度是一致的:
在这个例子中,移动速度的总量将会是相同的,无论是2fps仍是60fps。基于时间的逻辑须要一点额外的代码,可是它可使程序更精确而没必要在意暂时的延迟。
固然能够用基于帧的逻辑来开发游戏。重要的是,不要混合它们。好比,若是你的图形代码使用基于时间的逻辑来渲染玩家虚拟人偶的移动动画,可是游戏逻辑代码却使用基于帧的逻辑在游戏世界中来移动它,这样移动的动画将不能玩玩家移动的距离彻底同步。
若是可能的话,请尽可能移除基于帧的逻辑。基于时间的逻辑将会对你有更大的帮助。
游戏逻辑代码的核心功能就是管理游戏状态的规则与进度。根据你的游戏设计,这可能意味着任何事情。可是,仍是有一些基本模式基于制做的游戏的类型。
游戏逻辑不与任何一个特定的类相关联,它游戏状态对象中表现出来。当主游戏状态被初始化后,它将会为关卡载入与初始化必要的资源。例如猜谜游戏中的一组提示与单词、玩家虚拟人偶的图片数据以及玩家当前所在区域的图片数据。在游戏循环中,游戏逻辑将会接受用户输入,运行物理模拟,并负责处理全部的碰撞结局消息,模拟AI动做,执行游戏规则。最后,当应用程序须要终止主游戏状态,它会释放释放全部的游戏资源,并可能将游戏状态保存到硬盘驱动器上。
根据游戏的复杂度,你可能会发现很方便进一步分解游戏逻辑。好比,若是你在开发一款冒险游戏,你可能有一个充满环境数据(地面、建筑、河流、树等)、能够移动、与玩家交互的实体(玩家虚拟人偶、敌人、非玩家角色、开关、障碍物等),各类GUI使玩家做出特殊动做和显示重要信息的游戏世界。每种游戏特征都必须有大量的代码。虽然它们合在一块儿才能组成完整的游戏,可是你仍是能够保持它们的工做模块化。
你能够建立一个Level Manager类来处理游戏关键,包括载入和卸载显示在游戏世界中的物理与图像数据与调用游戏引擎来侦测实体与游戏世界的碰撞。你还能够建立另一个类或者一些类来处理游戏世界中存在的实体。每一个类都载入和卸载渲染那些物体的必要的物理和图片数据,以及包括控制它们的AI。
最后,你可能建立另一个单独的类来处理游戏中用户交互,以保持代码与三大概念独立。
这个体系结构适用于任何类型的游戏。首先评估游戏设计的主要特性,接着以某种方式组合,将相近的功能与数据组合在一块儿。
你应该对创造一个游戏引擎时必须完成的任务有了一个基本的理解。这将会帮助咱们在下一节建立这些元素,为咱们的游戏作准备。