对 verlet-rope-latest 做了一下扩展:
1。可以添加绳子端点的sprite
2。以 b2RopeJoint 对绳联体做最大距离限制
3。可以在body的fixture上面任取一点作为连接点(原版仅仅支持连接到物体的中心点),这个也是推动我做修改的初衷~
废话不多说:
上效果图:
上相关代码:
// // BYRope.h // HungryBear // // Created by Bruce Yang on 12-2-26. // Copyright (c) 2012年 EricGameStudio. All rights reserved. // #import <Foundation/Foundation.h> #import "cocos2d.h" #import "Box2D.h" #import "VPoint.h" #import "VStick.h" #import "GCfg.h" #import "GameConfig.h" @interface BYRope : NSObject { b2Body *_b1; b2Body *_b2;
b2Vec2 _p1Original; b2Vec2 _p2Original;
b2RopeJoint *_ropeJoint;
int _pointsCount; float _antiSagHack;
NSMutableArray *_vPoints; NSMutableArray *_vSticks; CCSprite *_spriteBar1; CCSprite *_spriteBar2; NSMutableArray *_segmentSprites;
CCSpriteBatchNode *_batchBar; CCSpriteBatchNode *_batchSegment;
/** * 用于标识使用何种 update 方法( 即根据调用构造方法的不同,调用不同的 update 方法)~ * true 表示绳子连接的两个点为两 body 的中心点~ * false 表示不以两 body 的中心点为连接点~ */ BOOL _isBodyCenter; } /** 1.简化调用的静态方法~ */ +(id) ropeWithBody1:(b2Body*)body1 body2:(b2Body*)body2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar; +(id) ropeWithBody1:(b2Body*)body1 body2:(b2Body*)body2 posi1:(b2Vec2)posi1 posi2:(b2Vec2)posi2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar; /** 2.初始化~ */ -(id) initWithBody1:(b2Body*)body1 body2:(b2Body*)body2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar; -(id) initWithBody1:(b2Body*)body1 body2:(b2Body*)body2 posi1:(b2Vec2)posi1 posi2:(b2Vec2)posi2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar; /** 3.重置~ */ -(void) reset; /** 4.逻辑更新:施加重力并更新 points 的位置、紧缩小棍儿~ */ -(void) update:(float)dt; // --> GameScene.tick~ Essential! /** * 5.更新 sprites 的位置 * (因为绳子的受力逻辑并不受 box2d 物理引擎掌控,没有一个所依附的 body,因此 sprite 的位置需手动来调整)~ */ -(void) updateSprites; // --> GameScene.draw~ Essential! /** 6.调试~ */ -(void) debugDraw; /** 7.清理,dealloc~ */ -(void) removeSprites; -(void) dealloc; @end |
// // BYRope.mm // HungryBear // // Created by Bruce Yang on 12-2-26. // Copyright (c) 2012年 EricGameStudio. All rights reserved. // #import "BYRope.h" @implementation BYRope #pragma mark- #pragma mark Quick Static Interface~ /** * 1.简化调用的静态方法~ */ +(id) ropeWithBody1:(b2Body*)body1 body2:(b2Body*)body2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar { return [[[self alloc] initWithBody1:body1 body2:body2 batchSegment:batchSegment batchBar:batchBar]autorelease]; } +(id) ropeWithBody1:(b2Body*)body1 body2:(b2Body*)body2 posi1:(b2Vec2)posi1 posi2:(b2Vec2)posi2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar { return [[[self alloc] initWithBody1:body1 body2:body2 posi1:posi1 posi2:posi2 batchSegment:batchSegment batchBar:batchBar]autorelease]; } #pragma mark- #pragma mark Init~ /** * 2.初始化~ */ -(void) createRope:(CGPoint)pointA pointB:(CGPoint)pointB { _vPoints = [[NSMutableArrayalloc]init]; _vSticks = [[NSMutableArrayalloc]init]; _segmentSprites = [[NSMutableArrayalloc]init]; float distance = ccpDistance(pointA, pointB);
// increase value to have less segments per rope, decrease to have more segments int segmentFactor =16;// 最好是能和绳子小节图片的长度相一致~
_pointsCount = distance / segmentFactor; CGPoint diffVector = ccpSub(pointB, pointA); float multiplier = distance / (_pointsCount -1);
// HACK: scale down rope points to cheat sag. set to 0 to disable, max suggested value 0.1 _antiSagHack =0.01f; for(int i =0; i <_pointsCount; ++ i) { CGPoint tmpVector =ccpAdd(pointA,ccpMult(ccpNormalize(diffVector),multiplier*i*(1-_antiSagHack))); VPoint *tmpPoint = [VPointvPoint]; [tmpPoint setPos:tmpVector.xy:tmpVector.y]; [_vPoints addObject:tmpPoint]; } for(int i =0; i <_pointsCount - 1; ++ i) { VStick *tmpStick = [VStickvStick:[_vPointsobjectAtIndex:i] pointb:[_vPointsobjectAtIndex:i +1]]; [_vSticks addObject:tmpStick]; } if(_batchSegment ==nil ||_batchBar ==nil) { // 多亏了这个,不然我又得找半天,真是瞎猫碰上死耗子了 printf("\nBYRope: _batchSegment == nil || _batchBar == nil\n"); } float segmentHeight = [[[_batchSegmenttextureAtlas]texture] pixelsHigh]/[GCfggetInstance].factor; for(int i = 0; i < _pointsCount - 1; ++ i) { VPoint *point1 = [[_vSticksobjectAtIndex:i]getPointA]; VPoint *point2 = [[_vSticksobjectAtIndex:i]getPointB]; CGPoint stickVector = ccpSub(ccp(point1.x, point1.y),ccp(point2.x, point2.y)); float stickAngle = ccpToAngle(stickVector);
CGRect rect = CGRectMake(0,0, multiplier, segmentHeight); CCSprite *tmpSprite = [CCSpritespriteWithBatchNode:_batchSegmentrect:rect]; ccTexParams params = { GL_LINEAR,GL_LINEAR, GL_REPEAT,GL_REPEAT }; [tmpSprite.texture setTexParameters:¶ms]; [tmpSprite setPosition:ccpMidpoint(ccp(point1.x, point1.y),ccp(point2.x, point2.y))]; [tmpSprite setRotation:-1 *CC_RADIANS_TO_DEGREES(stickAngle)]; [_batchSegment addChild:tmpSprite]; [_segmentSprites addObject:tmpSprite]; }
// 绳子端点的 sprite~ CGRect barRect = CGRectMake(0,0,8.0f, 8.0f); _spriteBar1 = [CCSpritespriteWithBatchNode:_batchBarrect:barRect]; _spriteBar2 = [CCSpritespriteWithBatchNode:_batchBarrect:barRect]; VPoint *firstPoint = (VPoint*)[_vPointsobjectAtIndex:0]; VPoint *lastPoint = (VPoint*)[_vPointsobjectAtIndex:(_pointsCount -1)]; [_spriteBar1 setPosition:ccp(firstPoint.x, firstPoint.y)]; [_spriteBar2 setPosition:ccp(lastPoint.x, lastPoint.y)]; [_batchBar addChild:_spriteBar1]; [_batchBar addChild:_spriteBar2]; } -(id) initWithBody1:(b2Body*)body1 body2:(b2Body*)body2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar { if((self = [superinit])) { _isBodyCenter = YES; _b1 = body1; _b2 = body2;
b2Vec2 p1 = body1->GetPosition(); b2Vec2 p2 = body2->GetPosition();
// 创建绳子关节限定两点间的最大距离~ b2RopeJointDef rjd; rjd.bodyA = body1; rjd.bodyB = body2; rjd.localAnchorA =b2Vec2_zero; rjd.localAnchorB =b2Vec2_zero; rjd.maxLength = (p2 - p1).Length(); _ropeJoint = (b2RopeJoint*)body1->GetWorld()->CreateJoint(&rjd);
CGPoint pointA = ccp(p1.x * PTM_RATIO, p1.y *PTM_RATIO); CGPoint pointB = ccp(p2.x * PTM_RATIO, p2.y*PTM_RATIO); _batchSegment = batchSegment; _batchBar = batchBar; [self createRope:pointA pointB:pointB]; } return self; } -(id) initWithBody1:(b2Body*)body1 body2:(b2Body*)body2 posi1:(b2Vec2)posi1 posi2:(b2Vec2)posi2 batchSegment:(CCSpriteBatchNode*)batchSegment batchBar:(CCSpriteBatchNode*)batchBar { if((self = [superinit])) { _isBodyCenter = NO; _b1 = body1; _b2 = body2;
// 创建绳子关节限定两点间的最大距离~ b2RopeJointDef rjd; rjd.bodyA = body1; rjd.bodyB = body2; rjd.localAnchorA = posi1 - body1->GetPosition(); rjd.localAnchorB = posi2 - body2->GetPosition(); rjd.maxLength = (posi2 - posi1).Length(); _ropeJoint = (b2RopeJoint*)body1->GetWorld()->CreateJoint(&rjd);
_p1Original = posi1 - body1->GetPosition(); _p2Original = posi2 - body2->GetPosition(); CGPoint pointA = ccp(posi1.x *PTM_RATIO, posi1.y *PTM_RATIO); CGPoint pointB = ccp(posi2.x *PTM_RATIO, posi2.y *PTM_RATIO); _batchSegment = batchSegment; _batchBar = batchBar; [self createRope:pointA pointB:pointB]; } return self; } #pragma mark- #pragma mark Reset~ /** 3.重置~ */ -(void) resetWithPoints:(CGPoint)pointA pointB:(CGPoint)pointB { float distance = ccpDistance(pointA, pointB); CGPoint diffVector = ccpSub(pointB, pointA); float multiplier = distance / (_pointsCount -1); for(int i =0; i <_pointsCount; ++ i) { CGPoint tmpVector =ccpAdd(pointA,ccpMult(ccpNormalize(diffVector), multiplier*i*(1-_antiSagHack))); VPoint *tmpPoint = [_vPointsobjectAtIndex:i]; [tmpPoint setPos:tmpVector.xy:tmpVector.y]; } } -(void) reset { CGPoint pointA; CGPoint pointB; if(_isBodyCenter ==YES) { pointA = ccp(_b1->GetPosition().x*PTM_RATIO,_b1->GetPosition().y*PTM_RATIO); pointB = ccp(_b2->GetPosition().x*PTM_RATIO,_b2->GetPosition().y*PTM_RATIO); } else { b2Vec2 vec1 = _b1->GetWorldPoint(_p1Original); b2Vec2 vec2 = _b2->GetWorldPoint(_p2Original); pointA = ccp(vec1.x *PTM_RATIO, vec1.y *PTM_RATIO); pointB = ccp(vec2.x *PTM_RATIO, vec2.y *PTM_RATIO); } [selfresetWithPoints:pointApointB:pointB]; } #pragma mark- #pragma mark Logic Update~ /** * 4.逻辑更新:施加重力并更新 points 的位置、紧缩小棍儿~ */ -(void) updateWithPoints:(CGPoint)pointA pointB:(CGPoint)pointB dt:(float)dt { // manually set position for first and last point of rope [[_vPoints objectAtIndex:0]setPos:pointA.xy:pointA.y]; [[_vPoints objectAtIndex:_pointsCount -1]setPos:pointB.xy:pointB.y]; // update points, apply gravity for(int i =1; i <_pointsCount - 1; ++ i) { [[_vPoints objectAtIndex:i] applyGravity:dt]; [[_vPointsobjectAtIndex:i]update]; } // contract sticks int iterations = 4; for(int j =0; j < iterations; ++ j) { for(int i =0; i <_pointsCount - 1; ++ i) { [[_vSticksobjectAtIndex:i]contract]; } } } -(void) update:(float)dt { CGPoint pointA; CGPoint pointB; if(_isBodyCenter ==YES) { pointA = ccp(_b1->GetPosition().x*PTM_RATIO,_b1->GetPosition().y*PTM_RATIO); pointB = ccp(_b2->GetPosition().x*PTM_RATIO,_b2->GetPosition().y*PTM_RATIO); } else { b2Vec2 vec1 = _b1->GetWorldPoint(_p1Original); b2Vec2 vec2 = _b2->GetWorldPoint(_p2Original); pointA = ccp(vec1.x *PTM_RATIO, vec1.y *PTM_RATIO); pointB = ccp(vec2.x *PTM_RATIO, vec2.y *PTM_RATIO); } [self updateWithPoints:pointApointB:pointBdt:dt]; } #pragma mark- #pragma mark Sprite Update~ /** * 5.更新 sprites 的位置 * (因为绳子的受力逻辑并不受 box2d 物理引擎掌控,没有一个所依附的 body,因此 sprite 的位置需手动来调整)~ */ -(void) updateSprites { for(int i = 0; i < _pointsCount - 1; ++ i) { VPoint *point1 = [[_vSticksobjectAtIndex:i]getPointA]; VPoint *point2 = [[_vSticksobjectAtIndex:i]getPointB]; CGPoint point1_ = ccp(point1.x, point1.y); CGPoint point2_ = ccp(point2.x, point2.y); float stickAngle = ccpToAngle(ccpSub(point1_, point2_)); CCSprite *tmpSprite = [_segmentSpritesobjectAtIndex:i]; [tmpSprite setPosition:ccpMidpoint(point1_, point2_)]; [tmpSprite setRotation: -CC_RADIANS_TO_DEGREES(stickAngle)]; }
/** 以下代码无法消除端点抖动的情况~ */ // VPoint *firstPoint = (VPoint*)[_vPoints objectAtIndex:0]; // VPoint *lastPoint = (VPoint*)[_vPoints objectAtIndex:(_pointsCount - 1)]; // [_spriteBar1 setPosition:ccp(firstPoint.x, firstPoint.y)]; // [_spriteBar2 setPosition:ccp(lastPoint.x, lastPoint.y)];
/** 消抖(将端点 sprite的 position与 b2RopeJoint 对象的两个 anchor 绑定),可能报错!舍弃~ */ // b2Vec2 vec1 = _ropeJoint->GetAnchorA(); // b2Vec2 vec2 = _ropeJoint->GetAnchorB();
/** 消抖2(采用和更新点一样的思路),最终方案~ */ b2Vec2 vec1 = _b1->GetWorldPoint(_p1Original); b2Vec2 vec2 = _b2->GetWorldPoint(_p2Original); [_spriteBar1 setPosition:ccp(vec1.x *PTM_RATIO, vec1.y *PTM_RATIO)]; [_spriteBar2 setPosition:ccp(vec2.x *PTM_RATIO, vec2.y *PTM_RATIO)]; } #pragma mark- #pragma mark Debug Draw~ /** * 6.调试~ */ -(void) debugDraw { glColor4f(0.0f,0.0f,1.0f, 1.0f); glLineWidth(5.0f); for(int i =0; i <_pointsCount - 1; ++ i) { VPoint *pointA = [[_vSticksobjectAtIndex:i]getPointA]; VPoint *pointB = [[_vSticksobjectAtIndex:i]getPointB]; ccDrawPoint(ccp(pointA.x, pointA.y)); ccDrawPoint(ccp(pointB.x, pointB.y)); ccDrawLine(ccp(pointA.x, pointA.y),ccp(pointB.x, pointB.y)); } // restore to white and default thickness glColor4f(1.0f,1.0f,1.0f, 1.0f); glLineWidth(1); } #pragma mark- #pragma mark Clear & Dealloc~ /** * 7.清理,dealloc~ */ -(void) removeSprites { // 1.从 _batchSegment中将绳子小节 sprite移除~ for(int i=0;i<_pointsCount-1;i++) { [(CCSprite*)[_segmentSpritesobjectAtIndex:i]removeFromParentAndCleanup:YES]; } [_segmentSpritesremoveAllObjects];
// 2.从 _batchBar中将绳子端点 _spriteBar1, _spriteBar2移除~ [_spriteBar1removeFromParentAndCleanup:YES]; [_spriteBar2removeFromParentAndCleanup:YES]; } -(void) dealloc { [_segmentSprites release]; [_vPoints release]; [_vSticks release]; [superdealloc]; } @end |
上全部下载连接:
PS:
(仅仅只是绳子的代码部分,不是完整的项目,留给各位自己来探究一下如何运用,
也可以参照我之前写过的关于绳子的文章,那里有 verlet-rope-latest 的原文出处,
而且,verlet-rope-latest VRope.mm 文件的注释中详细点明了绳子类的使用方法,
创建,在 Scene 的 tick 方法里面做逻辑更新,在 draw 方法里面做sprite 位置更新,清理等等,一应俱全)
还有可以更进一步的地方,绳子的端点有点儿抖动的现象可以通过将端点sprite附着到 b2RopeJoint的两个 anchor上解决~