免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!
本教程由 无敌葫芦娃翻译,校对:蓝羽、子龙山人
教程截图:
如果你曾经在COCOS2D中手工布局过游戏关卡,你应该有体会那是一件多么痛苦的事情。
在没有工具帮助的情况下,你会发现你老是先猜测一个坐标位置,再运行测试看效果,然后调整坐标位置再测试,不断地重复着这些事情。
还好,因为一些工具的出现,制作游戏关卡的工作会变得更简单一些了。
在这个教程里,我将向你展示怎样利用 和 这两个工具,同时结合Cocos2d和Box2d来创建一个简单的Platformer游戏。
为了完成这篇教程的学习,你需要购买LevelHelper 和SpriteHelper这两个软件,但是如果你没有这两个软件,你可以先只是读一下这篇教程,学习一下这两个工具如何使用,在本教程的最后,你可以下载代码运行看看效果。
本教程假设你已经熟悉怎样使用COCOS2D和BOX2D。如果你还是个COCOS2D或者BOX2D的新手,建议你先看一下我的博客上面的。
透露一下:我是从原作者那里免费获得这两个软件的,而且这两个工具也在我的网站上做了广告,但是我的评论和建议是以我作为一个游戏开发者的角度出发的,我绝对不是托。
游戏概览
在教程里,我们将制作一个非常简单的游戏,玩家扮演一个小天使试着从关卡的这头跳到另一个头,同时要避免不从空缺处掉下去,否则就game over了。
我们将使用LevelHelper 和 SpriteHelper这两个工具, 来创建一个Actor在滚动关卡中运动的游戏,以后的教程里我们还会增加一些的其他好玩的特征。
要制作这个游戏,请先下载一些由我妻子为大家制作的。
这个游戏 就叫做 Raycast ,因为之后 我会将 BOX2D RayCasting 整合到游戏中,并且我认为这个游戏非常棒!
Getting Started
打开XCODE,到File\New\New Project, choose iOS\cocos2d\cocos2d_box2d,点击NEXR,将新的工程命名为Raycast,点击Next,选一个文件夹保存我们的工程,点击Create。
从你下载的 游戏资源中,将字体和声音等资源文件拖拽到工程中,别忘了选择 “Copy items into destination group’s folder (if needed)” 单击Finish。
现在 在HelloWorldLayer.h 和 HelloWorldLayer.mm 文件中包含了一些示例的Box2D代码,这里我们删掉这些代码,创建一个空的Layer。
按住Control 单击HelloWorldLayer.h 和 HelloWorldLayer.mm,选定,右击彻底删除这两个文件。
然后选择File\New\New File, 选择 iOS\Cocoa Touch\Objective-C class, 点击 Next. 基类为 NSObject, 点击NEXT, 将新类命名为 ActionLayer.mm, 点击 Save.
把 ActionLayer.h 替换成下面的代码:
#import "cocos2d.h" @interface ActionLayer : CCLayer { } + (id)scene; @end
这里声明了一个CCLayer的子类,同时定义了一个静态方法来创建一个新的CCScene。
在 ActionLayer.mm 加入:
#import "ActionLayer.h" @implementation ActionLayer + (id)scene { CCScene *scene = [CCScene node]; ActionLayer *layer = [ActionLayer node]; [scene addChild:layer]; return scene; } @end
这里实现了刚刚的那个静态方法,用来创建一个新的CCScene,同时把该层当作唯一的孩子添加进去。
最后, 在 AppDelegate.mm 做如下的改动:
// Replace #import "HelloWorldLayer.h" with this: #import "ActionLayer.h" // Replace call to runWithScene with this: [[CCDirector sharedDirector] runWithScene: [ActionLayer scene]];
编译运行工程,这个空的Scene将作为我们的起点。
SpriteHelper and Sprite Sheet Generation
SpriteHelper是一个结合了(spritesheet生成工具)和(物理shape定义工具)的部分功能的工具。
如果要比较他们之间的特点,TexturePacker和Physics Editor更简洁,他们有许多好用的特性,像抖动,支持命令行,与COCOS2D无缝集成,自动的外形追踪(shape-trace)等等。
但是 SpriteHelper 真正的闪光之处在于它和LevelHelper的完美组合,一旦你使用了SpriteHelper创建了 Spritesheet 和shapes,你可以使用LevelHelper来做场景展示,而如果你是用其他的工具,那么这个过程将不得不手动完成。
那么我们试一下!打开SpriteHelper出现以下的窗口:
打开Finder找到你下载的 图片等资源,将目录中所有的图片拖拽到SpriteHelper的方格区域。
这些图片将会互相重叠着出现,按照如下设置,让SpriteHelper自动排布这些Sprites.
确保Crop 处于未选定状态。如果这个选定了,他将移走sprites周围的透明域,但是我们不希望他这样做,因为透明的区域对于我们做的动画来讲很重要。
确保 Save SD 选中,我们的图片是HD版本,选定SD在我们保存时,将会自动的创建一个缩放了一半的SD版本。
确保NPOT未选中。这样将以NPOT(NON POWER OF TWO)的形式保存Spritesheet,
这样在iPad, iPhone 4, and iPod Touch 4th,这些能读NPOT textures的设备上就不必像在旧设备上那样将他们膨胀到最接近的POT(POWER OF TWO )形式,因而节省了texture 内存。
注意你已经在CCCONFIG.H中将COCOS2D中的CC_TEXTURE_NPOT_SUPPORT 激活。
最后,点击Pack Sprites, spritehelper将会自动地排列Sprites.
至此,你已经完成了使用SpriteHelper制作sprite sheet的部分,接下来让我们进入物理SHAPE定义部分。
SpriteHelper and Physics Shape Definitions
对于每一个你加入SpriteHelper中的Sprite,你可以将物理的Body映射到sprite上。
默认的,他映射一个与sprite的边界吻合的矩形Body。你可以通过设置shape border使这个矩形略小,通过设置 is Circle 将他设置成圆形,或者通过 Create Shape 按键 创建你自己的多边形。
你也可以设置其他的物理属性:
- IS SENSOR: 如果这个为TRUE,这个物体可以检测碰撞,但是可以穿过其他的物体。
- SHAPE TYPE: Static 意味着 “不动”,Kinematic 意味着“通过设置速度移动,对于力或者摩擦不作响应”,Dynamic意味着“在BOX2D中的正常运动”通常对于地面要使用 static,对于其他的大多数物体使用dymatic 。
- DENSITY: 移动body的难度(惯性)
- RESTITUTION :BODY的弹性(0没有弹性,1完全弹性)
- MaskBit: 一个用于标识BODY属于哪一类。
- GroupIndex :任何有着相同的Group Index的body将有一直有碰撞产生(如果Index是正的)或者从不产生碰撞(如果Index是负的),这种关系屏蔽了 Category和mask的设置,我个人更偏向于使用mask和category bits而不是 group indices,因为我发现 他们更容易使用。
我发现如果你不事先在纸上将category写出来, 设置MASK BITS 和 CATEGORY BITS 将会很困难。 这里是在这个游戏中我们将使用的CATEGORY和MASKS(注意到所有的categories 必须是2的几次方,因为他们都是存放在一个bitfiled中)
•Laser: Category 1. 应该可以和heros (2)碰撞, 所以他的 mask bit 是 2.
•Hero: Category 2. 应该可以和 lasers (1) 与 clouds (4),碰撞, 所以他的 mask bit 是 5.
•Cloud: Category 4. 应该可以和 heros (2) 与 monsters (8)碰撞, 所以他的 mask 是 10.
•Monster: Category 8.应该可以和 clouds (4)碰撞, 所以他的 mask bit 是 4.
经过以上设置, 设置 shapes 和 properties 将变得简单! 检查每一个 shape 按照如下设置.
Monster
这里设置BODY为Dynamic,用一个带有一点边界的圆形SHAPE作为MONSTER的shape。
Cloud
这里,将body设置为static(因为他不动),用一个带有大边界的方形的SHAPE更好的与cloud匹配。我们也设置了一点摩擦,防止hero在它上面滑动的太厉害。
Laserbeam
设置 body为kinematic,因为将通过手动设置速度来移动laser,并且我们不想让其他任何的力(像重力)影响它,我们也把它设成 sensor,让它不会被其它任何body弄停。
Hero (一次选中所有 4 个sprites)
这是一个Dynamic 的body,他有一个 SHAPE边界来更好的覆盖主body,Friction被设置的很大,用来防止滑动,并且bounciness 被减少了,我们也将body设置成Fixed ROTATION ,这样角色body就不会旋转。
完成!
到此 使用SpriteHelper完成了,到FILE\SAVE ,如果 弹出框出现点击yes,在你的设备上保存为“Sprites”(SPRITER将会自动将.PNG添加到保存位置)。另一个SAVEAS对话框将会出现,再次输入“Sprites”(SpriteHelper 将自动将.pshs添加到该位置)。
如果你没有LevelHelper,你可以使用SpriteHelper来生成一些代码,你可以使用它们在你的游戏中生成 SPRITE SHEET 和物理数据,但是我们有 levelhelper,那么让我们继续!
Getting Started with LevelHelper
打开LevelHelper,将出现:
通过点击 工程旁边的“+”按钮。在出现的对话框的左边输入“Raycast”作为新的工程名称,选择下面的“Iphone Landscape(480*320)”,点击“CreateNEW project”
在一个XCODE的工程中你可以有多于一个层,你可以在这里设定该层属于哪个工程,这个之所以很重要是因为你为每一个XCODE工程生成的代码将包含一些你不仅在LevelHelper也在代码中引用的特征。
然后在屏幕的底部,设置这些值,
•Game World Size: 设置成 (0,0,968,320). 这是对于玩家来说可见的区域.这里把它设置成IPhone屏幕的两倍的宽度以保证 层小并且容易控制,并且容易微调!
•Can Drag Outside World: 设置成选定.这里之后需要将一个物体拖拽到世界边界之外,
•Physic Boundaries: 设置成 (0,0,968,450).
其实如你所知,BOX2D是没有边界的,你可以使用LevelHelper来创建一个边界,以此将物体限定在创建的边界之内。
对于这个游戏,阻止player从左、右、上飞出world是有用的,
注意到,我把底部的边界设定的更低一些,因为在Level上player可以掉进洞里!
•Graivty: 设置成 (0, -10). 这是个重力的估算值(-9.8 m/s^2)
另外,开始时有一件事我不是很明白,那就是怎样滚动 Game world。
要做这个,按住CONTROL并且拖拽(drag in)编辑器,你可以排布(pan)视图。
现在,导入精灵!到File\Import SpriteHelper Scene,选择事先使用SpriteHelper 制作的Sprites.pshs 文件,将看到一列sprites出现在右边(如果没有,点击顶部的第一个tab,把他选定)。
另外,要知道一旦从SpriteHelper导入了一个scene,但是回去重新再次添加另一个sprite 到spritehelper scene里面的时候,会引起重新排布。如果这时在你的levelhelper scene中使用新的sprite,这个过程将变得非常困难。
我曾尝试这样做过,结果将我现存的level给弄乱了(错误的sprites出现在游戏中)。LevelHleper很好用,但只有你将你的game art完成,并且在之后不会再做改动才行。
考虑到事实上,在你做开发时,game art 改动的非常频繁,这还是软件的缺陷,期待更新的版本能够解决这个问题。
OK——现在可以排布关卡(level)了,先在底部做简单的一列cloud——之后我们将回来完成关卡的设计。
从列表中拖拽一片cloud到屏幕的最左下角,然后选定这个cloud,点击在底部工具条上的小箭头按钮,
可以通过这种方法 在相同的一组offset下 自动 重复添加cloud 若干次(而不用重复拖拽若干次),输入15作为复制的份数,68作为 X offset,0 作为 Y offset,点击 Make Clones:
一列 cloud应该出现在关卡的底部,然后从列表中拖拽一个Monster到关卡中的右上方某处,
到这里,我们就先做这些! 到 File \Save 定位目录到到 你的 Raycast 项目的位置,保存为 TestLevel。LevelHelper 将自动生成一个plhs文件。
然后,使用LevelHelper生成我们需要读取的代码。
在主工具条中,点击Download Code,这将会从一个服务器上下载最新的代码模板,然后File\Generate Code\Cocos2D with Box2D,定位到你的Raycast 工程目录,选择目录生成文件。
会在你的工程目录下生成两个文件,LevelHelperLoader.h 和 LevelHelperLoader.mm,现在使用它们!
Integrating LevelHelper with Cocos2D
首先,我们将把SpriteHelper 和LevelHelper生成的文件导入到工程中,选择你使用SpriteHelper 保存的 Sprites.png 和 Sprite-hd.png,将它们拖拽到你的工程目录中,注意选中 “Copy items into destination group’s folder”单击Finish。
打开 ActionLayer.h 作如下的改动,
// Add to top of file #import "Box2D.h" #import "GLES-Render.h" #import "LevelHelperLoader.h" // Add inside @interface b2World * _world; GLESDebugDraw * _debugDraw; LevelHelperLoader * _lhelper;
这里导入了需要的头文件,预先声明了Box2d world,debug drawing ,level helper loader class。
#import "ActionLayer.h" #import "SimpleAudioEngine.h" @implementation ActionLayer + (id)scene { CCScene *scene = [CCScene node]; ActionLayer *layer = [ActionLayer node]; [scene addChild:layer]; return scene; } - (void)setupWorld { b2Vec2 gravity = b2Vec2(0.0f, 0.0f); bool doSleep = false; _world = new b2World(b2Vec2(0,0), doSleep); } - (void)setupLevelHelper { _lhelper = [[LevelHelperLoader alloc] initWithContentOfFile:@"TestLevel"]; [_lhelper addObjectsToWorld:_world cocos2dLayer:self]; [_lhelper createWorldBoundaries:_world]; [_lhelper createGravity:_world]; } - (void)setupDebugDraw { _debugDraw = new GLESDebugDraw([_lhelper pixelsToMeterRatio] * [[CCDirector sharedDirector] contentScaleFactor]); _world->SetDebugDraw(_debugDraw); _debugDraw->SetFlags(b2DebugDraw::e_shapeBit | b2DebugDraw::e_jointBit); } - (void)setupAudio { [[SimpleAudioEngine sharedEngine] playBackgroundMusic:@"Raycast.m4a"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"ground.wav"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"laser.wav"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"wing.wav"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"whine.wav"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"lose.wav"]; [[SimpleAudioEngine sharedEngine] preloadEffect:@"win.wav"]; } - (id)init { if ((self = [super init])) { [self setupWorld]; [self setupLevelHelper]; [self setupDebugDraw]; [self setupAudio]; [self scheduleUpdate]; } return self; } - (void)updateLevelHelper:(ccTime)dt { [_lhelper update:dt]; } - (void)updateBox2D:(ccTime)dt { _world->Step(dt, 1, 1); _world->ClearForces(); } - (void)updateSprites:(ccTime)dt { for (b2Body* b = _world->GetBodyList(); b; b = b->GetNext()) { if (b->GetUserData() != NULL) { CCSprite *myActor = (CCSprite*)b->GetUserData(); if(myActor != 0) { //THIS IS VERY IMPORTANT - GETTING THE POSITION FROM BOX2D TO COCOS2D myActor.position = [_lhelper metersToPoints:b->GetPosition()]; myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle()); } } } } - (void)update:(ccTime)dt { [self updateLevelHelper:dt]; [self updateBox2D:dt]; [self updateSprites:dt]; } -(void) draw { glClearColor(98.0/255.0, 183.0/255.0, 214.0/255.0, 255.0/255.0); glClear(GL_COLOR_BUFFER_BIT); glDisable(GL_TEXTURE_2D); glDisableClientState(GL_COLOR_ARRAY); glDisableClientState(GL_TEXTURE_COORD_ARRAY); _world->DrawDebugData(); glEnable(GL_TEXTURE_2D); glEnableClientState(GL_COLOR_ARRAY); glEnableClientState(GL_TEXTURE_COORD_ARRAY); } - (void)dealloc { [_lhelper release]; _lhelper = nil; delete _world; [super dealloc]; } @end
WOW! 好长的一段代码 啊! 如果你熟悉COCOS2D和Box2d这里的许多代码应该已经见到了,在这里我只是点出 使用到Levelhelper的一些特别之处。
- setupLevelHelper包含了初始化的代码。创建了一个新的 LevelhelperLoader实例,
- 传递进你想使用的 Level文件(不用添加.plhs扩展名),然后调用一个方法来 将你事先使用 SpriteHleper /Level Helper创建的 各种 shape 添加进Box2d的world,然后创建 World的边界,将重力设置成在 LevelHelper中定义的大小。
- 注意到使用Box2d时,通常在某个头文件中定义PTM_RATIO,使用LevelHelper你可以从LevelHelperLoader 类中获得这个值,而不用调用 pixelsToMeterRatio Method。默认值是32.0,当然如果你需要别的值,你也可以手动设置它。
- 在开始更新Box2d world之前,要给LevelHelperLoader时间来运行调用update方法,LevelHelperLoader用此来使物体沿着路径运动,或者控制平行滚动。虽然在这个游戏中没有什么区别,但是这样做是一个好的习惯。
- 我们应该更新与每一个 Box2d中 与 Body相关联的sprite,使得他们的位置重合,LevelHelperLoader在 body的userData中自动存储了与之相关的sprite的引用。
- 编译运行工程,monster掉到了clouds上!
Creating a Simple Level with LevelHelper
现在我们知道这是怎么工作的了,让我们回去添加更多的关卡。
我们已经讲了怎么使用LevelHelper在LEVEL 中添加物体,那么回去从列表中拖拽一些sprites 创建一个像这样的level(当然,你也可以做的有些不同!)。
重要提示:你也要从列表中拖拽一个lase tbeam到屏幕之外,在这个教程里你可能不需要,但是之后的教程将会用到!
完成之后,编译运行,得到如下:
这还不算令人兴奋,因为没有任何东西在动!所以让我们加一些角色的移动和滚动逻辑。
开始之前,我们要在LevelHelper中做一些改动,如你所知,我们呢需要一些方法来引用我们在LevelHelper中创建的物体,有两种方法可以做到:
1、通过为object 设置tag(一种像CSS中的class的东西)
2、通过为每一个object设一个独一无二的名字。
下面开始设置tag,在levelhelper的屏幕底部,在sprite properties部分的下面,点击“+”按钮,创建一个新的tag。
输入PLAYER作为名字,点击ADD,同理对于 MONSTER,LASER,GROUND.
然后点击每一个你添加到level中的sprite,设置他们的tag,注意这里可以多个同时选定(通过功能键+点击或者拖动选定)。
然后在 “sprites in level”下面的表中 找到你为hero 添加的sprite,双击“Unique Name”下方的入口,把它变成 “HERO”.
保存文件。另外,无论何时你修改了tag值,你都要重新生成代码,因为tag值将生成在头文件中,所以当你完成,到 File\Generate Code\Cocos2D with Box2D. 选择你的Raycast目录,重新生成代码。
你可以在LevelHelperLoader.h中修改这些值:
enum LevelHelper_TAG { DEFAULT_TAG = 0, PLAYER = 1, MONSTER = 2, LASER = 3, GROUND = 4, NUMBER_OF_TAGS = 5 };
知道Cocos2d sprites怎样获得一个tag 值吗?LevelHelper根据你在Levelhelper中设定的值,设定这些tag值。
OK!现在终于可以添加 角色的移动和滚动逻辑的代码了!在ActionLayer.h中作如下的改动:
// Add inside @interface double _playerVelX; CCSprite * _hero; b2Body * _heroBody; BOOL _gameOver;
在ActionLayer.mm中作如下的改动:
// Add to top of file #define MOVE_POINTS_PER_SECOND 80.0 // Add to bottom of setupLevelHelper _hero = [_lhelper spriteWithUniqueName:@"hero"]; NSAssert(_hero!=nil, @"Couldn't find hero"); _heroBody = [_lhelper bodyWithUniqueName:@"hero"]; NSAssert(_heroBody!=nil, @"Couldn't find hero body"); // Add at bottom of init self.isTouchEnabled = YES; // Add after init -(void)setViewpointCenter:(CGPoint) position { CGSize winSize = [[CCDirector sharedDirector] winSize]; CGRect worldRect = [_lhelper gameWorldSize]; int x = MAX(position.x, worldRect.origin.x + winSize.width / 2); int y = MAX(position.y, worldRect.origin.y + winSize.height / 2); x = MIN(x, (worldRect.origin.x + worldRect.size.width) - winSize.width / 2); y = MIN(y, (worldRect.origin.y + worldRect.size.height) - winSize.height/2); CGPoint actualPosition = ccp(x, y); CGPoint centerOfView = ccp(winSize.width/2, winSize.height/2); CGPoint viewPoint = ccpSub(centerOfView, actualPosition); self.position = viewPoint; } - (void)loseGame { [_hero runAction:[CCSequence actions: [CCScaleBy actionWithDuration:0.35 scale:2.0], [CCDelayTime actionWithDuration:0.75], [CCScaleTo actionWithDuration:0.35 scale:0], nil]]; [_hero runAction:[CCRepeatForever actionWithAction: [CCRotateBy actionWithDuration:0.5 angle:360]]]; _gameOver = YES; } - (void)winGame { _gameOver = YES; [[SimpleAudioEngine sharedEngine] playEffect:@"win.wav"]; } - (void)updateHero:(ccTime)dt { if (_playerVelX != 0) { b2Vec2 b2Vel = _heroBody->GetLinearVelocity(); b2Vel.x = _playerVelX / [_lhelper pixelsToMeterRatio]; _heroBody->SetLinearVelocity(b2Vel); } } - (void)updateViewpoint:(ccTime)dt { [self setViewpointCenter:_hero.position]; } - (void)updateGameOver:(ccTime)dt { if (_gameOver) return; CGRect worldRect = [_lhelper gameWorldSize]; if (_hero.position.x > (worldRect.origin.x + worldRect.size.width) * 0.95) { [self winGame]; } if (_hero.position.y < 0.5) { [self loseGame]; } } // Add in update, at beginning [self updateHero:dt]; // Add in update, at end [self updateViewpoint:dt]; [self updateGameOver:dt]; // Add after draw - (void)handleTouchAtPoint:(CGPoint)touchLocation { if (touchLocation.x < _hero.position.x) { _playerVelX = -MOVE_POINTS_PER_SECOND; _hero.flipX = YES; } else { _playerVelX = MOVE_POINTS_PER_SECOND; _hero.flipX = NO; } } - (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { if (_gameOver) return; UITouch *touch = [touches anyObject]; CGPoint touchLocation = [self convertTouchToNodeSpace:touch]; [self handleTouchAtPoint:touchLocation]; if (touch.tapCount > 1) { _heroBody->ApplyLinearImpulse(b2Vec2(_playerVelX/[_lhelper pixelsToMeterRatio], 1.25), _heroBody->GetWorldCenter()); [[SimpleAudioEngine sharedEngine] playEffect:@"wing.wav"]; } } - (void)ccTouchesMoved:(NSSet *)touches withEvent:(UIEvent *)event { if (_gameOver) return; UITouch *touch = [touches anyObject]; CGPoint touchLocation = [self convertTouchToNodeSpace:touch]; [self handleTouchAtPoint:touchLocation]; } - (void)ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { _playerVelX = 0; } - (void)ccTouchesCancelled:(NSSet *)touches withEvent:(UIEvent *)event { _playerVelX = 0; }
- 这里又有许多代码,当然也有许多是重复的:这里有些提示:
- 在 setupLevelHelper的底部,我们使用了 spriteWithUniqueName 和bodyWithUniqueName 来唯一定位"hero"的sprite和body。
- 在LevelHelper的 set up in the Sprite in Level 部分。
- setViewpointCenter是一个通过滚动layer以保持hero在中心的函数,更多的细节看
- 《How To Drag and Drop Sprites with Cocos2D or our Learning Cocos2D》 一书.
- 我们通过调用 SetLinerVelocity来根据玩家触摸了player的左边还是右边来手动设置速度。我们使用convertTouchToNodeSpace来获得layer内的触摸点。
- 如果玩家tap了两下屏幕,让player接受一个小的力,这个过程通过ApplyLinearImpulse完成,产生一个微弱的飞翔的效果。
- 如果player到了屏幕的最右端,,我们说player赢了,如果player掉进坑里,失败。
- 在胜利或者失败时会播放一小段动画和音乐。
- 编译运行,看你是否能赢了这个游戏!
- 如果你不能,你可以使用levelhelper编辑关卡,让游戏容易一些!
LevelHelper and SpriteHelper: My Review
到这里你应该已经知道使用LevelHelper 和 SpriteHelper的基本方法,这篇教程也要结束了。
但是我还要分享一下我对这两个工具的见解,如果你还在犹豫是否要使用这两个工具,
读下面的一些关于使用这两个软件的优点:
1、使得创建和修改关卡变得简单,尤其是和手动的靠着猜测调整sprite的位置相比。
2、这里仍然还有一些我没有说明的好的特点,像创建动画,路径,平行滚动,joints,甚至可以节省更多的时间!
3、API 简明易懂。
这里也有一些缺点:
1、中途向工程中添加新的art有困难,正如先前说的,我这样做毁了我创建好的level,我不得不重做了一个。
2、由于缺乏设计的各种UI缺陷。
更新: 实际上是可行的,这里有教大家怎么做。
总而言之,优点多于缺点,特别是当你拿他们和手动放置sprite或者费事创建你自己的编辑器时。
所以,如果你在寻找做好的Cocos2d的编辑器,并且你可以忍受以上的局限,我想这两个工具是个不错的选择!节省了你宝贵的开发时间
Where To Go From Here?
这里有本教程的。
期待下一篇教程吧,到时候,我们会扩展这个游戏,同时为monster添加更多的功能,使得游戏更有乐趣---小小的透露一下:使用了中级的box2d物理小技巧。
如果你有任何问题,或者对于翻译教程有任何疑问,你可以在本博客留言,也可以登录泰然论坛和大家一起交流,同时还可以加入我们的qq群。
论坛交流地址,请点击!