29
2012
10

解析手机游戏《Tiny Wings》开发步骤

 非本人原创,难得的技术好文,推荐大家看看,对于制作许多类似的2D游戏起到抛砖引玉的作用。

文章转自游戏邦,版权归其所有(http://gamerboom.com/archives/30969)。

---------------------------------------------------------------------------------------------------------

相信很多人都不会对《Tiny Wings》感到陌生,都知道它是一款由Andreas Illiger开发的热门手机游戏,其玩法就是控制一只小鸟,让它借助坡度在日落前飞向终点。

乍一看,这游戏的设置非常简单,但这只是一种表象,该游戏的实际制作过程可有不少讲究。游戏中的小山丘及其纹理会动态变化,它使用的是Box2D物理引擎来模拟小鸟的动作。

tiny-wings(from digitaltrends.com)

tiny-wings(from digitaltrends.com)

许多开发者都对这款人气游戏及其开发技巧抱有极大兴趣,本文是根据由Sergey Tikhonov编写的样本对象而撰写的开发教程,主要包括三个环节:

1.必备条件:首先要查看《How To Creat Dynamic Textures with CCRenderTexture》这篇教程,掌握创建山丘及背景纹理的方法。

2.第一部分:本篇将教您创建《Tiny Wings》运行过程中的动态小山丘。

3.第二部分:本篇将教您添加Box2D游戏设置的方法。

当然,领会本教程的前提条件是您已经精通Cocos2D开发技术,假如您还是新手,可能得首先熟悉Cocos2D新手教程的有关内容才行。

开始动工

请先下载Sergey Tikhonov创建的样本对象有关资料。

然后根据File\New\New File的路径创建一个山丘分类,选择iOS\Cocoa Touch\Objective-C class路径,并点击“Next”,创建一个CCNode的子类,点击“Next”,将其命名为Terrain.m,

然后再点击“保存”。

随后打开Terrain.h,将其用以下内容来代替:

代码1

代码1

这里要说明一下,有个名为 _hillKeyPoints 的阵列代表我们过后将保存的所有小丘顶峰,以及一个用于控制山丘滚动距离的偏移。

下一步就是开始执行Terrain.m,我们将逐一讲述其中步骤。首先把Terrain.m所有现存内容删除,并逐段添加以下代码:

代码2

代码2

这是让一些随机性小山丘生成关键点的方法,也是一个极为简单的操作过程,由此我们可以得到一个起点。

第一个点位于屏幕左侧,位于Y轴的中间。随后的每一点都会沿着X轴以屏幕一半宽度的距离移动,并且沿着Y轴设置从0至屏幕顶端的数值:

代码3

代码3

用初始化方法调用generateHills创建山丘,以绘图方式简单画出需要调试的每一个点之间的线段,以便我们从屏幕上看到效果:

代码4

代码4

这时候就该考虑山丘的移动方式了——随着小鸟沿着X轴的坡度前行,这些坡度会向左滑移。所以我们就得以-1来增加偏移,切记控制好比例。

这时候基本上可以开始测试效果了。切换到HelloWorldLayer.h以做如下改变:

代码5

代码5

然后切换到HelloWorldLayer.m,再做如下改变:

代码6

代码6

注意你每次敲进代码时,都会将Terrain原来的条纹转变成新的随机性条纹,这可以让测试更加顺手。

另外,当你在更新时调用setTextureRect on _background时,就意味着你想以0.7的数值增加偏移,以便让背景滚动速度比山丘更慢。

这就对了,现在可以编译并运行代码,观察屏幕中线段的走势,它代表山丘顶峰移动的位置:

山丘顶点线段

山丘顶点线段

看到山丘滚动方式,你可能就会发现这并不是理想的《Ting Wings》游戏运行效果。由于我们是随机性选择Y轴,所以这些山丘有时不是太大就是太小。而且X轴也缺乏足够的变化。

但现在你只需通过采用更好的算法,就可以让测试码顺利运行,提高山丘视觉效果,并有效对其进行调试。

你可以稍费点功夫用自己的山丘运行算法,替换generateHills中的代码,也可以选择使用Sergey的操作方式,也就是下文要体现的内容。

更优化的算法

如果你选择Sergey的执行方式,那就请按以下内容替换Terrain.m中的generateHills:

代码7

代码7

具体操作方式如下:

·X轴的增量范围是160+0至40间的随机性数字

·Y轴的增量范围是60+0至40间的随机性数字

·不可时常反相Y轴的偏移

·勿让Y轴数值过于接近顶点或底端(paddingTop,paddingBottom)

·从屏幕最左侧开始,将第二个点硬编至(0, winSize.height/2),这样山丘就会从屏幕左侧开始呈现

现在编译并运行代码,然后就可以看到一个更优化的山丘演示效果,如下图:

改良后的山顶曲线

改良后的山顶曲线

绘制线段

在执行更多操作之前,我们得先做一个主要的运行性能优化。现在我们要画出所有山丘的1000个顶点,虽然我们在屏幕上一次只能看到其中一小部分。

我们可以根据屏幕显示面积简单计划每次需展示多少个顶点,这样有助于节省大量时间,如下图所示:

计算屏幕显示的关键点

计算屏幕显示的关键点

先试试在Terrain.h中添加两个实体变量:

代码8

代码8

然后在Terrain.h的初始化方法之上增加一个新函数resetHillVertices,如下图所示:

代码9

代码9

现在我们可以依次通过每个关键点(从0开始)查看它们的X轴。

当前的偏移会将视图指向屏幕左侧边缘,这样我们就可以减去winSize.width/8。如果所得数值更小,我们就得继续执行操作直到找到更大一点的数值为止。从keypoint开始至toKeypoint都要以此操作。

现在让我们看看实际成效,请按以下操作修改绘图函数:

代码10

代码10

我们不需要画出所有的点,只需使用之前计算的目录画出可视的关键点即可。我们还将这些线条改成红线以使其更加清晰可辨。

下一步就是针对Terrain.m做出更多修改以调用resetHillVertices:

代码11

代码11

为了让这些改变更加明显,现在得到HelloWorldLayer初始化方法的底端添加如下内容:

代码12

代码12

编译并运行代码,你将看到这些线段已经各就其位:

红色线段

红色线段

绘制平滑的斜坡

目前一切进展顺利,但还有一个大问题——这些线段看起来根本不像山丘。现实生活中的山坡绝不会这样棱角分明,它们毕竟存在一定坡度。

如何画出山丘曲线呢?我们在高中所学的余弦在这个时候就可以派上大用场了!

这里需要先快速回顾一下余弦曲线的知识,如下图:

余弦曲线

余弦曲线

曲线的起点是1,曲线下落的极点就是-1。

怎样才能将这一原理运行用创建一个将关键点相连接的平滑曲线呢?在此我们仅考虑其中两个顶点的情况,见以下图表所示:

P0至P1曲线

P0至P1曲线

首先,我们需要画一条分段的线,每10点分为1段。同样地,我们还需要一条完整的余弦曲线(绿线),这样我们就根据段数把PI等分,从而在每个点上取得直角三角。

然后,Y坐标上的P0映射为COS(0),P1映射为COS(P1)。每个映射称为COS(角)。在P0和P1之间取中点,得到结果(在图中标为ampl)。

因为COS(0)=1,COS(P1)=-1,据此我们可以得到ampl在p0时和在p1时的值。所以ampl在P0与P1之间取中点时所对应的Y坐标点就是我们需要的。

让我们再看看这些代码的情况,先在Terrain.h添加一个线段长度定义:

代码13

代码13

然后将以下数值添加到绘图函数中,也就是在调用ccDrawLine之后:

代码14

代码14

花点时间确认你了解这些代码的运行原理,因为我们过后还要在这个基础上创建内容。

最后需要注意的是,我们不需要再放大比例,所以这里要返回HelloWorldLayer.mm,将初始化比例调整回原来的1.0:

代码15

代码15

编译并运行代码,你会看到如下图连绵起伏的曲线:

平滑的山丘曲线

平滑的山丘曲线

绘制山丘

既然我们已经明白如何显示出山顶曲线,接下来就很容易使用之前生成的纹理编写画出山丘的代码。

每一段山丘都可以使用这种方法,我们将计算渲染山丘所需的两个三角形数值,如下图所示:

计算纹理三角数值

计算纹理三角数值

我们还在每一点设置了纹理坐标轴。在X轴上,我们将根据纹理宽度(游戏邦注:这些纹理是重复的)进行划分;在Y轴我们则将山丘最低点映射为0,最高点为1,根据条状分配纹理高度。

为执行这一命令,首先得对Terrain.h做出一些修改:

代码16

代码16

然后在Terrain.m中的resetHillVertices添加以下代码:

代码17

代码17

多数操作看起来都很眼熟,因为我们之前用余弦定理画山丘曲线时已经包含这些内容。

这里的不同之处在于我们要为各段山丘用顶点添加一个阵列,每一个条状都需要四个顶点和四个同等的纹理。

执行完这些操作后,现在可以在绘图函数中添加如下代码:

代码18

代码18

将条状纹理绑定在一起使用,进入由顶点组成的阵列和之前制作的纹理坐标轴,画出一个三角形条状的阵列。

除此之外,如果你想看到真正出色的效果,不想因调试绘图而搞砸一切,那就应该为画出山丘线段和曲线的代码添加注释。

编译和运行代码,你就可以看到如下图所示的理想效果:

理想的山丘效果图

理想的山丘效果图

进一步完善

如果细看这张图,就会发现它仍然有点小瑕疵,那就是纹理线条有点粗糙:

纹理瑕疵

纹理瑕疵

Cocos2D论坛上有些人认为只要添加一些垂直片段就可以解决这个问题,但我自己发现这处操作其实无助于提高图像质量,但增加水平片段却可以达到效果。打开Terrain.h,根据如下内容修改kHillSegmentWidth:

更为优化的山丘效果图

更为优化的山丘效果图

再次运行代码,你会发现这些山丘看起来更加完美了!

 

第一部分内容里,我们阐述了如何制作游戏的动态山体和背景材质。

在此系列教程的第二部分,我们将阐述某些有趣的东西,如何为游戏添加主角并使用Box2D来模拟其移动!

以下教程的背景是,你对Cocos2D和Box2D很熟悉。如果你还是个Cocos2D或Box2D新手,需要先去看看其他的Cocos2D和Box2D教程。

tiny-wings(from mytechknowledge.com)

tiny-wings(from mytechknowledge.com)

开始动工

先下载我们在上文中发布的项目样例。

接下来,我们要添加“Hello, Box2D!”代码。我们将创造Box2D世界,添加某些代码设立调试绘图并增加某些测试形状,确保我们目前为止做出来的所有东西都能生效。

首先,对HelloWorldLayer.h做如下修改:

代码1

代码1

这包括Box2D标题文件和调试绘图文件,预设定变量跟随Box2D世界和调试绘图类改变。

它还将点每米比例(游戏邦注:即代码中的“PTM_RATIO”)设定为32.0。作为老手,我们需要用此在Box2D单元(米)和Cocos2D单元(点)之间转化。

然后在HelloWorldLayer.mm中在初始方法之上增加下列新方法:

代码2

代码2

如果你对Box2D很熟悉,应该检查这些方法。

setupWorld方法创造出带有引力的Box2D世界,重力加速度略小于地球标准9.8m/s2。

createTestBodyAtPosition方法的作用是构造一个测试对象-一个半径为25点的圆,在你做触摸测试的时候,这个方法将产生一个测试对象,并且测试完成后销毁该对象

HelloWorldLayer这个文件的代码还不完整,我们还需要做一些修改:

代码3

代码3

你向源代码添加的这段代码只是调用你先前编写的新方法来设立Box2D世界。采用这种方法,最终它可以利用该世界来为山体创造Box2D物体。接下来,我们将为此编写某些占位符代码。

你添加至更新的代码让Box2D有时间通过调用_world->Step在每次更新中运行物理模拟。应该注意的是,这执行的是固定的时间步,在运行物理模拟时比变动时间步要好。要获悉更多这种做法如何发挥作用的信息,查看Cocos2D书籍中的Box2D章节。

你添加至ccTouchesBegan中的代码通过运行助手方法来在任何你点击的地方创造Box2D物体。再次声明,这只是用来调试并证实到目前为止Box2D运转良好。

应该注意的是,触屏协调置于地形协调中。这是因为地形会移动,我们想要的是触电在地形中的位置,而不是在屏幕上的位置。

接下来,我们对Terrain.h/m做以下改变:

代码4

代码4

这只包含Box2D,创造某些实例变量来追踪我们为山体制作的Box2D世界和Box2D物体,Box2D世界将成为新初始程序的参数。

随后添加以下新方法至Terrain.m,位于generateHills之上:

代码5

代码5

这只是个辅助方法,沿山体底部创造Box2D物体,即为“地面”。这只是个临时的方法,因而我们可以添加物体,让它们与其他东西碰撞而不是永无止境地下落,后期我们会将其替换,将山体本身模型化。

到现在为止,所有事物都指出X轴是关键。

接下来,修改Terrain.m以调用此代码,设立调试绘图:

代码6

代码6

每当山体最高点被重置,我们就调用resetBox2DBody来创造Box2D呈现可视山体。现在物体还未改变(游戏邦注:只是增加了一条线代表地面),但接下来我们会修补Box2D物体,将可视山体模型化。

为Box2D物体设立调试绘图需要setupDebugDraw和绘图代码。如果你对Box2D很熟悉,应该检查下。

但是,你可能会产生疑问,为何调试绘图代码在Terrain.m中,而不是HelloWorldLayer.mm。这是因为,在此游戏中画面滚动通过移动Terrain.m来执行。因而,为使Box2D轴系统与屏幕视图相符,我们需要将调试绘图代码添加至Terrain.m中。

现在做最后一步。如果你尝试现在编辑,会出现大量错误。这是因为Terrain.m导入Terrain.h,Terrain.h导入HelloWorldLayer.h,HelloWorldLayer.h导入Box2D.h。导入C++代码(游戏邦注:如Box2D)的.m文件总是会有大量的错误。

幸运的是,解决方法很简单,将Terrain.m重命名为Terrain.mm即可。

编辑然后运行,现在当你点击屏幕时,会看到许多圆物体掉到屏幕中!

圆形物体(from raywenderlich.com)

圆形物体(from raywenderlich.com)

在Box2D中确定山体形状

现在,我们有个Box2D形状来在屏幕底部呈现地面,但是我们真正需要的是呈现山体的形状。

幸运的是,这很简单,因为所有东西都以准备就绪!

1、我们有一系列山脉至高点(borderVertices),我们在上个教程生成resetHillVertices三角形条带时创造出来的。

2、无论至高点何时改变,我们都有个方法可以调用(resetBox2DBody)。

因而,我们只需要替换resetBox2DBody来为borderVertices的每个入口创造边缘!用下列代码将其替换:

代码7

代码7

新执行首先会看下是否已存在Box2D物体,在继续之前会先摧毁老物体。

然后它会创造新物体,开始不断在系列边界至高点中循环,包括山体的至高点。在每个片段中,它可以创造出连接两个至高点的边界。

这确实很容易。将其编辑并执行,现在你得到的是良好的Box2D物体,沿着山体斜坡移动!

沿着山体滚动(from raywenderlich.com)

沿着山体滚动(from raywenderlich.com)

添加海豹

我们做的这个项目名为《Tiny Seal》,但现在还没有海豹!

这是件很悲剧的事情,所以现在必须马上解决。

首先,下载并解压这个项目的资源包,将“Sprite Sheets”和“Sounds”拖动到Xcode项目上。确认每个地址簿选择“复制内容到目标群组文件夹”,然后点击“完成”。

Go to File\New\New File,选择iOS\Cocoa Touch\Objective-C类,点击“下一步”。输入CCSprite作为次类别,点击“下一步”,将文件命名为Hero.mm(游戏邦注:必须使用.mm扩展名,因为文件将使用Box2D),然后点击“完成”。

以下列代码替换Hero.h:

代码8

代码8

这是个非常简单的工作,只是导入Box2D.h然后预设变量随世界和Box2D物体改变。

然后转向Hero.mm,以下列代码替换:

代码9

代码9

createBody方法创造的圆形代表海豹。除了半径有所差别外,这几乎与你之前编写的createTestBodyAtPosition方法相同。

而且,摩擦力设为0,这样海豹就会变得特别光滑。弹性也设为0,这样海豹在撞到地面是就不会弹射起来。

这段代码还设定某些线性阻尼,可以随时间逐渐减少。将物体设定为无法旋转,因为在这款游戏中根本不需要旋转。

initWithWorld方法将精灵初始化为特别精灵框架,调用上述createBody方法。

上述方法更新海豹的位置,使其与Box2D物体位置相匹配,精灵的转动根据海豹的位置来决定。

接下来,你需要修改Terrain.h和Terrain.mm,因为我们要在Terrain.m中插入精灵节点。

首先,对Terrain.h做下列修改:

代码10

代码10

然后,对Terrain.mm做下列修改:

代码11

代码11

这只是为TinySeal.png精灵层面创造了节点,将精灵框架定义从TinySeal.plist装载至精灵框架中。

这样整个过程就几乎完成了!只需对HelloWorldLayer.h做下列修改:

代码12

代码12

然后对HelloWorldLayer.mm做如下修改:

代码13

代码13

编译运行,现在你应该会看到屏幕左侧有只快乐的海豹!

海豹有点脱离屏幕(from raywenderlich.com)

海豹有点脱离屏幕(from raywenderlich.com)

但令人感到厌烦的是,他的位置在画面之外,最好让他再往右边移点。

幸运的是,这个问题很容易便可以解决。只需要打开Terrain.mm,然后将setOffsetX替换如下:

代码14

代码14

这样海豹就往右移了一点。编译运行,现在你可以看到海豹显示得更多了!

海豹完全显示在屏幕上(from raywenderlich.com)

海豹完全显示在屏幕上(from raywenderlich.com)

让海豹移动

现在,我们的游戏即将完成了。我们已经设计出了海豹,只需要让他动起来就可以了!

我们将采用以下策略:

1、首次点击屏幕时,让海豹向右跳一点,准备开始移动。

2、当触点向下移动时,给海豹施加作用力让它向下移动。这会让他迅速沿山体下滑,如果有足够的速度,就会在遇到下座山时飞起来。

3、添加代码确保海豹每次能移动一定的距离,我们不想看到海豹卡在某处!

让我们执行上述做法。对Hero.h做如下修改:

代码15

代码15

然后,对Hero.mm做如下修改:

代码16

代码16

唤醒方法用冲力来让海豹产生最初的移动。

下潜方法运用强大的向下冲力,以及轻微的向右冲力。向下的冲力会让海豹滑下山体,而山体的坡度越大,海豹在遇到下个山体时飞起来的幅度也越大。

limitVelocity只是确保海豹在X轴上至少以5m/s2的加速度移动,在Y轴上的加速度达到-40m/s2。

这部分内容就快要完成了,只要对HelloWorldLayer有些许修改即可。在@interface中添加下列示例变量:

BOOL _tapDown;

然后对HelloWorldLayer.mm做如下修改:

代码18

代码18

编译运行,现在你应该可以看到飞翔的海豹!

飞翔的海豹(from raywenderlich.com)

飞翔的海豹(from raywenderlich.com)

修正海豹的摇晃问题

你在测试关卡时会发现,海豹在地面滑行时摇摇晃晃。

解决该问题的方法之一是采用线性速度,而不总是将位置定在当前速度上。

让我们尝试下这种方法,添加以下代码至Hero.h:

代码19

代码19

然后采用以下代码修改Hero.mm中的更新方法:

代码20

代码20

编译运行,现在你应该会看到海豹的飞行更为平稳!

飞翔得更为平稳的海豹(from raywenderlich.com)

飞翔得更为平稳的海豹(from raywenderlich.com)

缩小

《Tiny Wings》中有个很酷的效果,就是在海豹飞高时场景会缩小。这增添了某种特别酷的感觉,好似你真正在移动!

要添加此效果,只需在HelloWorldLayer.mm的更新方法_hero update调用之后简单添加下列代码:

代码21

代码21

如果海豹在winSize.height*3/4,范围值为1。如果其位置< winSize.height*3/4,范围值会变大。如果其位置> winSize.height*3/4,范围值缩小。

编译运行,试试看你自己能飞多高!

高飞的海豹(from raywenderlich.com)

高飞的海豹(from raywenderlich.com)

动画和音乐

游戏肯定要有些动画和音乐!

只要花很短的时间就能做出让人欣赏的东西。对Hero.h做下列修改:

代码22

代码22

这只是设定我们制作的动画以及海豹没有下滑时的调用方法。

下一步,对Hero.mm做下列修改:

代码23

代码23

这为海豹正常移动时创造动画效果,确定动画运行的方法。下滑的动画是个独立的框架,所以我们也增加个助手方法来设置。

然后,我们对HelloWorldLayer.mm做些许改变:

代码24

代码24

最后修改Terrain.mm,在绘制方法中调用_world->DrawDebugData。

编译运行,现在你的海豹可以各种方法在山体上着陆!

下一步的工作

以上是教程的示例代码。

现在你已经有了个基本的游戏。为何不改变海豹的移动行为,使他沿山体上下的移动更为自然呢?或者开始添加可供收集的道具,山体的装饰,以及可以获取的分数,充分发挥你的想象力吧!

 

« 上一篇下一篇 »

评论列表:

1.路个的  2012-11-2 12:19:04 回复该留言
数学不行的人看不懂啊。。。。

发表评论:

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。