如何使用spritehelper和levelhelper教程:引子

免责申明(必读!):本博客提供的所有教程的翻译原稿均来自于互联网,仅供学习交流之用,切勿进行商业传播。同时,转载时不要移除本申明。如产生任何纠纷,均与本博客所有人、发表该翻译稿之人无任何关系。谢谢合作!


本教程由 无敌葫芦娃翻译,校对:蓝羽、子龙山人


教程截图:

如何使用spritehelper和levelhelper教程:引子

如果你曾经在COCOS2D中手工布局过游戏关卡,你应该有体会那是一件多么痛苦的事情。

在没有工具帮助的情况下,你会发现你老是先猜测一个坐标位置,再运行测试看效果,然后调整坐标位置再测试,不断地重复着这些事情。

还好,因为一些工具的出现,制作游戏关卡的工作会变得更简单一些了。

在这个教程里,我将向你展示怎样利用SpriteHelper 和 LevelHelper 这两个工具,同时结合Cocos2d和Box2d来创建一个简单的Platformer游戏。

为了完成这篇教程的学习,你需要购买LevelHelper 和SpriteHelper这两个软件,但是如果你没有这两个软件,你可以先只是读一下这篇教程,学习一下这两个工具如何使用,在本教程的最后,你可以下载代码运行看看效果。

本教程假设你已经熟悉怎样使用COCOS2D和BOX2D。如果你还是个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和levelhelper教程:引子

SpriteHelper and Sprite Sheet Generation

SpriteHelper是一个结合了Texture Packer(spritesheet生成工具)和Physics Editor(物理shape定义工具)的部分功能的工具。

如果要比较他们之间的特点,TexturePacker和Physics Editor更简洁,他们有许多好用的特性,像抖动,支持命令行,与COCOS2D无缝集成,自动的外形追踪(shape-trace)等等。

但是 SpriteHelper 真正的闪光之处在于它和LevelHelper的完美组合,一旦你使用了SpriteHelper创建了 Spritesheet 和shapes,你可以使用LevelHelper来做场景展示,而如果你是用其他的工具,那么这个过程将不得不手动完成。

那么我们试一下!打开SpriteHelper出现以下的窗口:

如何使用spritehelper和levelhelper教程:引子

 

打开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和levelhelper教程:引子

至此,你已经完成了使用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

如何使用spritehelper和levelhelper教程:引子

这里设置BODY为Dynamic,用一个带有一点边界的圆形SHAPE作为MONSTER的shape。

Cloud

如何使用spritehelper和levelhelper教程:引子

这里,将body设置为static(因为他不动),用一个带有大边界的方形的SHAPE更好的与cloud匹配。我们也设置了一点摩擦,防止hero在它上面滑动的太厉害。

Laserbeam

如何使用spritehelper和levelhelper教程:引子

设置 body为kinematic,因为将通过手动设置速度来移动laser,并且我们不想让其他任何的力(像重力)影响它,我们也把它设成 sensor,让它不会被其它任何body弄停。

Hero (一次选中所有 4 个sprites)

如何使用spritehelper和levelhelper教程:引子

这是一个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,将出现:

如何使用spritehelper和levelhelper教程:引子

通过点击 工程旁边的“+”按钮。在出现的对话框的左边输入“Raycast”作为新的工程名称,选择下面的“Iphone Landscape(480*320)”,点击“CreateNEW project”

如何使用spritehelper和levelhelper教程:引子

在一个XCODE的工程中你可以有多于一个层,你可以在这里设定该层属于哪个工程,这个之所以很重要是因为你为每一个XCODE工程生成的代码将包含一些你不仅在LevelHelper也在代码中引用的特征。

然后在屏幕的底部,设置这些值,

如何使用spritehelper和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和levelhelper教程:引子

 另外,要知道一旦从SpriteHelper导入了一个scene,但是回去重新再次添加另一个sprite 到spritehelper scene里面的时候,会引起重新排布。如果这时在你的levelhelper scene中使用新的sprite,这个过程将变得非常困难。

我曾尝试这样做过,结果将我现存的level给弄乱了(错误的sprites出现在游戏中)。LevelHleper很好用,但只有你将你的game art完成,并且在之后不会再做改动才行。

考虑到事实上,在你做开发时,game art 改动的非常频繁,这还是软件的缺陷,期待更新的版本能够解决这个问题。 

OK——现在可以排布关卡(level)了,先在底部做简单的一列cloud——之后我们将回来完成关卡的设计。

从列表中拖拽一片cloud到屏幕的最左下角,然后选定这个cloud,点击在底部工具条上的小箭头按钮,

如何使用spritehelper和levelhelper教程:引子

可以通过这种方法 在相同的一组offset下 自动 重复添加cloud 若干次(而不用重复拖拽若干次),输入15作为复制的份数,68作为 X offset,0 作为 Y offset,点击 Make Clones:

如何使用spritehelper和levelhelper教程:引子

一列 cloud应该出现在关卡的底部,然后从列表中拖拽一个Monster到关卡中的右上方某处,

如何使用spritehelper和levelhelper教程:引子

到这里,我们就先做这些! 到 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上!

如何使用spritehelper和levelhelper教程:引子

Creating a Simple Level with LevelHelper

现在我们知道这是怎么工作的了,让我们回去添加更多的关卡。

我们已经讲了怎么使用LevelHelper在LEVEL 中添加物体,那么回去从列表中拖拽一些sprites 创建一个像这样的level(当然,你也可以做的有些不同!)。

如何使用spritehelper和levelhelper教程:引子

重要提示:你也要从列表中拖拽一个lase tbeam到屏幕之外,在这个教程里你可能不需要,但是之后的教程将会用到!

完成之后,编译运行,得到如下:


如何使用spritehelper和levelhelper教程:引子

这还不算令人兴奋,因为没有任何东西在动!所以让我们加一些角色的移动和滚动逻辑。

开始之前,我们要在LevelHelper中做一些改动,如你所知,我们呢需要一些方法来引用我们在LevelHelper中创建的物体,有两种方法可以做到:

1、通过为object 设置tag(一种像CSS中的class的东西)

2、通过为每一个object设一个独一无二的名字。

下面开始设置tag,在levelhelper的屏幕底部,在sprite properties部分的下面,点击“+”按钮,创建一个新的tag。

如何使用spritehelper和levelhelper教程:引子

输入PLAYER作为名字,点击ADD,同理对于 MONSTER,LASER,GROUND.

然后点击每一个你添加到level中的sprite,设置他们的tag,注意这里可以多个同时选定(通过功能键+点击或者拖动选定)。

然后在 “sprites in level”下面的表中 找到你为hero 添加的sprite,双击“Unique Name”下方的入口,把它变成 “HERO”.

如何使用spritehelper和levelhelper教程:引子

保存文件。另外,无论何时你修改了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编辑关卡,让游戏容易一些!

如何使用spritehelper和levelhelper教程:引子

LevelHelper and SpriteHelper: My Review

到这里你应该已经知道使用LevelHelper 和 SpriteHelper的基本方法,这篇教程也要结束了。

但是我还要分享一下我对这两个工具的见解,如果你还在犹豫是否要使用这两个工具,

读下面的一些关于使用这两个软件的优点:

1、使得创建和修改关卡变得简单,尤其是和手动的靠着猜测调整sprite的位置相比。

2、这里仍然还有一些我没有说明的好的特点,像创建动画,路径,平行滚动,joints,甚至可以节省更多的时间!

3、API 简明易懂。

这里也有一些缺点:

1、中途向工程中添加新的art有困难,正如先前说的,我这样做毁了我创建好的level,我不得不重做了一个。

2、由于缺乏设计的各种UI缺陷。

3.不能够定制CCSprite,然后指定给LevelHelper中定义的对象

更新: 实际上是可行的,这里有一篇教程教大家怎么做。

总而言之,优点多于缺点,特别是当你拿他们和手动放置sprite或者费事创建你自己的编辑器时。

所以,如果你在寻找做好的Cocos2d的编辑器,并且你可以忍受以上的局限,我想这两个工具是个不错的选择!节省了你宝贵的开发时间

Where To Go From Here?

  这里有本教程的完整源代码

     期待下一篇教程吧,到时候,我们会扩展这个游戏,同时为monster添加更多的功能,使得游戏更有乐趣---小小的透露一下:使用了中级的box2d物理小技巧。

     如果你有任何问题,或者对于翻译教程有任何疑问,你可以在本博客留言,也可以登录泰然论坛和大家一起交流,同时还可以加入我们的qq群。

     论坛交流地址,请点击传送门



你可能感兴趣的:(Sprite)