如何在SpriteKit中创build一个绳索?

我想创build一个像这个video中显示的绳索。 开发这样的绳索最好的方法是什么?

我已经试着开始了,我认为最好的办法是制作许多小小的“绳索”部分,并用针脚连接起来(这真的是最好的吗?!?)。 但我不知道如何开始。

也许有人可以给我一些示例代码.. THX 🙂

我是该video的作者。 由于对源代码的大量需求,我已经在Github上发布了它。

你可以在这里find它

我也做了与PinJoints连接单个部分的绳索。 我认为这是展示“绳索”的唯一方法。 我认为在video中是一样的,你可以看到单链环节。 你甚至不需要这么多的连接元素,只要让精灵重叠了一下物理体,使其看起来非常真实。 下面是我的示例方法…只是replace图像名称n东西…

+(void) addRopeJointItems:(CGPoint)leftStartPosition countJointElements:(int)countJointElements game:(SKScene*)game { int itemJointWidth = 25; //Left Physics Anchor SKSpriteNode * leftAnchor = [SKSpriteNode spriteNodeWithImageNamed:@"dummypixel_transparent.png"]; leftAnchor.position = CGPointMake(leftStartPosition.x, leftStartPosition.y); leftAnchor.size = CGSizeMake(1, 1); leftAnchor.zPosition = 2; leftAnchor.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: leftAnchor.size]; leftAnchor.physicsBody.affectedByGravity = false; leftAnchor.physicsBody.mass = 99999999999; [game addChild:leftAnchor]; //add RopeElements for (int i=0; i<countJointElements; i++) { SKSpriteNode * item = [SKSpriteNode spriteNodeWithImageNamed:@"suspensionrope.png"]; item.name = [NSString stringWithFormat:@"ropeitem_%d", i]; item.position = CGPointMake(leftStartPosition.x + (i*itemJointWidth) + itemJointWidth/2, leftStartPosition.y+60); item.size = CGSizeMake(itemJointWidth + 5, 5); item.zPosition = 2; item.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: item.size]; item.physicsBody.categoryBitMask = kNilOptions; [game addChild:item]; //Add Joint to the element before SKPhysicsBody* bodyA; if (i == 0) { bodyA = leftAnchor.physicsBody; } else { bodyA = [game childNodeWithName:[NSString stringWithFormat:@"ropeitem_%d", i-1]].physicsBody; } SKPhysicsJointPin* joint = [SKPhysicsJointPin jointWithBodyA:bodyA bodyB:item.physicsBody anchor:CGPointMake((item.position.x - item.size.width/2) + 5, item.position.y)]; [game.physicsWorld addJoint:joint]; } //Right Physics Anchor SKSpriteNode * rightAnchor = [SKSpriteNode spriteNodeWithImageNamed:@"dummypixel_transparent.png"]; rightAnchor.position = CGPointMake((leftStartPosition.x + (countJointElements*itemJointWidth)), leftStartPosition.y+60); rightAnchor.size = CGSizeMake(1, 1); rightAnchor.zPosition = 2; rightAnchor.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: rightAnchor.size]; rightAnchor.physicsBody.affectedByGravity = false; rightAnchor.physicsBody.mass = 99999999999; [game addChild:rightAnchor]; //Add the Last Joint SKPhysicsJointPin* jointLast = [SKPhysicsJointPin jointWithBodyA:[game childNodeWithName:[NSString stringWithFormat:@"ropeitem_%d", countJointElements - 1]].physicsBody bodyB:rightAnchor.physicsBody anchor:rightAnchor.position]; [game.physicsWorld addJoint:jointLast]; } 

迅速

  func addRopeJointItems(leftStartPosition: CGPoint, numOfJoints countJointElements:Int, andScene game:SKScene ){ var itemJointWidth = 25 var leftAnchor = SKSpriteNode(imageNamed: "rope_ring.png") leftAnchor.position = CGPointMake(leftStartPosition.x, leftStartPosition.y) leftAnchor.size = CGSizeMake(1, 1); leftAnchor.zPosition = 2; leftAnchor.physicsBody = SKPhysicsBody(rectangleOfSize: leftAnchor.size) leftAnchor.physicsBody?.affectedByGravity = false leftAnchor.physicsBody?.mass = 999999999; game.addChild(leftAnchor) for index in 0...countJointElements { var item = SKSpriteNode(imageNamed: "rope_ring.png") item.name = "ropeitem_" + String(index) item.position = CGPointMake(leftStartPosition.x + CGFloat((index * itemJointWidth)) + CGFloat(itemJointWidth / 2) , leftStartPosition.y + 60) item.size = CGSizeMake(CGFloat(itemJointWidth + 5), 5); item.zPosition = 2; item.physicsBody = SKPhysicsBody(rectangleOfSize: item.size) item.physicsBody?.categoryBitMask = 0; game.addChild(item) var bodyA = SKPhysicsBody() if (index == 0) { bodyA = leftAnchor.physicsBody!; } else { var nameString = "ropeitem_" + String(index - 1) var node = game.childNodeWithName(nameString) as SKSpriteNode bodyA = node.physicsBody! } var joint = SKPhysicsJointPin.jointWithBodyA(bodyA, bodyB: item.physicsBody, anchor: CGPointMake((item.position.x - item.size.width/2) + 5, item.position.y)) game.physicsWorld.addJoint(joint) } var rightAnchor = SKSpriteNode(imageNamed: "rope_ring.png") rightAnchor.position = CGPointMake(leftStartPosition.x + CGFloat((countJointElements * itemJointWidth)), CGFloat(leftStartPosition.y + 60)) rightAnchor.size = CGSizeMake(1, 1); rightAnchor.zPosition = 2; rightAnchor.physicsBody = SKPhysicsBody(rectangleOfSize: rightAnchor.size) rightAnchor.physicsBody?.affectedByGravity = false rightAnchor.physicsBody?.mass = 999999999; game.addChild(rightAnchor) var nameString = NSString(format: "ropeitem_%d", countJointElements - 1) var node = game.childNodeWithName(nameString) var jointLast = SKPhysicsJointPin.jointWithBodyA(node!.physicsBody!, bodyB: rightAnchor.physicsBody, anchor: rightAnchor.position) game.physicsWorld.addJoint(jointLast) } 

我刚刚发布了自己的绳子版本的灵感来源于Mraty的,但是对弹性效果“bug”的解决方法。

这是我的绳子接口:

 #import <SpriteKit/SpriteKit.h> @interface ALRope : NSObject @property(nonatomic, readonly) NSArray *ropeRings; @property(nonatomic) int ringCount; @property(nonatomic) CGFloat ringScale; @property(nonatomic) CGFloat ringsDistance; @property(nonatomic) CGFloat jointsFrictionTorque; @property(nonatomic) CGFloat ringsZPosition; @property(nonatomic) CGPoint startRingPosition; @property(nonatomic) CGFloat ringFriction; @property(nonatomic) CGFloat ringRestitution; @property(nonatomic) CGFloat ringMass; @property(nonatomic) BOOL shouldEnableJointsAngleLimits; @property(nonatomic) CGFloat jointsLowerAngleLimit; @property(nonatomic) CGFloat jointsUpperAngleLimit; -(instancetype)initWithRingTexture:(SKTexture *)ringTexture; -(void)buildRopeWithScene:(SKScene *)scene; -(void)adjustRingPositions; -(SKSpriteNode *)startRing; -(SKSpriteNode *)lastRing; @end 

执行代码:

 #import "ALRope.h" @implementation ALRope { SKTexture *_ringTexture; NSMutableArray *_ropeRings; } static CGFloat const RINGS_DISTANCE_DEFAULT = 0; static CGFloat const JOINTS_FRICTION_TORQUE_DEFAULT = 0; static CGFloat const RING_SCALE_DEFAULT = 1; static int const RING_COUNT_DEFAULT = 30; static CGFloat const RINGS_Z_POSITION_DEFAULT = 1; static BOOL const SHOULD_ENABLE_JOINTS_ANGLE_LIMITS_DEFAULT = NO; static CGFloat const JOINT_LOWER_ANGLE_LIMIT_DEFAULT = -M_PI / 3; static CGFloat const JOINT_UPPER_ANGLE_LIMIT_DEFAULT = M_PI / 3; static CGFloat const RING_FRICTION_DEFAULT = 0; static CGFloat const RING_RESTITUTION_DEFAULT = 0; static CGFloat const RING_MASS_DEFAULT = -1; -(instancetype)initWithRingTexture:(SKTexture *)ringTexture { if(self = [super init]) { _ringTexture = ringTexture; //apply defaults _startRingPosition = CGPointMake(0, 0); _ringsDistance = RINGS_DISTANCE_DEFAULT; _jointsFrictionTorque = JOINTS_FRICTION_TORQUE_DEFAULT; _ringScale = RING_SCALE_DEFAULT; _ringCount = RING_COUNT_DEFAULT; _ringsZPosition = RINGS_Z_POSITION_DEFAULT; _shouldEnableJointsAngleLimits = SHOULD_ENABLE_JOINTS_ANGLE_LIMITS_DEFAULT; _jointsLowerAngleLimit = JOINT_LOWER_ANGLE_LIMIT_DEFAULT; _jointsUpperAngleLimit = JOINT_UPPER_ANGLE_LIMIT_DEFAULT; _ringFriction = RING_FRICTION_DEFAULT; _ringRestitution = RING_RESTITUTION_DEFAULT; _ringMass = RING_MASS_DEFAULT; } return self; } -(void)buildRopeWithScene:(SKScene *)scene { _ropeRings = [NSMutableArray new]; SKSpriteNode *firstRing = [self addRopeRingWithPosition:_startRingPosition underScene:scene]; SKSpriteNode *lastRing = firstRing; CGPoint position; for (int i = 1; i < _ringCount; i++) { position = CGPointMake(lastRing.position.x, lastRing.position.y - lastRing.size.height - _ringsDistance); lastRing = [self addRopeRingWithPosition:position underScene:scene]; } [self addJointsWithScene:scene]; } -(SKSpriteNode *)addRopeRingWithPosition:(CGPoint)position underScene:(SKScene *)scene { SKSpriteNode *ring = [SKSpriteNode spriteNodeWithTexture:_ringTexture]; ring.xScale = ring.yScale = _ringScale; ring.position = position; ring.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ring.size.height / 2]; ring.physicsBody.allowsRotation = YES; ring.physicsBody.friction = _ringFriction; ring.physicsBody.restitution = _ringRestitution; if(_ringMass > 0) { ring.physicsBody.mass = _ringMass; } [scene addChild:ring]; [_ropeRings addObject:ring]; return ring; } -(void)addJointsWithScene:(SKScene *)scene { for (int i = 1; i < _ropeRings.count; i++) { SKSpriteNode *nodeA = [_ropeRings objectAtIndex:i-1]; SKSpriteNode *nodeB = [_ropeRings objectAtIndex:i]; SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:nodeA.physicsBody bodyB:nodeB.physicsBody anchor:CGPointMake(nodeA.position.x, nodeA.position.y - (nodeA.size.height + _ringsDistance) / 2)]; joint.frictionTorque = _jointsFrictionTorque; joint.shouldEnableLimits = _shouldEnableJointsAngleLimits; if(_shouldEnableJointsAngleLimits) { joint.lowerAngleLimit = _jointsLowerAngleLimit; joint.upperAngleLimit = _jointsUpperAngleLimit; } [scene.physicsWorld addJoint:joint]; } } //workaround for elastic effect should be called from didSimulatePhysics -(void)adjustRingPositions { //based on zRotations of all rings and the position of start ring adjust the rest of the rings positions starting from top to bottom for (int i = 1; i < _ropeRings.count; i++) { SKSpriteNode *nodeA = [_ropeRings objectAtIndex:i-1]; SKSpriteNode *nodeB = [_ropeRings objectAtIndex:i]; CGFloat thetaA = nodeA.zRotation - M_PI / 2, thetaB = nodeB.zRotation + M_PI / 2, jointRadius = (_ringsDistance + nodeA.size.height) / 2, xJoint = jointRadius * cosf(thetaA) + nodeA.position.x, yJoint = jointRadius * sinf(thetaA) + nodeA.position.y, theta = thetaB - M_PI, xB = jointRadius * cosf(theta) + xJoint, yB = jointRadius * sinf(theta) + yJoint; nodeB.position = CGPointMake(xB, yB); } } -(SKSpriteNode *)startRing { return _ropeRings[0]; } -(SKSpriteNode *)lastRing { return [_ropeRings lastObject]; } @end 

场景代码:

 #import "ALRopeDemoScene.h" #import "ALRope.h" @implementation ALRopeDemoScene { __weak SKSpriteNode *_branch; CGPoint _touchLastPosition; BOOL _branchIsMoving; ALRope *_rope; } -(id)initWithSize:(CGSize)size { if (self = [super initWithSize:size]) { /* Setup your scene here */ self.backgroundColor = [SKColor colorWithRed:0.2 green:0.5 blue:0.6 alpha:1.0]; [self buildScene]; } return self; } -(void) buildScene { SKSpriteNode *branch = [SKSpriteNode spriteNodeWithImageNamed:@"Branch"]; _branch = branch; branch.position = CGPointMake(CGRectGetMaxX(self.frame) - branch.size.width / 2, CGRectGetMidY(self.frame) + 200); branch.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(2, 10)]; branch.physicsBody.dynamic = NO; [self addChild:branch]; _rope = [[ALRope alloc] initWithRingTexture:[SKTexture textureWithImageNamed:@"rope_ring"]]; //configure rope params if needed // _rope.ringCount = ...;//default is 30 // _rope.ringScale = ...;//default is 1 // _rope.ringsDistance = ...;//default is 0 // _rope.jointsFrictionTorque = ...;//default is 0 // _rope.ringsZPosition = ...;//default is 1 // _rope.ringFriction = ...;//default is 0 // _rope.ringRestitution = ...;//default is 0 // _rope.ringMass = ...;//ignored unless mass > 0; default -1 // _rope.shouldEnableJointsAngleLimits = ...;//default is NO // _rope.jointsLowerAngleLimit = ...;//default is -M_PI/3 // _rope.jointsUpperAngleLimit = ...;//default is M_PI/3 _rope.startRingPosition = CGPointMake(branch.position.x - 100, branch.position.y);//default is (0, 0) [_rope buildRopeWithScene:self]; //attach rope to branch SKSpriteNode *startRing = [_rope startRing]; CGPoint jointAnchor = CGPointMake(startRing.position.x, startRing.position.y + startRing.size.height / 2); SKPhysicsJointPin *joint = [SKPhysicsJointPin jointWithBodyA:branch.physicsBody bodyB:startRing.physicsBody anchor:jointAnchor]; [self.physicsWorld addJoint:joint]; } -(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self]; if(CGRectContainsPoint(_branch.frame, location)) { _branchIsMoving = YES; _touchLastPosition = location; } } -(void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event { _branchIsMoving = NO; } -(void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{ if(_branchIsMoving) { UITouch *touch = [touches anyObject]; CGPoint location = [touch locationInNode:self], branchCurrentPosition = _branch.position; CGFloat dx = location.x - _touchLastPosition.x, dy = location.y - _touchLastPosition.y; _branch.position = CGPointMake(branchCurrentPosition.x + dx, branchCurrentPosition.y + dy); _touchLastPosition = location; } } -(void)didSimulatePhysics { //workaround for elastic effect [_rope adjustRingPositions]; } @end 

注意[场景didSimulatePhysics]的[rope adjustmentsRingPositions]调用。 这是我对弹性bug的实际修复。

完整的演示代码在这里