游戏邦在:
杂志专栏:
gamerboom.com订阅到鲜果订阅到抓虾google reader订阅到有道订阅到QQ邮箱订阅到帮看

如何创造一款类似《割绳子》的游戏(一)

发布时间:2013-11-28 11:38:44 Tags:,,,,

作者:Gustavo Ambrozio

在这一系列教程中,你将使用Cocos2D和Box2D从头开始创造一款《割绳子》般的游戏。(请点击此处阅读本文第二部分

你将使用Ray的妻子Vicki所创造的图像去创造一款非常基本的《割绳子》游戏,即主要使用Box2D呈现如何创造并破坏绳子。

在这两部分的教程中,你将学到:

如何使用绳子的节点

如何使用verlet绘制绳子

如何切断绳子

以及其它更多内容!

开始

在本篇教程中,你将使用最新的Xcode版本(在写本文的时候是4.3x)以及Cocos2D的2.0版本。我们正在使用2.0是因为它包含了Box2D中的新节点,而这也正是我们所需要的,这是1.0.1版本所没有的。

如果你仍在使用Cocos2D 1.0.1,并担心你现在的模版会被换掉,请别担心。因为Cocos2D版本2的模版会与1.0.1版本的模版一起安装。

当你安装了Cocos2D的2.0版本时,启动Xcode并点击“Create a new Xcode project。”在iOS下,选择Cocos2Dv2.x,然后选择“Cocos2D iOS with Box2D”模版,并点击Next。

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

在下一个屏幕上,为你的项目命名为“CutTheVerlet”并填写你的公司标识。不要忘记选择“iPhone”作为器件系列。

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

在下一步,选择文件夹去储存你的项目。你不需要创造新的文件夹—-Xcode将为你创造一个。点击“Create”,你便能开始编写代码了。

你可能知道这一模版项目是关于什么,但为了乐趣,点击Run看看会发生什么:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

你可以使用一些新组块。这很酷,但是你要创造的更酷!

遵从GitHub

如果你想要遵循我,先检查这一项目的GitHub页面!

你将会发现这一便利性,即能够放置你受困于某些内容,或在你想要在特定阶段着眼于某一代码时提供给你帮助。

而如果你不这么做,你便可以遵循这里的步骤!

清理

打开HelloWorldLayer.h并删除精灵spriteTexture_ instance变量的声明:

//Remove this line
CCTexture2D *spriteTexture_;    // weak ref

打开HelloWorldLayer.mm并完全删除如下三个方法的安装启用:

1.1.-(void) addNewSpriteAtPosition:(CGPoint)p,

2.-(void) createMenu,

3.-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event

前两个方法在文件最上方也带有声明。确保你也删除了这些声明。

引导调用了createMenu和addNewSpriteAtPosition:,现在你在这一文件的init方法中拥有一些警告信息了。通过删除这些方法调用而清楚init方法。

接下来删除启用加速器的内容,因为你不能在这一项目中使用它。同样删除关于窗口大小的内容,因为你现在并不需要它:

// delete these lines!
self.isAccelerometerEnabled = YES;
CGSize s = [CCDirector sharedDirector].winSize;

在init方法的最后,删除所有会添加内容到场景中的东西:

// Delete ALL the lines listed here!

//Set up sprite

#if 1
// Use batch node. Faster
CCSpriteBatchNode *parent = [CCSpriteBatchNode batchNodeWithFile:@"blocks.png" capacity:100];
spriteTexture_ = [parent texture];
#else
// doesn’t use batch node. Slower
spriteTexture_ = [[CCTextureCache sharedTextureCache] addImage:@”blocks.png”];
CCNode *parent = [CCNode node];
#endif
[self addChild:parent z:0 tag:kTagParentNode];

[self addNewSpriteAtPosition:ccp(s.width/2, s.height/2)];

CCLabelTTF *label = [CCLabelTTF labelWithString:@"Tap screen" fontName:@"Marker Felt" fontSize:32];
[self addChild:label z:0];
[label setColor:ccc3(0,0,255)];
label.position = ccp( s.width/2, s.height-50);

这便是关于HelloWorld.mm的设置!

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

在项目导航器中打开资源群组并删除blocks.png,因为你并不需要它。当被问到如果你想要删除参考或将其丢进回收站,不要害羞。点击“Move to Trash”,你不需要任何不必要的文件。

添加一些精灵

现在你已经丢弃了不需要的内容,你可以添加一些需要的内容,开始创造图像。我是从Vicki的网站中获取大多数图像,她也为这一教程创造了一些额外的精灵。继续并从GitHub中下载图像文件。

获取ZIP文件内容。这将创造一个名为“Art”的新文件夹。在这一文件夹中有还有两个文件夹:Images和Originals。Originals文件夹带有所有原始PNG文件,即基于视网膜分辨率。

你不能在项目中直接使用这些原始文件。你也许知道,在Cocos2D中使用精灵的推荐方法是使用基于TexturePacker这样的项目所创造的精灵图表。

但是在这里,我们不需要创造精灵图表—-我已经为你创造了一个!你可以在ZIP中下载到它,就在Images子目录中。

继续前进。Art文件夹中的Images文件夹拥有你想要添加到项目中的一切内容,你只需要继续向前并将该文件夹拖到项目的资源群组便可。

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

确保选中“Copy items into destination group’s folder”去复制这些文件到你的项目文件夹中。

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

现在你拥有所需的元素了,你便可以开始创造场景。你将创造如下的初始场景:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

为了做到这点,首先你需要添加参考到你将在场景中使用的鳄鱼精灵上。所以添加如下代码到HelloWorldLayer.h,也就是在现有的m_debugDraw声明下方:

CCSprite *croc_;            // weak ref

然后千万HelloWorldLayer.mm并在init方法中的initPhysics调用前插入如下代码:

// Load the sprite sheet into the sprite cache
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:@”CutTheVerlet.plist”];

// Add the background
CCSprite *background = [CCSprite spriteWithSpriteFrameName:@"bg.png"];
background.anchorPoint = CGPointZero;
[self addChild:background z:-1];

// Add the croc
croc_ = [CCSprite spriteWithSpriteFrameName:@"croc_front_mouthclosed.png"];
croc_.anchorPoint = CGPointMake(1.0, 0.0);
croc_.position = CGPointMake(320.0, 30.0);
[self addChild:croc_ z:1];

第一行加载了你的所有精灵,并将其保存在精灵图表框架缓存中。然后你添加了背景和鳄鱼。创建并运行,看看结果是否如你预想的那样:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

哎呀,并不完全一样!场景是基于纵向模式进行设计,但是代码却让它颠倒了。

不过这是个很容易解决的问题。打开AppDelegate.mm,找到shouldAutorotateToInterfaceOrientation:,将执行改为如下:

return UIInterfaceOrientationIsPortrait(interfaceOrientation);

现在运行并微笑:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

到目前为止都很简单吧?现在开始接触较为复杂的部分:绳索。

你所说的Verlet是什么?

所以,你会问该如何使用Cocos2D和Box2D执行绳索?

Box2D提供的唯一可用的东西是一个名为绳索节点的节点。绳索节点可以被当成是“最大距离”的节点。你将明确两个实体以及它们之间的最大距离,Box2D会确保这两个实体永远都不会超过这一距离,就像连接着彼此终点的真正绳索那样。

这点很棒,但是Cocos2D并没有办法去绘制这样的绳索。你可以在两个实体间画一条线,但这不能描述所有场景中的真实绳索。举个例子来说吧,如果对象间的距离小于最大距离(在这个例子中的绳索长度),那么绳索便会下垂。但如果你在两个点之间绘制一条直线,显然它不会下垂,所以这看起来并不真实。

为了获得下垂的效果,我发现了一个名为VRope的类,即Clever Hamster Games的PatrickC在Cocos2D论坛上所编写的,基于YoAmbulante.com中的ActionScript的代码。多亏了这些网站,你将轻松许多。

为了模拟绳索,VRope类使用了Verlet整合理念。就像YouAmbulante.com中所解释的:

Verlet整合存在于点和链接中,在这些点之间每个点都记得其最初的位置,并由此去决定下一步,x的新位置等于x + x – previous_x(y亦是如此),然后每个点将两两链接在一起以保持彼此间的同等距离,就像程序开始时那样。

我将进一步解释这是如何在VRope类中执行的,但首先让我们添加一个简单的绳索到项目中并看看它是如何行动的。

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

从PatrickC中下载初始VRope类代码。将vrope-latest文件夹拖到你的项目树中:

再一次选择复制条目到目标文件夹中:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

如果你现在编译代码,你将遇到两个错误与一个警示。在进一步前进前先修改问题。

这里的错误在于VRope.mm的debugDraw方法。这一类的创造目的是为了与Cocos2D的1.0.1版本共同使用,即支持OpenGL ES 1.0。Cocos2D 2.x使用了OpenGL ES2.0,而一些早前的Cocos2D 1.x函数与其并不兼容。因为你并不会使用debugDraw,所以只要删除它表面可以。不要忘记也删除VRope.h中的方法声明。

警告是来自一个被弃用的Cocos2D方法,即调用自createRope:pointB:。这是Xcode中应该被标记为黄色的内容:

CCSprite *tmpSprite = [CCSprite spriteWithBatchNode:spriteSheet rect:CGRectMake(0,0,multiplier,[[[spriteSheet textureAtlas] texture] pixelsHigh])];

将其替换为:

CCSprite *tmpSprite = [CCSprite spriteWithTexture:spriteSheet.texture
rect:CGRectMake(0,0,
multiplier,
[[[spriteSheet textureAtlas] texture] pixelsHigh]/CC_CONTENT_SCALE_FACTOR())];

现在项目便可以不带错误或警告进行编译了。

你必须稍微修改下VRope类去适应游戏的需求。初始类记录了两个Box2D实体并基于这些实体的移动模拟了绳索。但它基于两个对象(即在被创造出来时)间的距离决定了绳索的长度。这将导致游戏中的生错长度的不适当,即绳索可能会不紧实。

你将修改类去使用一个b2RopeJoint实例,它已经附加了2个实体了。它同样也带有关于绳索长度的信息,你需要考虑b2RopeJoint附在两个实体上的定位点。

换句话说,你将明确绳索的附加位置,长度,而绳索将自动估算适当的“下垂”指数去满足这些限制。

所以打开VRope.h并将如下:

b2Body *bodyA;
b2Body *bodyB;

换成:

b2RopeJoint *joint;

同样将初始方法声明从:

-(id)init:(b2Body*)body1 body2:(b2Body*)body2 spriteSheet:(CCSpriteBatchNode*)spriteSheetArg;

改为:

-(id)initWithRopeJoint:(b2RopeJoint*)joint spriteSheet:(CCSpriteBatchNode*)spriteSheetArg;

这将让我们能够通过直接传入b2RopeJoint而更轻松地创造一个绳索的视觉再现。

接下来将createRope方法从:

-(void)createRope:(CGPoint)pointA pointB:(CGPoint)pointB;

变成:

-(void)createRope:(CGPoint)pointA pointB:(CGPoint)pointB distance:(float)distance;

这让我们能够设置绳索的长度。

现在,前往VRope.mm并替换现有的init:body2:spriteSheet: 执行为:

-(id)initWithRopeJoint:(b2RopeJoint*)aJoint spriteSheet:(CCSpriteBatchNode*)spriteSheetArg {
if((self = [super init])) {
joint = aJoint;
CGPoint pointA = ccp(joint->GetAnchorA().x*PTM_RATIO,joint->GetAnchorA().y*PTM_RATIO);
CGPoint pointB = ccp(joint->GetAnchorB().x*PTM_RATIO,joint->GetAnchorB().y*PTM_RATIO);
spriteSheet = spriteSheetArg;
[self createRope:pointA pointB:pointB distance:joint->GetMaxLength()*PTM_RATIO];
}
return self;
}

在这里我们从b2RopeJoint取出了两个实体并调用了之前的初始化器。

同样,替换现有的reset和update执行去使用绳索节点而不是实体,如下:

-(void)reset {
CGPoint pointA = ccp(joint->GetAnchorA().x*PTM_RATIO,joint->GetAnchorA().y*PTM_RATIO);
CGPoint pointB = ccp(joint->GetAnchorB().x*PTM_RATIO,joint->GetAnchorB().y*PTM_RATIO);
[self resetWithPoints:pointA pointB:pointB];
}

-(void)update:(float)dt {
CGPoint pointA = ccp(joint->GetAnchorA().x*PTM_RATIO,joint->GetAnchorA().y*PTM_RATIO);
CGPoint pointB = ccp(joint->GetAnchorB().x*PTM_RATIO,joint->GetAnchorB().y*PTM_RATIO);
[self updateWithPoints:pointA pointB:pointB dt:dt];
}

最后,createRope需要进行更新去使用你所提供的距离,而不是当前两个点之间的距离。所以,将方法声明从:

-(void)createRope:(CGPoint)pointA pointB:(CGPoint)pointB {

改为:

-(void)createRope:(CGPoint)pointA pointB:(CGPoint)pointB distance:(float)distance {

将下面一行从方法中删除:

// Remove this line
float distance = ccpDistance(pointA,pointB);

你同样也需要在initWithPoints:pointB:spriteSheet:中将早前的调用更新为createRope。前往这一方法并改变如下行:

[self createRope:pointA pointB:pointB];

为:

[self createRope:pointA pointB:pointB distance:ccpDistance(pointA, pointB)];

现在这便足够了。但在我们创造绳索前,你需要添加更多基础设施去处理绳索和糖果。

给鳄鱼的糖果

你的游戏将出现一只俄语而不是名为Om Nom的怪物(游戏邦注:即《割绳子》中的角色),但是你仍然需要喂养他!也就是糖果,不过在这里会以菠萝的形式呈现出来。

Pineapple-Croc(from raywenderlich)

Pineapple-Croc(from raywenderlich)

首先,添加更多实例变量到HelloWorldLayer.h:

NSMutableArray *ropes;
NSMutableArray *candies;
b2Body* groundBody;    // weak ref
CCSpriteBatchNode *ropeSpriteSheet; // weak ref

数组将帮助你追踪你所创造的绳索和糖果。groundBody的实例变量将为世界实体提供一个参考,ropeSpriteSheet将保持精灵的批次节点,即能够用于VRope实例的创造。

现在,打开HelloWorldLayer.mm,并添加如下内容到init中,即在self.isTouchEnabled = YES;之后:

ropes = [[NSMutableArray alloc] init];
candies = [[NSMutableArray alloc] init];
ropeSpriteSheet = [CCSpriteBatchNode batchNodeWithFile:@"rope_texture.png"];
[self addChild:ropeSpriteSheet];

并添加如下内容到dealloc,即在super dealloc的调用之前:

[ropes release];
[candies release];

你需要保持在initPhysics所创造的世界实体参考。所以前往方法并将其从:

b2Body* groundBody = world->CreateBody(&groundBodyDef);

改为:

groundBody = world->CreateBody(&groundBodyDef);

现在添加该方法到文件的末尾(但却是在final@end的前面):

-(b2Body *) createCandyAt:(CGPoint)pt
{
// Get the sprite from the sprite sheet
CCSprite *sprite = [CCSprite spriteWithSpriteFrameName:@"pineapple.png"];
[self addChild:sprite];

// Defines the body of your candy
b2BodyDef bodyDef;
bodyDef.type = b2_dynamicBody;
bodyDef.position = b2Vec2(pt.x/PTM_RATIO, pt.y/PTM_RATIO);
bodyDef.userData = sprite;
bodyDef.linearDamping = 0.3f;
b2Body *body = world->CreateBody(&bodyDef);

// Define the fixture as a polygon
b2FixtureDef fixtureDef;
b2PolygonShape spriteShape;

b2Vec2 verts[] = {
b2Vec2(-7.6f / PTM_RATIO, -34.4f / PTM_RATIO),
b2Vec2(8.3f / PTM_RATIO, -34.4f / PTM_RATIO),
b2Vec2(15.55f / PTM_RATIO, -27.15f / PTM_RATIO),
b2Vec2(13.8f / PTM_RATIO, 23.05f / PTM_RATIO),
b2Vec2(-3.35f / PTM_RATIO, 35.25f / PTM_RATIO),
b2Vec2(-16.25f / PTM_RATIO, 25.55f / PTM_RATIO),
b2Vec2(-15.55f / PTM_RATIO, -23.95f / PTM_RATIO)
};

spriteShape.Set(verts, 7);
fixtureDef.shape = &spriteShape;
fixtureDef.density = 30.0f;
fixtureDef.filter.categoryBits = 0×01;
fixtureDef.filter.maskBits = 0×01;
body->CreateFixture(&fixtureDef);

[candies addObject:[NSValue valueWithPointer:body]];

return body;
}

该方法将在屏幕上一个特定位置创造一个菠萝实体及其精灵。它使用了一个多边形作为实体的固定装置。固定装置的点是使用VertexHelper。

现在开始创造一些绳索!

最后我们可以添加方法去创造绳索并附加两个实体到它上面。

首先,在HelloWorldLayer.mm添加如下输入内容:

#import “VRope.h”

现在添加这一方法到文件的最后:

-(void) createRopeWithBodyA:(b2Body*)bodyA anchorA:(b2Vec2)anchorA
bodyB:(b2Body*)bodyB anchorB:(b2Vec2)anchorB
sag:(float32)sag
{
b2RopeJointDef jd;
jd.bodyA = bodyA;
jd.bodyB = bodyB;
jd.localAnchorA = anchorA;
jd.localAnchorB = anchorB;

// Max length of joint = current distance between bodies * sag
float32 ropeLength = (bodyA->GetWorldPoint(anchorA) – bodyB->GetWorldPoint(anchorB)).Length() * sag;
jd.maxLength = ropeLength;

// Create joint
b2RopeJoint *ropeJoint = (b2RopeJoint *)world->CreateJoint(&jd);

VRope *newRope = [[VRope alloc] initWithRopeJoint:ropeJoint spriteSheet:ropeSpriteSheet];

[ropes addObject:newRope];
[newRope release];
}

现在内容变得更有趣了!

首先,你创造了一个b2RopeJointDef并使用所提供的局部定位点附加了两个实体到方法中。例如,附加绳索到你在坐标轴(0,0)上通过的实体中心位置。

然后,你估算了特定局部定位点上两个实体间的当前距离,并将其乘以一个“下垂”数值。这一下垂数值将大于1,并将决定绳子会变得多长。举个例子来说吧,如果两个实体距离10米,那么下垂值为1.1,绳索的最大长度便是11米。

然后你将创造b2RopeJoint并将其发送到你所创造的VRope初始方法。新的绳索对象将保存在一个数组中(所以你能在之后更新它)。

这便是你添加一些绳索所需要做的事。现在在init(创造了所有实体和绳索,包括美味的菠萝)下添加一些新方法:

#define cc_to_b2Vec(x,y)   (b2Vec2((x)/PTM_RATIO, (y)/PTM_RATIO))

-(void) initLevel
{
CGSize s = [[CCDirector sharedDirector] winSize];

// Add the candy
b2Body *body1 = [self createCandyAt:CGPointMake(s.width * 0.5, s.height * 0.7)];

// Add a bunch of ropes
[self createRopeWithBodyA:groundBody anchorA:cc_to_b2Vec(s.width * 0.15, s.height * 0.8)
bodyB:body1 anchorB:body1->GetLocalCenter()
sag:1.1];

[self createRopeWithBodyA:body1 anchorA:body1->GetLocalCenter()
bodyB:groundBody anchorB:cc_to_b2Vec(s.width * 0.85, s.height * 0.8)
sag:1.1];

[self createRopeWithBodyA:body1 anchorA:body1->GetLocalCenter()
bodyB:groundBody anchorB:cc_to_b2Vec(s.width * 0.83, s.height * 0.6)
sag:1.1];
}

这是一个非常简单的方法,但是需要注意一件重要的内容:坐标轴空间的差别。为了做到更加边界,我们想要使用屏幕坐标轴去放置绳索和菠萝,但是你的createRope方法将定位点设置在局部实体坐标轴上,而这些内容必须在Box2D的世界坐标轴上进行详细说明,而不是在屏幕坐标轴上。

为了避免这点,你创造了一个宏指令,cc_to_b2Vec,它将转换屏幕坐标轴到世界坐标轴。

现在你只需要添加一个调用到init的initLevel,即在initPhysics调用之后:

[self initLevel];

编译并运行,看看你所创造的结果。应该如下图所示:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

可能这还不是你真正期待的。问题在于你忘了基于Box2D世界模拟更新精灵。你同样也忘记调用绳索的更新方法。

所以前往HelloWorldLayer.mm的update:并在最后添加:

//Iterate over the bodies in the physics world
for (b2Body* b = world->GetBodyList(); b; b = b->GetNext())
{
CCSprite *myActor = (CCSprite*)b->GetUserData();
if (myActor)
{
//Synchronize the AtlasSprites position and rotation with the corresponding body
myActor.position = CGPointMake( b->GetPosition().x * PTM_RATIO, b->GetPosition().y * PTM_RATIO);
myActor.rotation = -1 * CC_RADIANS_TO_DEGREES(b->GetAngle());
}
}

// Update all the ropes
for (VRope *rope in ropes)
{
[rope update:dt];
[rope updateSprites];
}

再次编译并运行,这便是你所创造的内容:

CutTheVerlet(from raywenderlich)

CutTheVerlet(from raywenderlich)

很棒,你创造了很好看的绳索!

你也许会注意到,当应用开始时,绳索会出现较大的范围的移动,这看起来会有点奇怪。之所以会出现这样的情况是因为当VRope对象被创造出来是,它是先呈现出直线的样式,然后根据游戏的物理原理,模拟会让绳索下降。下垂的绳索是基于物理原理而运行。

为了解决这一情况,你可以在最初的场景布局时适时向前跳跃。添加如下内容到initLevel最后:

// Advance the world by a few seconds to stabilize everything.
int n = 10 * 60;
int32 velocityIterations = 8;
int32 positionIterations = 1;
float32 dt = 1.0 / 60.0;
while (n–)
{
// Instruct the world to perform a single step of simulation.
world->Step(dt, velocityIterations, positionIterations);
for (VRope *rope in ropes)
{
[rope update:dt];
}
}

// This last update takes care of the texture repositioning.
[self update:dt];

再次创建并运行,你应该不会再看到弹跳的绳索了。相反的你会看到一个伴随着饥饿的鳄鱼的安静场景,绳索在请求被割断前会保持不动。

最后

这便是本篇教程的全部内容。而在下一篇也是最后一部分教程中,你将学习如何切断verlet并喂养鳄鱼。

本文为游戏邦/gamerboom.com编译,拒绝任何不保留版权的转载,如需转载请联系:游戏邦

How to Make a Game Like Cut the Rope – Part 1

Gustavo Ambrozio

In this tutorial series, you’ll build a cool Cut the Rope-type game from scratch using Cocos2D and Box2D.

You’ll use art created by Ray’s lovely and talented wife Vicki to build a very rudimentary Cut the Rope, mainly to show how to create and destroy ropes using Box2D.

In this 2-part tutorial series, you’ll learn:

How to use rope joints

How to draw ropes using verlets

How to cut the ropes (of course)

And much, much more!

This tutorial series assumes that you’ve already gone through the Intro to Box2D with Cocos2D Tutorial: Bouncing Balls, or have equivalent knowledge. It also uses a bunch of concepts found in another great tutorial series by Ray, How To Create A Breakout Game with Box2D and Cocos2D – Part 1 and Part 2.

Getting Started

In this tutorial, you’ll use the latest version of Xcode (4.3.x at the time of writing) and version 2.0 of Cocos2D. We’re using 2.0 because it contains a new joint in Box2D that we need (the rope joint), which 1.0.1 does not have.

If you’re still using Cocos2D 1.0.1 and are afraid that your current templates will be replaced, don’t worry. The templates for Cocos2D version 2 are installed alongside the templates for version 1.0.1, so there’s no problem installing them alongside each other.

Once you have version 2.0 of Cocos2D installed, fire up Xcode and click “Create a new Xcode project.” Under iOS, choose Cocos2D v2.x, choose the “Cocos2D iOS with Box2D” template and click Next.

On the next screen, give your project the name “CutTheVerlet” and fill out your company identifier. Don’t forget to choose “iPhone” as the device family.

In the next step, select the folder where you want to store your project. You don’t have to create a new folder – Xcode will create one for you. Click “Create” and you’re ready to start coding!

You probably know what this template project is all about, but just for the fun of it, click Run and see what it does:

You can play with a few blocks. This is cool, but not nearly as cool as what you’re about to make!

Follow Along on GitHub
If you want to follow me wherever I may go, check out this project’s GitHub page!

I’ll publish the entire tutorial, with comments and tags for every step on GitHub!

You might find this handy in case you get stuck somewhere or want to look at the code at a particular stage instead of typing it out yourself. Otherwise, keep following along here!

The repository tag for this point in the tutorial is ProjectTemplate. You can download the project zip at this state here.

Cleaning Up

Before you get your hands dirty, time for some cleanup. :]

Open HelloWorldLayer.h and remove the declaration of the spriteTexture_ instance variable:

Open HelloWorldLayer.mm and completely remove the implementations of the following three methods:

1.-(void) addNewSpriteAtPosition:(CGPoint)p;
2.-(void) createMenu;
3.-(void) ccTouchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
The first two methods also have declarations at the top of the file. Make sure you remove those as well.

Now you’ll have a couple of warnings on the init method in this file, because of calls to createMenu and addNewSpriteAtPosition:. Clean up the init method by removing those method calls.

Next, delete the line that enables the accelerometer, as you won’t be using it in this project. Also remove the line that grabs the window size, as you won’t need it, at least not now:

At the end of the init method, remove everything that adds stuff to the scene:

That’s it for HelloWorld.mm!

Open the resources group on the project navigator and delete blocks.png, since you don’t need it. When asked if you want to remove the reference only or move to trash, don’t be shy. Click “Move to Trash” – you don’t want any unnecessary files hanging around.

To make sure everything works, compile and run. You should see an empty scene:

The repository tag for this point in the tutorial is CleanedUpProject.

Adding Some Sprites

Now that you’ve tossed out what you don’t need, you can add some things you do need, starting with the art. I got most of these images from Vicki’s site, and she made a few additional sprites just for this tutorial. Go ahead and download the art files from GitHub.

Extract the ZIP file contents. This will create a new folder called “Art.” Inside this folder are two more folders: Images and Originals. The Originals folder has all the original PNG files, in retina resolution.

You won’t use these original files directly in the project. As you may know, the recommended approach for using sprites in Cocos2D is to work with a sprite sheet created with a program like TexturePacker.

Note: If you want to know more about how to use TexturePacker, there’s an excellent tutorial – guess where? Yep, right here on raywenderlich.com, in the aptly-named “How to Create and Optimize Sprite Sheets in Cocos2D with Texture Packer and Pixel Formats“.

But in this case, there’s no need to make a sprite sheet – I’ve already made one for you! :] They’re in the ZIP you just downloaded, in the Images subdirectory.

Moving on then. The Images folder inside the Art folder has everything you need to add to the project, so go ahead and drag this folder to the Resources group of your project.

Be sure to check the “Copy items into destination group’s folder” to copy these files to your project folder.

You now have the elements you need to begin building your scene. You’re going to make the initial scene look like this:

To do this, first you need to add a reference to the crocodile sprite you’ll be using in your scene. So add the following line to HelloWorldLayer.h below the existing declaration for m_debugDraw:

Then go to HelloWorldLayer.mm and insert the code below right before the initPhysics call in the init method:

The first line loads all your sprites and holds them in the sprite sheet frame cache. You then add the background and the croc. Build and run to see whether it looks the way you want it to:

Oops, not exactly! The scene is designed for portrait mode, but the code is forcing it to run in landscape.

There’s an easy fix for this. Open AppDelegate.mm, find shouldAutorotateToInterfaceOrientation:, and change the implementation to this:

Simple so far, right? Now comes the hard part: the rope.

The repository tag for this point in the tutorial is BasicScene.

What’s This Verlet You Speak Of?
So, how are you going to implement the rope using Cocos2D and Box2D, you ask?

Out of the box, the only useful thing provided by Box2D is a joint called rope joint. A rope joint is a joint that can be thought of as a “maximum distance” joint. You specify two bodies and the maximum distance between them, and Box2D makes sure that these two bodies are never more than this distance apart, much like a real rope with something tied to each end.

This is very nice, but Cocos2D does not (yet) have any way to draw this rope. You could draw a straight line between the two bodies, but this won’t depict a realistic rope in all scenarios. For instance, if the distance between the objects is less than the maximum distance (the rope length in your case), the rope should be sagging. But if you were to draw a straight line between the two points, obviously it would not sag, so it would not look realistic.

To achieve this sagging effect, I found a very nice class called VRope that PatrickC of Clever Hamster Games wrote about in the Cocos2D forums, based on code written in ActionScript at YoAmbulante.com. Thanks to both of those great sites for your contributions – you made my life easier. :]

To simulate a rope, the VRope class uses the concept of Verlet integration. As YouAmbulante.com explains:

The Verlet integration consists in dots and links between these dots where each dot has remembered which was its previous position to determine its next step, the new position of x is equal to x + x – previous_x (same for y) and then each of these dots are associated (grouped) by pairs that try to keep same distance between each other as they were when the program started.I’ll explain in more detail how this is implemented in the VRope class, but first add a simple rope to your project to see it in action.

Download the original VRope class code from PatrickC here. Extract and drag the vrope-latest folder to your project tree:

Again, choose to copy the items into the destination folder:

If you compile the code now, you’ll get two errors and one warning. Fix those before you go any farther.

The errors are in VRope.mm‘s debugDraw method. This class was built to be used with version 1.0.1 of Cocos2D, which supports OpenGL ES 1.0. Cocos2D 2.x uses OpenGL ES 2.0, and some of the older Cocos2D 1.x functions are not forward-compatible. Since you won’t use debugDraw, just remove it. Don’t forget to also remove the method declaration in VRope.h.

The warning comes from a deprecated Cocos2D method that’s called from createRope:pointB:. This is the offending line that should be in yellow in Xcode:

Replace it with:

Now the project will compile with no errors or warnings.

You’ll have to modify the VRope class a little to suit your game’s needs. The original class keeps track of two Box2D bodies and simulates the rope based on the movement of these bodies. But it determines the rope length based on the distance between the two objects at the time they were created. This could result in an incorrect rope length in your game, as the rope might not be tight when you create the VRope instance.

You’ll modify the class to use an instance of b2RopeJoint (discussed at the beginning of this section), which already has two bodies attached to it. It also has information about the length of the rope, taking into consideration the anchor points where the b2RopeJoint attaches to the two bodies.

In other words, you say where the rope is attached, and how long it is, and the rope will automatically calculate the appropriate amount of “sagging” necessary to meet those constraints.

So, open VRope.h and replace these lines:

With this:

Also, change the init method declaration from:

To:

This will make it easier to create a visual representation of a rope joint by passing in the b2RopeJoint directly.

Next change the createRope method from:

To:

This allows us to set the length of the rope.

Now, go to VRope.mm and replace the existing implementation for init:body2:spriteSheet: with:

Here we pull out the two bodies from the b2RopeJoint and call the previous initializer.

Also replace the existing reset and update implementations to use the rope joint instead of the bodies, as follows:

Finally, createRope needs to be updated to use the distance you provide instead of the current distance between the two points. So, just change the method declaration from:

To:

And remove this line from the method:

You also need to update an old call to createRope inside initWithPoints:pointB:spriteSheet:. Go to this method and change the line:

To:

This should be enough for now. But before you get to making your rope, you need to add a little more infrastructure to handle the ropes and the candies.

Candy for Crocs
Your game may have a crocodile instead of the Om Nom monster, but you still have to feed him! And that means candy, which in this project comes in the form of pineapple. (What else to crocodiles eat?!)

The arrays will help you keep track of the ropes and candies you’ll create. The groundBody instance variable will hold a reference to the world body, and ropeSpriteSheet will hold the sprite batch node that will be used in the creation of the VRope instances.

Now, open HelloWorldLayer.mm and add these lines to init right after self.isTouchEnabled = YES;:

First, add a few more instance variables to HelloWorldLayer.h:

Just so you don’t forget, add these lines to dealloc before the call to super dealloc:

You need to keep a reference to the world body created in initPhysics. So, go to that method and change:

To:

Now, add this method to the end of the file (but before the final @end):

This method will create a pineapple body and its sprite at a particular position on the screen. It uses a polygon as the fixture for the body. The points for the fixture were taken using VertexHelper. You can see how to use VertexHelper in the How To Use Box2D For Just Collision Detection with Cocos2D iPhone tutorial. It’s pretty basic Cocos2D+Box2D stuff. :]

And Now For Some Ropes!
It’s finally time to add the method to create the rope and attach two bodies to it. No, not that kind of body – this game is rated G! :]

First, add this import at the top of HelloWorldLayer.mm:

Now add this method to the end of the file:

Now things are getting a little more interesting!

First, you create a b2RopeJointDef and attach the two bodies passed into the method using the provided local anchor points. For example, to attach the rope to the center of a body you’d pass in coordinates (0,0).

Then, you calculate the current distance between the two bodies at the given local anchors and multiply by a “sag” value. This sag value should be always greater than 1 and will determine how much longer the rope will be. So, for example, if the two bodies are 10 meters apart and sag is 1.1, the rope will have a maximum length of 11 meters.

You then create the b2RopeJoint and send it to the init method of VRope that you created above. The new rope object is kept in an array (so you can update it later) and returned.

That’s all you need to add some ropes! Now add a new method below init that creates all the bodies and ropes (including a delectable pineapple):

This is a pretty simple method, but notice one important thing: the difference in coordinate space. To make life easier, you want to use screen coordinates to place the ropes and pineapples, but your createRope method takes anchor points in local body coordinates, and those have to be specified in Box2D’s world coordinates, not as screen coordinates.

To get around this, you create a macro, cc_to_b2Vec, which translates screen coordinates to world coordinates.

Now you just need to add a call to initLevel in init, right after the call to initPhysics:

Compile and run and let’s see what you get. :] It should be something like:

Not exactly what you expected… The problem is that you forgot to update your sprites based on the Box2D world simulation. You also forgot to call the rope’s update method. (If you remembered these things and already did them, good for you!)

So, go to update: in HelloWorldLayer.mm and add this at the end:

Compile and run again, and this is what you should get:

Great, what a nice looking rope you’ve got!

You might notice, though, that the ropes move a lot when the app starts, and it looks a little weird. This happens because when a VRope object is created, it’s first laid out as a straight line, and then the simulation makes the rope fall, according to the game physics. The wobbling rope is physics working as it should!

To solve this, you can jump forward in time when you first lay out the scene. Add these lines to the end of initLevel:

Build and run again, and you should see no more bouncing rope. Instead you should have a tranquil scene with a hungry crocodile, and ropes so still they’re begging to be cut!

The repository tag for this point in the tutorial is BasicRopes.

Where to Go From Here?
That’s it for today. In the next and last part of the tutorial, you’ll learn how to cut the verlet and feed the croc.

In the meantime, if you have any questions about this part of the tutorial, join in the forum discussion below!(source:raywenderlich


上一篇:

下一篇: