因为是要模拟物理效果,所以创建工程的时候使用cocos2d ios with Box2D模板。接着,准备一个用来作为绳子片段的图片,例如:

rope.png:(4px×2px的一个橙色小方块,如果你想要带有样式的绳子,可以用PS简单制作),注意,纹理的长宽一定要是2的幂指数(因为我们要用到平铺纹理)。

将rope.png导入工程的resources中。

在模拟的时候,实际上是将绳子切分为很多个小线段来处理的,当分段足够细足够多时,绳子的模拟效果就会足够平滑。

为了模拟绳子受力效果,我们为每一个小线段定义节点,然后对这些点施加力(或者说模拟这些点的位移)来模拟绳子的运动效果,同时,我们用这些点来计算和限制每个小线段的移动,防止整个绳子松散掉。

在下面的说明中,我们对于每一部分先给出一些代码,然后做一下解释。

首先按照设计,我们需要定义三个类(都继承NSObject类),一个是Rope类,不必多说,就是我们的“绳子”类,还有一个是Segment类,定义了用来细分绳子的小线段,最后一个是Vertex类,即前面所说的节点。

首先来看一下Rope类的声明:

#import"cocos2d.h"

#import"Vertex.h"

#import"Segment.h"

#import"Box2D.h"

@interfaceRope : NSObject {

NSMutableArray* vertexes;

NSMutableArray* segments;

NSMutableArray* segSprites;

CCSpriteBatchNode* ropeSegBatchNode;

b2Body* startVertex;

b2Body* endVertex;

int segmentCount;

float segmentConnectionRatio;

}

-(id)initWithStart:(b2Body*) startVertex end:(b2Body*) endVertexsegBatchNode:(CCSpriteBatchNode*) ropeSegBatchNode;

-(void)updateSegments:(float)delta;

-(void)updateSegmentSprites;

-(void)createRope;

@end

头文件中包括了cocos2d和box2d的头文件,还有Vertex类和Segment类的头文件。在Rope类中定义了节点Vertex和小线段Segment类的数组,用来存储整个绳子中包含的节点和线段,此外,还定义了一个CCSprite对象的数组,因为每一个小线段最终还是要通过精灵来显示的。除了这些绳子的组成部分,CCSpriteBatchNode成员用于共享纹理贴图(因为每一个Segment的CCSprite对象的纹理都是一样的,如果单个渲染效率代价太大)。startVertex和endVertex是两个box2d的刚体对象,用来模拟绳子的两个端点的物理效果。segmentCount用来存储绳子上的Segment个数(用于update循环),segmentConnectionRatio这个系数取值范围在0-0.1之间,我们知道Segment之间通过节点进行连接,如果节点定义在片段的边缘,那么两个片段在成一定角度的时候容易产生锯齿或者不连续的效果,因此将连接的节点向Segment内部移动一定的比例,保证其连续和平滑效果,segmentConnectionRatio就定义了这个比例,参考下面的图片:

在Rope类中定义了4个方法,initWithStart:end:segBatchNode:方法为初始化方法,通过传入起点和终点的刚体对象来做初始化工作。createRope创建绳子(包括Segments,Vertexes和CCSprite对象),updateSegments用来计算和更新线段的位置和节点的位置,updateSegmentSprites用来重新刷新每个线段对应的精灵的位置和旋转,实现绳子的动画效果。

这里我们先不给出实现,我们先给出Vertex和Segment类的声明和定义。

Vertex.h:

@interfaceVertex : NSObject {

float oldX;

float oldY;

}

@propertyfloat x;

@propertyfloat y;

-(void)setX:(float)xPos andY:(float)yPos;

-(void)updatePos;

-(void)applyGravity:(float) delta;

-(CGPoint)toCGPoint;

@end

类中声明了节点的位置x和y,另外还定义了前一次计算的位置oldX和oldY(oldX和oldY的用途在类定义的注释中会解释)。setX:andY方法用来更新x和y的值,updatePos方法用于根据oldX和oldY来修正x和y的位置(方法实现中有注释)。applyGravity方法对节点施加一个位移来模拟重力对绳子的影响。toCGPoint方法将Vertex类转换为CGPoint结构。

下面是Vertex的定义(Vertex.mm):

#import"Vertex.h"

@implementationVertex

@synthesize x;

@synthesize y;

-(void)setX:(float)xPos andY:(float)yPos {

oldX = x = xPos;

oldY = y = yPos;

}

-(void)updatePos {

//之所以要记录旧的位置,是因为每次调用updatePos方法

//更新位置之后,还会通过多次迭代计算来再次修正每个节点

//的位置(因为每个线段不能够被拉伸,而节点在重力和其他

//线段的影响下会致使线段拉伸,因此要做修正),这些修正

//并没有立即生效,而在下一次调用Rope的updateSegments

//方法更新时又会覆盖掉节点的位置调整,因此,需要记录旧

//的位置,并将之前的调整得到的差值反馈到节点位置上。

float tmpX = x;

float tmpY = y;

x += x - oldX;

y += y - oldY;

oldX = tmpX;

oldY = tmpY;

}

-(void)applyGravity:(float)delta {

y -= delta * 10.0; //10.0是重力的系数,如果要模拟轻绳子,可以适当减小这个系数,建议在5.0-10.0之间

}

-(CGPoint)toCGPoint {

return CGPointMake(x, y);

}

@end

下面是Segment的声明(Segment.h):

#import"Vertex.h"

@interfaceSegment : NSObject {

float length;

}

@propertyVertex* startVertex;

@propertyVertex* endVertex;

-(id)initWithStartVertex:(Vertex*) start andEndVertex:(Vertex*) end;

-(void)adjustVertexPosition;

@end

length为线段的初始长度,也是线段的不可变长度,根据这个长度来对线段的两端节点进行修正。startVertex和endVertex为两端节点。adjustVertexPosition用来修正节点。

Segment类定义(Segment.mm):

#import"Segment.h"

#import"cocos2d.h"

@implementationSegment

@synthesizestartVertex;

@synthesizeendVertex;

-(id)initWithStartVertex:(Vertex *)start andEndVertex:(Vertex *)end {

if (self = [super init]) {

startVertex = start;

endVertex = end;

//记录线段的初始长度

length = ccpDistance([start toCGPoint],[end toCGPoint]);

}

return self;

}

-(void)adjustVertexPosition {

//x和y方向的距离,用于后面求微调值时计算比例关系

float xLength = endVertex.x -startVertex.x;

float yLength = endVertex.y -startVertex.y;

//实际现在线段的长度(这个长度和初始值会有差异,所以需要修正)

float actualLength =ccpDistance([startVertex toCGPoint], [endVertex toCGPoint]);

//需要修正的长度

float adjustment = length - actualLength;

//通过三角形相似性来计算x和y方向上的修正长度,乘以0.5是因为起点和终点分别修正一半

float xAdjustment = adjustment * xLength *0.5 / actualLength;

float yAdjustment = adjustment * yLength *0.5 / actualLength;

//修正起点和终点位置

startVertex.x -= xAdjustment;

startVertex.y -= yAdjustment;

endVertex.x += xAdjustment;

endVertex.y += yAdjustment;

}

@end

初始化方法中通过两个节点计算出线段的长度。adjustVertexPosition中对各部分代码都做了详细的注释,这里不多解释了。

接着我们来看Rope类的定义。

Rope类的初始化方法定义如下:

-(id)initWithStart:(b2Body *)startVertex end:(b2Body *)endVertexsegBatchNode:(CCSpriteBatchNode *)ropeSegBatchNode {

if (self = [super init]) {

self->vertexes = [[NSMutableArrayalloc] init];

self->segments = [[NSMutableArrayalloc] init];

self->segSprites = [[NSMutableArrayalloc] init];

self->startVertex = startVertex;

self->endVertex = endVertex;

self->ropeSegBatchNode =ropeSegBatchNode;

[self createRope];

}

return self;

}

方法初始化了数组元素,将传入的两个端点的刚体对象赋给类的属性,传入纹理参数,调用createRope方法来初始化绳子中的元素(节点,线段,精灵等等)。

createRope方法:

-(void)createRope {

//获取两个端点的位置

b2Vec2 startVec =startVertex->GetPosition();

b2Vec2 endVec =endVertex->GetPosition();

CGPoint startPos = ccp(startVec.x *PTM_RATIO, startVec.y * PTM_RATIO);

CGPoint endPos = ccp(endVec.x * PTM_RATIO,endVec.y * PTM_RATIO);

//计算绳子长度

float totalLength = ccpDistance(startPos,endPos);

//定义每个线段的长度,减小这个值可以更平滑,但是太小容易导致不连续的效果

float segmentLength = 8;

//计算线段总个数

segmentCount = totalLength / segmentLength;

//计算表示绳子方向的基向量

CGPoint directionVector =ccpNormalize(ccpSub(endPos, startPos));

//定义线段连接处的缩进值(0-0.1之间)

segmentConnectionRatio = 0.1f;

//计算所有的节点的位置并加入数组

for (int i = 0; i < segmentCount + 1;i++) {

//通过起点和计数器来计算第i个节点的位置

CGPoint vPos = ccpAdd(startPos,ccpMult(directionVector, segmentLength * i * (1 - segmentConnectionRatio)));

Vertex* vertex = [[Vertex alloc] init];

[vertex setX:vPos.x andY:vPos.y];

[vertexes addObject:vertex];

}

//初始化所有的线段并加入数组

for (int i = 0; i < segmentCount; i++) {

Segment* seg = [[Segment alloc]initWithStartVertex:[vertexes objectAtIndex:i] andEndVertex:[vertexesobjectAtIndex:i+1]];

[segments addObject:seg];

}

//初始化精灵数组

if (ropeSegBatchNode != nil) {

for (int i = 0; i < segmentCount;i++) {

Segment* seg = [segmentsobjectAtIndex:i];

CGPoint startPoint =[seg.startVertex toCGPoint];

CGPoint endPoint = [seg.endVertextoCGPoint];

//初始化精灵

CCSprite* segSprite = [CCSpritespriteWithTexture:ropeSegBatchNode.texture rect:CGRectMake(0, 0, segmentLength,[[[ropeSegBatchNode textureAtlas] texture] pixelsHigh])];

//线段方向向量

CGPoint directionVector =ccpSub(startPoint, endPoint);

//线段的角度

float segmentAngle =ccpToAngle(directionVector);

//设置重复纹理

ccTexParams param = { GL_LINEAR,GL_LINEAR, GL_REPEAT, GL_REPEAT };

[segSprite.texturesetTexParameters:&param];

//设置线段的位置和角度

[segSpritesetPosition:ccpMidpoint(startPoint, endPoint)];

[segSpritesetRotation:-CC_RADIANS_TO_DEGREES(segmentAngle)];

[ropeSegBatchNodeaddChild:segSprite];

[segSprites addObject:segSprite];

}

}

}

方法计算了线段的个数,初始化了节点、线段和精灵数组。方法中做了详细的注释,这里不过多的做解释了。

创建完成Rope之后,接下来我们来看更新绳子运动模拟的方法:

-(void)updateSegments:(float)delta {

//获取两个端点的位置

b2Vec2 startVec =startVertex->GetPosition();

b2Vec2 endVec =endVertex->GetPosition();

CGPoint startPos = ccp(startVec.x *PTM_RATIO, startVec.y * PTM_RATIO);

CGPoint endPos = ccp(endVec.x * PTM_RATIO,endVec.y * PTM_RATIO);

//更新绳子两个端点的位置

[[vertexes objectAtIndex:0] setX:startPos.xandY:startPos.y];

[[vertexes objectAtIndex:segmentCount]setX:endPos.x andY:endPos.y];

//对绳子施加重力效果,更新绳子每个节点的位置

for (int i = 1; i < segmentCount; i++) {

Vertex* vertex = [vertexesobjectAtIndex:i];

[vertex applyGravity:delta];

[vertex updatePos];

}

//8次迭代(迭代次数越多,效率越低,动画越细腻)来修正线段节点的位置

int iterationCount = 8;

for (int i = 0; i < iterationCount; i++){

for (int j = 0; j < segmentCount;j++) {

[[segments objectAtIndex:j]adjustVertexPosition];

}

}

}

方法updateSegments首先更新两个端点的位置,然后对绳子施加重力,最后再对节点进行修正。这里注意,我们调用updateSegments方法实际上只是对虚拟的节点和线段做了调整,并没有更新精灵,所以此时绳子并没有表现出任何变化。updateSegmentSprites方法根据updateSegments中修正的节点的位置来更新精灵:

-(void)updateSegmentSprites {

if (ropeSegBatchNode != nil) {

for (int i = 0; i < segmentCount;i++) {

Segment* seg = [segmentsobjectAtIndex:i];

CGPoint startPoint =[seg.startVertex toCGPoint];

CGPoint endPoint = [seg.endVertextoCGPoint];

float angle =ccpToAngle(ccpSub(startPoint, endPoint));

CCSprite* sprite = [segSpritesobjectAtIndex:i];

[spritesetPosition:ccpMidpoint(startPoint, endPoint)];

[spritesetRotation:-CC_RADIANS_TO_DEGREES(angle)];

}

}

}

方法中对于每一个线段,根据两端节点的位置来更新精灵的位置和旋转,产生动画效果。

接着我们来修改主场景的层HelloWorldLayer,在HelloWorldLayer声明中添加三个成员:

CCSpriteBatchNode*segmentBatchNode;

b2Body*anchorPoint;

NSMutableArray*ropes;

其中,segmentBatchNode用于在Segment之间共享纹理贴图。anchorPoint为绳子固定的旋转轴(一个虚拟的节点),ropes为绳子数组。

在HelloWorldLayer的init方法中添加初始化的语句:

ropes =[[NSMutableArray alloc] init];

segmentBatchNode= [CCSpriteBatchNode batchNodeWithFile:@"rope.png" capacity:100];

[selfaddChild:segmentBatchNode];

初始化绳子数组和纹理对象。

在initPhysics(初始化物理模拟)方法中添加绳子的虚拟旋转轴anchorPoint的初始化语句:

b2BodyDefanchorPointDef;

anchorPointDef.position.Set(s.width* 0.5 / PTM_RATIO, s.height * 0.8 / PTM_RATIO);

anchorPoint =world->CreateBody(&anchorPointDef);

接着我们在addNewSpriteAtPosition(不同的Box2D框架的版本不同,该方法的名称可能不同,这里我们Box2D的版本为2.3.0,该方法用于向场景中添加刚体盒子)方法最后添加下面的代码:

b2RopeJointDefjointDef;

jointDef.bodyA= anchorPoint;

jointDef.bodyB= body;

jointDef.localAnchorA= b2Vec2(0, 0);

jointDef.localAnchorB= b2Vec2(0, 0);

jointDef.maxLength=(body->GetPosition() - anchorPoint->GetPosition()).Length();

world->CreateJoint(&jointDef);

Rope* rope =[[Rope alloc] initWithStart:anchorPoint end:bodysegBatchNode:segmentBatchNode];

[ropesaddObject:rope];

该方法定义了一个绳索关节,将盒子对象和绳子的旋转轴软连接在一起,然后在他们之间创建绳子对象。

最后,我们在update方法的最后添加下面的语句,更新所有添加进场景中的绳子:

for (Rope*rope in ropes) {

[rope updateSegments:dt];

}

for (Rope*rope in ropes) {

[rope updateSegmentSprites];

}

接着我们可以做一下测试,下面是运行效果:

我们可以增大绳子的长度,并且将盒子变为静态对象来看下效果:

大功告成,有问题欢迎留言讨论。

官网示例代码下载地址:http://www.cocoachina.com/bbs/job.php?action=download&aid=17114



带你一起分析cut the rope(切绳子游戏)中绳子的制作方法相关推荐

  1. 切水果游戏中的刀的实现

    在开发一个游戏时候,需要用到这样的效果.    首先定义     private Paint mPaint_weapon = null; // 真实的画笔        private Path mP ...

  2. c++自带的可持久化平衡树?rope大法好!(超详细解答 + 5道例题讲解,可直接替代可持久化的线段树、并查集、平衡树!)

    整理的算法模板合集: ACM模板 目录 c++自带的可持久化平衡树?rope大法好! 1. 声明 2. 支持操作 char类型的rope int类型的rope 3. 具体的细节 4. "可持 ...

  3. cut the rope HTML 5版本背后的开发故事

    译者注:Cut the Rope 是一款人见人爱的小游戏.有一个开发团队将它改造成了HTML5版本.想看看他们在改造过程中的经验之谈吗?那就看下面由开发人员自己写的文章吧~ 启示 在IE9中作为一个H ...

  4. 《Cut The Rope》 HTML 5版背后的开发故事

    译者注:Cut the Rope  是一款人见人爱的小游戏.有一个开发团队将它改造成了HTML5版本.想看看他们在改造过程中的经验之谈吗?那就看下面由开发人员自己写的文章吧! 启示 在IE9中作为一个 ...

  5. 【.NET程序性能分析】使用VS自带的工具分析.NET程序的性能

    这篇博文给大家分享的是,如何使用VS自带的性能分析工具来分析我们编写的.NET程序,一边找出程序性能的瓶颈,改善代码的质量.在实际开发中,性能真的很重要,往往决定一个产品的生死~良好的用户体验的基础之 ...

  6. shiro实现无状态的会话,带源码分析

    转载请在页首明显处注明作者与出处 朱小杰      http://www.cnblogs.com/zhuxiaojie/p/7809767.html 一:说明 在网上都找不到相关的信息,还是翻了大半天 ...

  7. Unity3D自带案例AngryBots分析(二)——人物动作控制逻辑

    Unity3D将自带demo放在C:\Users\Public\Documents\Unity Projects\4-0_AngryBots中,可以在open project中直接选择,或者从该目录中 ...

  8. BlueTooth: 蓝牙基带数据传输机理分析

    蓝牙基带数据传输机理分析 蓝牙(Bluetooth)是一种新型.开放.低成本.短距离的无线连接接技术,可取代短距离的电缆,实现话音和数据的无线传输.这种有效.廉价的无线连 接技术可以方便地将计算机及外 ...

  9. 蓝牙基带数据传输机理分析

    蓝牙基带数据传输机理分析 ZDNet 网络频道频道 更新时间: 2008-01-05 作者: 来源: cww 本文关键词:蓝牙 无线网络 蓝牙(Bluetooth)是一种新型.开放.低成本.短距离的无 ...

最新文章

  1. java.lang.OutOfMemoryError:GC overhead limit exceeded填坑心得
  2. php模拟一个简易的mvc模型
  3. Java里的堆(heap)栈(stack)和方法区(method)
  4. mysql分片库分页查询_mysql数据库分页查询优化
  5. 【心情】bjdldrz
  6. tab页签切换----bootstrap
  7. ES6 系列之我们来聊聊装饰器
  8. app上架oppo应用商店流程
  9. uni-app 支付宝小程序授权,获取用户基础信息(头像图片地址、昵称、性别、国家码、省份、所在市区)
  10. Unity 事件中心
  11. python手机编程调试_在Linux下调试Python代码的各种方法
  12. python爬取凤凰新闻网_python爬取凤凰网站的新闻,及其链接地址,来源,时间和内容,用selenium自动化和requests处理数据...
  13. 手把手教你学python第十三讲(MRO详解和神奇的魔法方法)
  14. Pivotal,天赋而成的云原生转型引导者
  15. Jmeter学习文档/使用
  16. Ubuntu下将python程序打包成可执行文件
  17. idea如何配置tomcat
  18. Mac下最专业的视频剪辑软件,FCPX视频降噪实用教学
  19. HDFS API 操作之文件下载、文件删除、文件名更改
  20. 刷脸支付时代的来临蜻蜓青蛙刷脸支付收银引领潮流

热门文章

  1. 【计算机科学基础】基于搜索引擎的信息检索
  2. SAM(segment anything model)分割一切 Demo测试及API调用
  3. 认知空间是什么意思_为什么很多女生都是“路痴”| 男女的空间认知有什么差异...
  4. 看懂了再说自己是程序员哈哈
  5. mysql 半同步 原理_MySQL半同步复制原理与配置详解
  6. VB编程:IF语句嵌套实例猜数小游戏-9
  7. B树,B+树,B*树以及R树的介绍
  8. String简单介绍
  9. 浏览器中优秀的收藏夹书签
  10. 100多个免费常用API接口分享,调用完全不限次数,以后总用得着!