这次我们来使用Box2d框架制作具有一定弹性的物体。这里我们说的弹性物体,不是指物体碰撞到其他物体后反弹的这种特性,而是指物体的外形在受到外力作用的时候会发生形变,比如我们小时候玩儿的弹力球。那么这里就有一个有意思的地方,我们知道在Box2d中,所有的物体都是“刚体”,刚体就是不会发生弹性形变的物体,那么不是矛盾了么?所以这里我们所得具有弹性的物体,实际上并不是一个物体,而是一系列刚体组成的一个物体。
好了,不多说废话了,下面就开始制作。
我们先看一下运行的效果截图:
我们要制作的就是这种具有一定弹性的环形物体(或者说球状物体)。
原理比较简单,我们可以看到,图中的环形物体实际上是由很多个小的梯形连接组成的,相邻的两个梯形物体通过关节进行连接,如下图所示:
相邻的两个梯形物体具有两个相邻的顶点,这两个顶点都使用具有固定作用的关节进行连接才能使两个物体形成一定的弹力效果,假如上图中我们去掉上面或者下面的关节,那么只有一个关节存在的时候,可以想象整个环形物体就会像下面这样松垮掉:
说道“具有固定作用的关节”,我指的就是能够将两个关节上的两个点固定在一起的关节,例如b2RevoluteJoint,b2WeldJoint,b2DistanceJoint。我们利用相邻的物体要脱离关节的约束时关节产生的反作用力,来体现出弹性的效果。
既然上面的三种约束都有固定的作用,我们分别试一下看看有什么效果。首先来看b2DistanceJoint的效果:
截图中我们看到下落的时候还好(其实在运行的时候,下落过程每个梯形物体都会发生剧烈抖动,感觉整个环要散开的样子),下落到地面后,就坍塌在了一起。这个现象其实比较容易理解,虽然b2DistanceJoint限制了两个相邻的梯形的相邻顶点的距离,但是并没有对彼此有任何自由度(旋转,平移)的限制,因此在发生抖动的时候,就没有任何规律最后塌陷在一起的时候,两边的梯形同时也产生了折叠的效果(折叠之后依然满足距离的限制)。
接着我们再来看b2WeldJoint:
可以看到效果要比b2DistanceJoint好很多,之后在碰撞的过程中,相邻的梯形之间会产生微小的毛边。尽管b2WeldJoint限制了物体的两个自由度,但是由于受力仍然会导致焊接有一些脱落。
最后的b2RevoluteJoint的效果就和我们最开始的截图一样,在收到剧烈碰撞的时候,当整个环被压得比较扁的时候,两侧的相邻梯形会有折叠的效果,但是很快就会恢复:
比较三种效果,我更倾向于使用b2RevoluteJoint,当环的段数越多的时候,最后一种的效果越平滑。
下面我们给出具体的代码实现。
Box2d的版本是2.3.1,关于创建工程等初始化的步骤这里就不再提了(之前的Box2d的博文中反复提过了),我们在HelloWorldLayer中添加下面的方法:
-(void)createSoftObject:(int) edgeCount center:(CGPoint) center {
//物体的中心点
b2Vec2 centerVec = [self toVec2:center];
//根据多边形的边数来计算每一条边对应的圆心角的大小
//然后我们根据圆心角的正弦和余弦来计算这一圈物体的顶点坐标
float angleBase = 360.0f / edgeCount;
//相邻的两个物体之间需要创建关节,因此用previousBody来记录前一个物体
b2Body* previousBody = nil;
//最后要把第一个物体和最后一个物体用关节连接,因此要保存第一个物体
b2Body* firstBody = nil;
//环的外半径
float r1 = 60;
//环的内半径
float r2 = r1 - 5;
for (int i = 0; i < edgeCount; i++) {
//计算第i个梯形物体的4个顶点的坐标
b2Vec2* vertexes = [selfcalculateVertexesWithIndex:i angleBase:angleBase edgeCount:edgeCountradiusOutside:r1 radiusInside:r2];
//两个用来创建关节的顶点的世界坐标
b2Vec2 revoluteJointPoint(centerVec.x +vertexes[0].x, centerVec.y + vertexes[0].y);
b2Vec2 revoluteJointPoint1(centerVec.x+ vertexes[1].x, centerVec.y + vertexes[1].y);
//定义物体
b2BodyDef bodyDef;
bodyDef.position = [selftoVec2:center];
bodyDef.type = b2_dynamicBody;
b2Body* body =world->CreateBody(&bodyDef);
//创建形状
b2PolygonShape* shape = newb2PolygonShape();
shape->Set(vertexes, 4);
//创建fixture
b2FixtureDef fixtureDef;
fixtureDef.density = 1;
fixtureDef.friction = 0.3f;
fixtureDef.shape = shape;
body->CreateFixture(&fixtureDef);
//如果前一个物体为nil,则说明是第一个物体,将其记录,如果不为nil,则将当前物体与之相连
if (previousBody != nil) {
[selfcreateRevoluteJoint:previousBody anotherBody:bodyjointPoint:revoluteJointPoint];
[selfcreateRevoluteJoint:previousBody anotherBody:bodyjointPoint:revoluteJointPoint1];
} else {
firstBody = body;
}
previousBody = body;
}
//最后将第一个物体与最后一个物体连接起来
b2Vec2* vertexes = [selfcalculateVertexesWithIndex:0 angleBase:angleBase edgeCount:edgeCountradiusOutside:r1 radiusInside:r2];
b2Vec2 revoluteJointPoint(centerVec.x +vertexes[0].x, centerVec.y + vertexes[0].y);
b2Vec2 revoluteJointPoint1(centerVec.x +vertexes[1].x, centerVec.y + vertexes[1].y);
[self createRevoluteJoint:previousBodyanotherBody:firstBody jointPoint:revoluteJointPoint];
[self createRevoluteJoint:previousBodyanotherBody:firstBody jointPoint:revoluteJointPoint1];
}
上面的方法用来创建环形物体,算法很简单,通过内半径,外半径以及圆心角来计算每个梯形物体的4各点的坐标,然后创建物体和关节,方法中已经做了详细的注释。上面的方法中涉及到的两个方法如下:
-(b2Vec2*)calculateVertexesWithIndex:(int) index
angleBase:(float)angleBase
edgeCount:(int)edgeCount
radiusOutside:(float)r1
radiusInside:(float)r2 {
b2Vec2* vertexes = new b2Vec2[4];
float angle1 =CC_DEGREES_TO_RADIANS(angleBase * index);
float angle2 =CC_DEGREES_TO_RADIANS(angleBase * ((index+1)%edgeCount));
float x1 = sinf(angle1) * r1;
float y1 = cosf(angle1) * r1;
vertexes[0] = [self toVec2:ccp(x1, y1)];
float x2 = sinf(angle1) * r2;
float y2 = cosf(angle1) * r2;
vertexes[1] = [self toVec2:ccp(x2, y2)];
float x3 = sinf(angle2) * r2;
float y3 = cosf(angle2) * r2;
vertexes[2] = [self toVec2:ccp(x3, y3)];
float x4 = sinf(angle2) * r1;
float y4 = cosf(angle2) * r1;
vertexes[3] = [self toVec2:ccp(x4, y4)];
return vertexes;
}
-(void)createRevoluteJoint:(b2Body*) body1 anotherBody:(b2Body*) body2jointPoint:(b2Vec2) jointPoint {
b2RevoluteJointDef jointDef;
jointDef.Initialize(body1, body2,jointPoint);
world->CreateJoint(&jointDef);
}
最后在这些代码都添加好之后,我们利用默认的addNewSpriteAtPosition方法,将其清空,替换为下面的代码即可:
[selfcreateSoftObject:32 center:p];
编译,运行,当我们点击屏幕的时候,就能以点击的位置为中心创建具有弹性的环状物体了。
注:还有一个bug尚未解决,就是环形物体之间有可能出现穿插的现象,如果有感兴趣的朋友欢迎一起研究~~