图文并茂使用CocosBuilder制作Cocos2D游戏

本文由Zynga 工程师原创,翻译:Iven,张作宸,Butterfly

手把手教你使用CocosBuilder一次性导出Cocos2d-html5,Cocos2d-x和Cocos2d-iPhone的游戏资源文件。这个游戏90%的工作量是用工具完成。

这篇教程将会展示如何使用 CocosBuildercocos2d-iphone制作游戏的动作,地图场景和界面。CocosBuilder 已经被Zynga 使用在游戏Dream PetHouse 和 Zynga Slots开发中。目前,由于若干游戏使用CocosBuilder,Zynga 索性将其开源出来(MIT License)。未来必定有更多的游戏在开发中使用该工具。
这篇文章是建立在你很熟悉object-c以及cocos2d-iphone或者cocos2d-x的基础上的。如果你希望学习cocos2d,泰然也有很多文章推荐给你。
在开始本文之前,确保你下载并安装了CocosBuilder 的最新版(本教程基于2.1beta版本)并且升级cocos2d2.0或cocos2d-x的2.03版本以上。

The Game

我们将创造游戏主角

Cocos Dragon。Cocos Dragon

有一对很小的翅膀因此他飞不高,所以我们需要让他触碰金币来给他加速上升直到碰到炸弹为止。你可以到youtube看这个游戏的视屏:youtube

本游戏可以在iOS模拟器上运行,游戏通过触摸来操作方向。假如你希望把这个游戏设计运用到你的产品中,我推荐你用重力感应来替换触摸方式。

设置工程

建立新的xcode工程。工程名称:

CocosDragon。

下载本教程需要用到的美术资源,解压并加到工程中。
现在我们需要建立游戏相应的CocosBuilder 工程。打开CocosBuilder 选择

File

 ->

New Project。

命名为

CocosDragon

 保存并把资源文件放到xcode的

Resources

 文件夹(CocosBuilder的资源在一个名为

ccbResources的文件夹

)打开the

HelloCocosBuilder.ccb

文件.我们不会使用HelloCocosBuilder 文件,所以你可以在CocosBuilder文件系统中干掉他。

创建动画类型的主界面

我们将开始制作Cocos Dragon所有的界面文件,然后将界面链接到相应的代码中。首先,我们创建一个主菜单。
在CocosBuilder打开的CocosDragon 工程中选择

File->New File。我们将让主界面只支持

iPhone,所以在resolutions settings(方案设置)中勾选iPhone 

Portrait

 ,并确保

root object type(根对象类型)为CCLayer

 并勾选

full screen

 (全屏)。

点击创建,然后命名为“

 MainMenuScene

 ”并且保存到

Resources

 文件夹。一个新的空文件MainMenuScene.ccb将在CocosBuilder中开启。

主界面我们会包含一个渐变的背景,一个logo,一个开始游戏的按钮,和几片云彩的动画。首先,让我们开始加入渐变的背景。在窗口顶部的工具栏点击CCLayerGradient 按钮。



我们希望渐变层(gradient layer)充满整个屏幕。选择这个层,设置填充(

content size

)大小单位为“%”并且设置宽高为100x100.



让我们把颜色修改为其他值以遍更适合我们游戏的主色调。点击开始颜色(

start color

)和完成颜色( 

end color

)以至RGB值为下图显示这样。

继续添加logo到主界面( menu scene)。在左边的工程视图(project view)中,拖拽logo.png到canvas 区域。你添加的图片就会像如下那样显示:



当启动主选单场景时会有漂亮的动画,但是我们还需要在启动时增加logo的动画。首先,点击canvas 区域下面的时间设置来指定动画的长度。这里我们把动画出现的时间线设置为2秒。



现在,我们把logo视为一个精灵,并设置logo精灵的关键帧(keyframes )。拖拽时间戳到动画完成的地方(我们这个工程就是之前设置的 

00:02:00

),并且确保logo已经被选定。在动画菜单中选择插入关键帧位置(

Keyframe /Position

),或者使用快捷键'P'.在时间线界面(timeline view),logo精灵会折叠并显示刚才添加的关键帧。



一旦关键帧插入时间线的节点中,我们节点的位置既可以自动添加新的关键帧。首先,移动时间戳到原点(

00:00:00的位置

)。然后,拖拽logo到绘图界面( canvas area)的顶部可见区域(你可以在拖拽的时候按住shift按钮以便对齐)。当你正在做以上操作时,一个新的关键帧就被自动添加到时间线的原点处,并且我们在两个关键帧中间生成平滑过渡的所有帧。

keyframes

你可以点击Play来测试一下这个动画。你也可以移动时间戳来看看每个帧的位置。

这个动画我们完成的很漂亮并且每帧的过渡很平滑,但是让我们再加点料。在关键帧之间右击 插值线(译者注:interpolation line,就是插入了过渡帧的地方)并且选择弹出(

Bounce Out

)。

OK,我们让logo被覆盖了,但是我们依然需要一个启动游戏的按钮。我们将用

CCMenu

 和

CCMenuItemImage来实现这个功能。那么,开始添加菜单功能,在工具栏中点击

CCMenu按钮。如下图:



一个CCMenu 将会添加到你的文件中。CCMenu 保持选中状态,在工具栏点击CCMenuItemImage 按钮(在CCMenu按钮右边)。CCMenuItemImage 会在可是换编辑区域左下角显示一个占位符图像。



拖动这个图片到屏幕中间,也可以使用Cmd+方向键或者 Cmd+shift+方向键来精准拖动,检视器( inspector)中输入需要的值。选中CCMenuItemImage ,选择你希望菜单显示的其他图片样式。通常情况,未按下时我们使用图片名称“

play-button.png

”,按下时我们使用“ 

play-button-down.png

”。



我们现在又一个带logo和play 按钮的选择菜单场景,但是这还是感觉有点空荡荡的。所以我们加入一些云彩来充实一下。在工程界面,拖拽一些云彩到可视化区域。你可以拖拽图片的边角来改变云彩的大小。要改变云彩的遮盖(z-order)可以在时间线区域将相应的时间线拖到上面或者下面,当然也可以在对象选项中(

Object

 menu)选择

Arrange / Bring放到上层,选择Arrange / Send放到下层。



现在让我们添加云彩的介绍动画。这就像之前logo精灵那样做就好。把时间戳移动到动画的末尾。给每个云彩和play按钮添加一个关键帧。你可以选中每个云彩然后点击快捷键‘p’。当关键帧已经被添加到所有对象的动画末尾后,把时间戳移动到动画开头。现在拖拽每个云彩,让底部

点击‘Play’来测试动画。我们现在有一个很漂亮的主菜单介绍动画。当动画播放完毕后,整个场景就是完全禁止的。这不是很好,所以后面我们还会完善。

CocosBuilder提供了多个时间线。在文件中,多个时间线可以连续或者不连续播放,也可以通过代码控制回放。两个不同动画的时间线可以实现平滑过渡。我们这个工程会实现多个时间线,当介绍动画的时间线播放完成后,会循环播放另一个动画。

在动画菜单选择编辑时间线(

Edit Timelines)。在弹出框中,先将默认的时间线重命名为Intro。然后点击

plus-sign添加一条时间线并命名为

Loop,点击完成。



我们选择要编辑的时间线,点击drop-down按钮(图上已经标出),选中

Timelines->Loop。



现在我们有一条未添加关键帧的时间线。这条时间线默认是10秒钟,我们的工程刚好就要这么长。为了方便看出完成后的长度,你可以拖动比例条(scale slider)到左边。



虽然只是一个很小的动画,但可以给场景带来生机。选择一片云彩。将时间戳拖到起点,点击快捷键's'。这将会给比例属性添加一个关键帧。现在把时间戳拖动到时间线末尾并再次点击快捷键‘s’。这将会在动画末尾添加一个关键帧。注意,这条线表示插入的view将会轻微淡出。这是因为两个关键帧是完全一样的,所以没有动画产生。现在这个情况就已经不错了,因为我们希望动画的首尾相同,以便重复播放。



按住option键点击两个关键帧中间,就在时间线的开头和结尾之间添加一个新的关键帧。点击这个新建的关键帧让它出于选中状态,通过调整比例值,或者拖动云彩,让云彩稍微变大一点。

回放动画,你可以看到一片云彩慢慢变大,然后再变回原始大小。让我们给每片云彩做相同的操作。点击所有的关键帧让选择框包含他们。你也可以用shift来选中。选中后,在

Edit菜单选择 复制。把时间戳移动到时间线原点,选择另一片云彩点击粘贴。并重复在所有云彩中操作(并没有添加关键帧)。



再次回放动画。你可以看到所有的云彩都按比例做放大缩小动作了,但是他们是在同时放大缩小,这很古怪,不是吗?点击每片云彩,延伸时间线,并移动中间那个关键帧的位置。这样就会让云彩在不同时间播放动画了。多试几次你就会有经验调整到合适的长度了。

最后,我们要让动画在我们需要的时候自动循环,就要用到链式时间线功能(

chain timeline

 )。点击时间线编辑器左下角的文本,这里显示没有链式时间线。弹出框中点击 循环(loop)。这就会让动画自动循环了。



现在回到介绍动画的时间线(点击时间线drop-down菜单,选择

Timelines -> Intro

)。在

Intro

右边完成的地方链接 

Loop时间线。当我们用代码调用这个场景时,会自动播放Intro动画,Intro完成后回循环播放Loop动画。

主菜单的大部分已经完成,我们剩下的仅仅是在代码中调用这个界面。要调用这个场景,我们要给根节点设置一个定制类。选择根节点(在文件document的CCLayer中)。设置类名为“

MainMenuScene

”。我们会在稍后在代码中创建

MainMenuScene。



选择Play按钮。进入

CCMenuItem下的pressedPlay:进入选择器的输入框,选择目标“Document root”。当我们点击按钮pressedPlay:文件根节点方法(MainMenuScene)就会被调用

游戏场景

我们使用游戏场景来载入在实际游戏中需要的所有东西。同时它也用来显示分数。在file菜单中选择New File 并选择和你MainMenuScene中选择的相同的选项(CCLayer, full screen, iPhone portrait),命名文件为GameScene并保存在文件夹。

对于这个游戏来说,我们会使用和菜单场景一样的背景梯度。或是再创建一遍,或是通过双击项目视图的MainMenuScene,选择CCLayerGradient,复制黏贴到你的新文件处。

点击工具栏上的CCLayer图标来给场景加一个层。稍后在代码中我们会使用这个空的层来加载一个关卡。

层添加后,我们添加一个label用来显示当前游戏的分数。点击工具栏上的CCLabelTTF标签。

如图所示,把位置设成(160,40),字体设置成o System Fonts / MarkerFelt-Wide字号大小24,尺寸设成100*40,校准设成center,最后把文字设成“0”

这个游戏场景已经差不多完工了,唯一还差的就是和代码的连接。选择根节点并且设置自定义类为GameScene然后选择文本标签,我们将要指派这个标签作为根节点类的一个成员变量。将左侧的下拉菜单中选择为Doc root var并且设置那个变量的名字为scoreLabel。

我们也需要连接刚才创建的那个空层到代码。处理步骤同上并将变量明设为levelLayer

现在我们已经完成这个游戏场景啦。让我们继续创建一些游戏对象。

增加游戏物体

我们这个游戏会用到4种游戏物体。游戏的主角:龙,以及钱币,炸弹,和爆炸效果。所有游戏物体都是我们稍后创建的

GameObject

类的子类。

GameObject类是CCNode的一个子类。

因此,CocosBuilder创建的游戏中的所有对象都继承自

CCNode

。(当然还有可能作为

GameObject的插件程序存在,但是在本游戏中,我们并需要使用

)。

让我们开始创建游戏中最复杂的物体—龙。在File菜单中选取New File,创建一个新文件。选择根节点对象为CCNode,反选全屏选项(

full screen

),选择分辨率选项为iphone。

选择根节点并设置自定义类为Dragon。

龙由几个不同的移动部件组成:身体和2个翅膀。首先我们先加入翅膀这样他们就会出现在身体的后面。在项目视图拖动

gameobjects.plist/dragon-wing.png

 到画布区域。设置翅膀的位置为(-8,4),另一个锚点设为(0.84,0.094).

然后,增加身体部分。拖动gameobjects.plist/dragon-body.png到画布区域。设置身体的位置为(0,0)。你的文件看起来就会是这样:

我们将龙的单个翅膀做动画,然后复制翅膀并翻转成一对做动画的翅膀。首先,设置时间轴长度为1秒。然后,选择翅膀,将时间轴标记(timeline)移到开始处并按R键来增加一帧,以做翻转。将时间轴(timeline)标记移动到结束处,再增加一帧翻转。现在移动到中部(00:00:15)。朝下旋转翅膀----你可以通过按住option键,并拖曳其中一个选择选择点来快捷旋转它。旋转这个翅膀差不多80度左右。

我们现在有了扑腾的翅膀啦,但是通过增加Bounce Out缓冲,我们可以让它显示的更加自然。在第一帧和中间帧之间右键,选择Bounce Out.。在中间那帧和最后一帧也做同样的设置。

现在来创建另外一只翅膀。首先确保没有帧被选中,然后选择翅膀。在edit菜单中选择Copy,并Paste.第二个翅膀就被复制到龙的身体前面了,因此我们需要使用Object菜单中的

Arrange /Send Backward

选项

请确保新的翅膀在检查器中FilpX选项有被选中。这只会翻转图像,我们同意需要设置它的位置和锚点,设置位置为(8,4),锚点(0.16,0.094)。这个翅膀的动画现在看起来不错了,但是选择的方向是错的。双击中间的那一帧来锁定它。你可以改变检查器(inspector)中的旋转角度(大概80度左右)。继续播放动画,这时候,龙的两只翅膀应该都正常了。

在游戏中,我们的小龙会在碰到炸弹的时候停止扑腾它的翅膀。当撞到炸弹的时候,我们会播放另一个剪短的动画,然后再扑腾翅膀。因此我们需要2个时间轴。选择

Animation

 菜单的Choose 

Edit Timelines

。重命名当前的时间轴为Flying,增加一个新的时间轴为Hit,然后点击Done

我们希望飞行的时间轴循环播放,所有点击No chained timeline并选择Flying。然后切换到新创建的Hit时间轴。设置它的长度为2秒并和Flying连接起来。当龙被击中时候,我们播放hit动画,播放完成后它自动继续Flying动画。

剩下还有要做的就是创建一个龙被击中的动画。移动时间轴的标记到末尾,选择每个翅膀并按R来增加旋转动画。现在移动标记到开头。将每个翅膀旋转向下,我分别设置了-123和123的值。为每个翅膀增加一个

Bounce Out

属性。

选择龙的身体部分,移动时间轴标记到00:00:15.处。按F来增加一帧。现在,移动标记到开头处并按F增加一个精灵帧。在检查器(inspector)中,设置精灵框为frame togameobjects.plist/dragon-body-hit.png点击play按钮来试试hit动画怎么样。

Bomb

炸弹

在街机游戏中怎么可能会少了坏人?我们需要一些很酷的炸弹~创建一个新文件就像dragon文件一样(选项也一样)。命名文件为Bomb并保存。选择根节点并设置自定义类为Bomb。

我们现在要为我们的炸弹增加一些旋转的钉子。设置时间轴长度为2s。拖曳

gameobjects.plist-bomb-spikes.png

到画布处并设置位置为(0,0),同样的拖曳the

gameobjects.plist/bomb-body.png

炸弹身体会在尖刺的上方。

为了让炸弹看起来更邪恶一点,我们需要让钉子旋转。选择钉子精灵并移动时间轴标记至初始部分。按R来增加一帧旋转的关键帧。通过移动时间轴标记到末尾增加一个关键帧(按R键)并设置精灵的旋转角度是360度。播放动画,我们可以看到钉子绕着身体旋转了。

最后,请确保时间轴自动的循环播放,你可以动过点击

No chained timeline

 text 并选择

Default的时间轴。

Coin

硬币

在我们的游戏中我们会看到2种硬币,普通硬币和结束硬币。吃了普通硬盘会给与我们的龙一个短暂的加速,吃了结束硬币会结束当前关卡。我们可以使用相同的类对于这2种硬币,但是在其中加一个额外的属性以方便我们在代码中区分他们。

用和dragon,bomb文件一样的设置创建一个新文件。命名为Coin,设置自定义类为Coin。

拖曳

gameobjects.plist/coin01.png

到画布区并设置位置为(0,0)。现在我们想要增加一帧基于硬币的动画,设置时间轴的长度为

00:01:06

.确保时间轴标记在最前面而且硬币精灵被选中。现在选择项目视图中的coin01.png到coin18.png。



选择

Animation

 菜单中的

Create Frames from Selected Resources按钮。

你将为硬币精灵添加一系列的关键帧。动画起了作用,但是好像稍微太快了点。拖动选择框包围他们来选择所有的关键帧,然后,选择

Animation

 菜单中的

Stretch Selected Keyframes 

按钮。设置拉伸率(

stretch

)为2.0并点击Done。这时关键帧时间被隔开,动画看起来就更慢了。

对于炸弹,通过设置默认的时间轴,完成这个文件,并保存。

现在我们开始创建“结束硬币”。打开Finder,复制Coin.ccb文件,并重命名为EndCoin.ccb。切回到CocosBuilder,双击项目视图来新建一个文件。为了区别这2种硬币,我们需要在根节点增加一个自定义的属性。选择根节点并点击检查器中的

Edit Custom Properties

。创建一个新的属性,命名为

isEndCoin

,设置种类为Bool并设置值为1.点击Done、



当文件在我们的app中载入的时候,这个自定义属性就会被设置进自定义的类里。为了使这个旋转的硬币视觉上有别于普通的硬币。选择硬币,并点击

color well

来弹出颜色选取器,设置如下所示的颜色,这样,我们就完成了2个不同的硬币。

Explosion

爆炸

当炸弹爆炸的时候,我们需要一些花哨的爆炸效果。我们可以使用粒子系统。每一个爆炸由2个粒子系统构成。

首先创建一个新文件,设置都同前面创建龙、炸弹和硬币的一样并命名文件为

Explosion。

设置时间轴长度为2秒,并定义根节点的自定义类名为

Explosion

。点击工具栏的粒子系统的图标2次,来增加2个粒子系统的文件。

两个粒子系统的参数设置如下图所示。如果想看一下效果,你可以点击检查器中的Start Particles按钮。

Creating a Level

创建一个关卡

我们完成了我们所有的游戏物体。唯一还没做的界面就是关卡地图了。创建一个新文件,请确保根节点中CCLayer和full screen的选项被选择,选择iPhone Portrait的分辨率,但是设置高度为4096.

将其保存为Level文件。设置根节点的自定义类名为Level。我们现在有一个很大的文件来放置我们这些游戏物体。首先先增加龙吧。将项目视图中的Dragon.ccb拖曳到画布区域。选择龙并设置位置为(160,40),如果你移动了龙,你可能需要滚动画布区域才能看到龙。我们需要向下滚动可视化编辑器,以便你在移动进入前能看到我们添加的龙。我们希望能够简便地在代码中加入龙,所以在下拉菜单总的代码连接选项(Code Connections)中选择Doc root var

现在,从项目视图中拖曳并放置更多游戏物体。在我这种情况下,第一关看起来像这样:

在关卡的顶部放置一个“结束硬币”。当龙接触到“接触硬币”的时候,关卡结束。当你很高兴的布局你的关卡的时候,请确保所有打开的文件要保存。现在,选择File菜单中的Publish按钮。这将会把你的文件打包成一个非常紧凑的二进制格式文件。

游戏编码

现在我们已经为游戏创建好了所有接口文件,下一步开始编码。

用Xcode打开项目,右键单击Resources文件夹并选择Add Files to “CocosDragon”…….确保“Create groups for any added folders”单选框被选中,并且“CocosDragon target”也被选中。将所有的图片文件添加到资源目录下,包括plist文件(sprite sheets)和所有ccbi文件。你不必添加以ccb为扩展名的文件进来,因为他们只在CocosBuilder下使用。

下一步,我们要添加CCBReader到项目下。CCBReader在示例代码的文件夹下。将他添加到“你项目/cocos2d-iphone”。将CCBReader文件夹添加到你项目之后,确认Create groups for any added folders被选中,并且Copy items into destination group’s folder被选中。

Xcode下,打开Prefix.pch文件,它在Supporting Files组下。引入头文件的代码如下:

#ifdef __OBJC__ #import <UIKit/UIKit.h> #import <Foundation/Foundation.h> #import "cocos2d.h" #endif

MainMenuScene

我们现在开始编码。让我们一起创建一个主菜单(main menu)!选择File菜单下的New/File。选择Objective-C class,命名该类为MainMenuScene并设定该类为CCLayer的子类。

在MainMenuScene.m的最上部import CCBReader.h。我们也将实现play按钮的回调函数,这个play按钮是我们在ccb文件中加入的。在实现文件中(*.m)中加入如下代码:

- (void) pressedPlay:(id)sender

{

 // Load the game scene

 CCScene* gameScene = [CCBReader sceneWithNodeGraphFromFile:@"GameScene.ccbi"];

 // Go to the game scene

 [[CCDirector sharedDirector] replaceScene:gameScene];

}

当我们按下play按钮,我们将第一次通过ccbi文件加载游戏场景。然后通知CCDirector去用游戏场景replace掉当前的场景。这部分的代码我们需要写到MainMenuScene里,而且需要在游戏开始时候加载。打开AppDelegate.m文件,引入CCBReader.h,然后用下面的代码替换既存的引入初始场景的代码:

// Load the main menu scene from the ccbi-file

CCScene* mainScene = [CCBReader sceneWithNodeGraphFromFile:@"MainMenuScene.ccbi"];

// Then add the scene to the stack. The director will run it when it automatically when the view is displayed.

[director_ pushScene: mainScene];

然后,还是在AppDelegate.m文件,用如下代码替换shouldAutorotateToInterfaceOrientation:方法:

- (BOOL)shouldAutorotateToInterfaceOrientation:(UIInterfaceOrientation)interfaceOrientation

{

 return UIInterfaceOrientationIsPortrait(interfaceOrientation);

}

这将确保我们的游戏以竖屏模式运行。你可以在github上找到源文件:

MainMenuScene.m

MainMenuScene.h

AppDelegate.m

GameScene

当我们按下play按钮之后,GameScene.ccbi文件将会被加载,并创建GameScene的实例。现在我们需要创建GameScene类。创建一个新类命名为GameScene,让其继承自CCLayer。

在CocosBuilder里,我们添加了两个成员变量(levelLayer和scoreLabel)。我们需要添加它们到文件中去。并且也需要加载等级(Level)以及动态的记录当前的分数。在GameScene.h添加前面提到的两个成员变量:

@interface GameScene : CCLayer

{

 CCLayer* levelLayer;

 CCLabelTTF* scoreLabel;

 CCNode* level;

 int score;

}

为了从别的类里更好的管理分数,我们将在GameScene类里增加一个属性。我们也将增加方法去管理游戏结束以及升级的情况。

@property (nonatomic,assign) int score;

+ (GameScene*) sharedScene;

- (void) handleGameOver;

- (void) handleLevelComplete;

@end

下面我们来实现GameScene类的方法。打开GameScene.m。在头部importCCBReader.h。在开始实现该类之前,先定义一个静态变量以方便共享该类的实例。

static GameScene* sharedScene;

在该类的类方法中返回这个共享实例。

+ (GameScene*) sharedScene

{

 return sharedScene;

}

我也需要去synthesize这个分数属性。

@synthesize score;

当一个ccbi文件被加载的时候,CCBReader将会调用创建每个节点的方法didLoadFromCCB。通过实现该方法,你将在文件加载完毕的时候收到一个回调函数。我们将会利用回调信息去设置当前场景以及加载等级。

- (void) didLoadFromCCB

{

 // Save a reference to the currently used instance of GameScene

 sharedScene = self;

 self.score = 0;

 // Load the level

 level = [CCBReader nodeGraphFromFile:@"Level.ccbi"];

 // And add it to the game scene

 [levelLayer addChild:level];

}

这部分代码加载Level.ccbi文件并且将其作为一个子节点加到我们在CocosBuilder里创建的levelLayer上。在真实的游戏当中我们可能会有不止一个的等级文件,并且对应玩家在游戏中的进度选择不同的文件。

当分数属性改变时,我们希望更新label上分数的显示。我们通过setScore:方法来实现。记住我们已经在CocosBuilder定义了scoreLabel。

- (void) setScore:(int)s

{

 score = s;

 [scoreLabel setString:[NSString stringWithFormat:@"%d",s]];

}

离开GameScene之前的最后一步是为控制游戏结束以及升级编码。在实际游戏中你可能做更多的事情在这些方法中,但是在该例子中,我们只是简单的返回主菜单场景。

- (void) handleGameOver

{

 [[CCDirector sharedDirector] replaceScene:[CCBReader sceneWithNodeGraphFromFile:@"MainMenuScene.ccbi"]];

}

- (void) handleLevelComplete

{

 [[CCDirector sharedDirector] replaceScene:[CCBReader sceneWithNodeGraphFromFile:@"MainMenuScene.ccbi"]];

}

你可以在github找到源码

 GameScene.m

 GameScene.h

GameObject

GameObject是一个抽象类它是所有游戏对象的父类他让我们可以等同对待所有的游戏对象创建一个CCNode的子类命名为GameObject这个类包含一些基础属性的设置以及一些基础的方法以便我们在游戏当中控制游戏对象如果我们想移除一个对象那么可以设置isScheduledForRemove属性这个更新方法会在每个框架更新游戏对象状态的时候调用一次我们将在检测冲突的时候(在我们的游戏当中每一个游戏对象都视为圆形)使用radius属性最后如果两个游戏对象发生碰撞两个发生碰撞的游戏对象的handleCollisionWith:方法将被调用下面就是头文件的定义代码

@interface GameObject : CCNode

{

 BOOL isScheduledForRemove;

}

@property (nonatomic,assign) BOOL isScheduledForRemove;

@property (nonatomic,readonly) float radius;

- (void) update;

- (void) handleCollisionWith:(GameObject*)gameObject;

@end

这个.m文件只是实现了一个空方法,因为他是一个抽象类。

@implementation GameObject

@synthesize isScheduledForRemove;

// Update is called for every game object once every frame

- (void) update

{}

// If this game object has collided with another game object this method is called

- (void) handleCollisionWith:(GameObject *)gameObject

{}

// Returns the radius of this game object

- (float) radius

{     return 0;

}

@end

源码参见以下文件链接:

GameObject.m

GameObject.h

Dragon

龙是我们游戏中最复杂的游戏对象。它控制着玩家将要控制的这个角色的行为,同时这也是游戏的主要行为。创建一个GameObject的子类命名为Dragon。

为了控制龙的运动,我们需要两个变量,纵向速度ySpeed,以及横向目标xTarget。xTarget将会在点击iPhone上的打击目标的时候被设置。之后变量将会被外部类设定,我们将会将他作为一个属性。下面是我们需要添加的头文件:

@interface Dragon : GameObject

{

 float ySpeed;

 float xTarget;

}

@property (nonatomic,assign) float xTarget;

@end

.m文件将会更加有趣。首先,我们会整合一些其他的类进来,我们来引入他们。(我们将会在完成Dragon类之后,编写Coin和Bomb类)

#import "Dragon.h"

#import "Coin.h"

#import "Bomb.h"

#import "GameScene.h"

#import "CCBAnimationManager.h"

下面,我们来定义几个常量来方便对龙的行为的控制。使用常量是一个非常不错的选择,因为这样很方便我们去从感官上控制游戏。

#define kCJStartSpeed 8

#define kCJCoinSpeed 8

#define kCJStartTarget 160

#define kCJTargetFilterFactor 0.05

#define kCJSlowDownFactor 0.995

#define kCJGravitySpeed 0.1

#define kCJGameOverSpeed -10

#define kCJDeltaToRotationFactor 5

实现Dragon类的第一步,我们需要synthesize属性xTarget。

@synthesize xTarget;

然后来到init方法,在这里我们将初始化我们的成员变量。xTarget的初始值为160,位于屏幕中心。

- (id) init

{

 self = [super init];

 if (!self) return NULL;

 xTarget = kCJStartTarget;

 ySpeed = kCJStartSpeed;

 return self;

}

我们将使用update方法让龙在屏幕上平滑的移动。在每个frame下,update方法会被调用一次。我们将利用一个计算原始点和目标点之间距离的过滤器方法来获得一个新的X坐标。我所说的目标点就是玩家在屏幕上触摸的点。Y坐标则是在原始坐标的基础上增加现有速度来计算得出的。之后我们更新速度,我们不但可以通过增加常量的方式加快速度,也可以利用参数减慢速度(这将防止龙的攻击速度过快)。我们同时依靠水平速度翘起龙的一侧。如果纵向速度向下过快,那么游戏结束。

- (void) update

{

 // Calculate new position

 CGPoint oldPosition = self.position;

 float xNew = xTarget * kCJTargetFilterFactor + oldPosition.x * (1-kCJTargetFilterFactor);

 float yNew = oldPosition.y + ySpeed;      self.position = ccp(xNew,yNew);

 // Update the vertical speed

 ySpeed = (ySpeed - kCJGravitySpeed) * kCJSlowDownFactor;

 // Tilt the dragon depending on horizontal speed

 float xDelta = xNew - oldPosition.x;

 self.rotation = xDelta * kCJDeltaToRotationFactor;

 // Check for game over

 if (ySpeed < kCJGameOverSpeed)

 {

 [[GameScene sharedScene] handleGameOver];

 }

}

在Dragon类里我们也需要去控制碰撞。我们将通过判断是碰撞了哪种对象来相应的做出动作。如果碰到了钱币,我们增加分数并且给龙一个向上增长的速度。如果我们碰到了炸弹,龙会降低速度并且播放在CocosBuilder里面制作的Hit动画。我们在userObject里用CCBReader保存的CCBAnimationManager,之后调用runAnimationsForSequenceNamed:方法。

- (void) handleCollisionWith:(GameObject *)gameObject

{

 if ([gameObject isKindOfClass:[Coin class]])

 {

 // Took a coin

 ySpeed = kCJCoinSpeed;

 [GameScene sharedScene].score += 1;

 }

 else if ([gameObject isKindOfClass:[Bomb class]])

 {

 // Hit a bomb

 if (ySpeed > 0) ySpeed = 0;

 CCBAnimationManager* animationManager = self.userObject;

 NSLog(@"animationManager: %@", animationManager);

 [animationManager runAnimationsForSequenceNamed:@"Hit"];

 }

}

最后我们要实现radius(半径)属性。它将用于控制碰撞。

- (float) radius

{

 return 25;

}

完整的Dragon类,请参见:

Dragon.m

Dragon.h

Coin

金币有一个相当简单的逻辑,金币在碰到龙的时候会被移除。如果最后一枚金币碰撞到了龙,该等级的任务完成。创建一个名为Coin的类,他是GameObject的子类。在CocosBuilder我们增加了一些自定义的属性,isEndCoin是专门针对最后一枚金币的,普通金币也使用相同的自定义类。我们需要实现这个属性在我们的类里,下文是头部文件:

@interface Coin : GameObject

{

 BOOL isEndCoin;

}

@property (nonatomic,assign) BOOL isEndCoin;

@end

.m文件,我们首先要synthesize属性isEndCoin。

@synthesize isEndCoin;

我们不必移动金币,所以我们不用实现update方法。但是,当背龙碰撞的时候我们想移除它。并且,如果是最后一枚金币的话,我们想升级。

- (void) handleCollisionWith:(GameObject *)gameObject

{

 if ([gameObject isKindOfClass:[Dragon class]])

 {

 if (isEndCoin)

 {

 // Level is complete!

 [[GameScene sharedScene] handleLevelComplete];

 }

 self.isScheduledForRemove = YES;

 }

}

最后,让我们来设定金币的半径。

- (float) radius

{

 return 15;

}

完整的代码,参见下方:

Coin.m

Coin.h

Bomb

炸弹是我们游戏当中的一个障碍物。创建名为Bomb的类,他是GameObject的子类。当炸弹碰撞到玩家的时候,它会爆炸。这个效果是通过移除炸弹并且动态的加载爆炸效果来实现的。我们没有添加任何新的属性进来,所以头文件不需要修改。在.m文件中,我们需要实现handleCollisionsWith:方法。

- (void) handleCollisionWith:(GameObject *)gameObject

{

 if ([gameObject isKindOfClass:[Dragon class]])

 {

 // Collided with the dragon, remove object and add an explosion instead

 self.isScheduledForRemove = YES;

 CCNode* explosion = [CCBReader nodeGraphFromFile:@"Explosion.ccbi"];

 explosion.position = self.position;

 [self.parent addChild:explosion];

 }

}

之后,我们需要设定炸弹的半径。

- (float) radius

{

 return 15;

}

完整的代码,如下:

Bomb.m

Bomb.h

Explosion

最后一个游戏对象,我们将会实现爆炸(Explosion)。爆炸不会影响其他的任何游戏对象。但是我们会在它完成播放之后移除调它。为此我们必须实现CCBAnimationManagerDelegate。在头文件,首先引入CCBAnimationManagerDelegate.h,然后将其作为一个协议加进到Explosion。

#import "CCBAnimationManager.h"

@interface Explosion : GameObject <CCBAnimationManagerDelegate>

@end

.m文件,我们将分配Explosion类作为CCBActionManager的代理,这步的创建将发生在爆炸加载的时候。我们将在didLoadFromCCB:方法来实现这步。

- (void) didLoadFromCCB

{

 // Setup a delegate method for the animationManager of the explosion

 CCBAnimationManager* animationManager = self.userObject;

 animationManager.delegate = self;

}

至此,当动画播放完毕我们将收到回调函数completedAnimationSequenceNamed:,实现该回调并增加一个移除爆炸的定时器。

- (void) completedAnimationSequenceNamed:(NSString *)name

{

 // Remove the explosion object after the animation has finished

 self.isScheduledForRemove = YES;

}

完整的代码见下方:

Explosion.m

Explosion.h

Level

我们就还剩一个Level类没有完成。Level将会控制所有的玩家输入,并且负责更新和移除我们的游戏对象。创建名为Level的类,它是CCLayer的子类。在CocosBuilder里,我们添加了一个成员变量dragon,所以我们要把它加到头文件。

@class Dragon;  @interface Level : CCLayer

{

 Dragon* dragon;

}

@end

.m文件,我们将引入计划访问的类。

#import "Dragon.h"

#import "GameObject.h"

我们也将定义两个常量用于层的滚动。因为龙需要是一直可见的。

#define kCJScrollFilterFactor 0.1

#define kCJDragonTargetOffset 80

我们使用onEnter方法,在每个frame之前,去提供一个回调函数update:。在onExit我们移除这个回调。

- (void) onEnter

{

 [super onEnter];

 // Schedule a selector that is called every frame

 [self schedule:@selector(update:)];

 // Make sure touches are enabled

 self.isTouchEnabled = YES;

}

- (void) onExit

{

 [super onExit];

 // Remove the scheduled selector

 [self unscheduleAllSelectors];

}

在update:方法我们将更新所有游戏对象。记住,在CocosBuilder里我们增加的游戏对象都是level的子对象。之后,游戏对象更新以及发生位置改变的时候我们将检测是否有碰撞。在这个游戏里我们只检测与龙之间的碰撞,因为只有他是移动的。在其他的游戏里,你可能需要写更多更复杂的代码去检测碰撞,或者有可能用到诸如Chipmunk,Box2d的物理引擎。因为所有的碰撞都是可控的,我们将会遍历游戏的所有对象去看哪些对象被定时移除了。我们为这些对象创建一个数组,使用这个队列来移除他们。最后,我们调整层的位置,所以龙总是可见的。当调整位置的时候我们使用过滤器代理去确保移动的平滑。

- (void) update:(ccTime)delta

{

 // Iterate through all objects in the level layer

 CCNode* child;

 CCARRAY_FOREACH(self.children, child)

 {

 // Check if the child is a game object

 if ([child isKindOfClass:[GameObject class]])

 {

 GameObject* gameObject = (GameObject*)child;

 // Update all game objects

 [gameObject update];

 // Check for collisions with dragon

 if (gameObject != dragon)

 {

 if (ccpDistance(gameObject.position, dragon.position) < gameObject.radius + dragon.radius)

 {

 // Notify the game objects that they have collided

 [gameObject handleCollisionWith:dragon];

 [dragon handleCollisionWith:gameObject];

 }

 }

 }

 }

 // Check for objects to remove

 NSMutableArray* gameObjectsToRemove = [NSMutableArray array];

 CCARRAY_FOREACH(self.children, child)

 {

 if ([child isKindOfClass:[GameObject class]])

 {

 GameObject* gameObject = (GameObject*)child;

 if (gameObject.isScheduledForRemove)

 {

 [gameObjectsToRemove addObject:gameObject];

 }

 }

 }

 for (GameObject* gameObject in gameObjectsToRemove)

 {

 [self removeChild:gameObject cleanup:YES];

 }

 // Adjust the position of the layer so dragon is visible

 float yTarget = kCJDragonTargetOffset - dragon.position.y;

 CGPoint oldLayerPosition = self.position;

 float xNew = oldLayerPosition.x;

 float yNew = yTarget * kCJScrollFilterFactor + oldLayerPosition.y * (1.0f - kCJScrollFilterFactor);

 self.position = ccp(xNew, yNew);

}

最后我们需要去做些事情来响应玩家的触摸。我们实现了ccTouchesBegan:withEvent:和ccTouchesMoved:withEvent:方法去获取触摸位置,以及设定龙的xTarget属性。

- (void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event

{

 UITouch* touch = [touches anyObject];

 CGPoint touchLocation = [touch locationInView: [touch view]];

 dragon.xTarget = touchLocation.x;

}

- (void) ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event

{

 UITouch* touch = [touches anyObject];

 CGPoint touchLocation = [touch locationInView: [touch view]];

 dragon.xTarget = touchLocation.x;

}

完整的代码请参见:

Level.m

Level.h

总结

用所有的CocosBuilder文件为游戏创建的类,你应该可以在虚拟机或者真机上编译运行。很感谢花时间读这个教程,祝你编码愉快!

标签: 工具

?>