Comment créer une corde dans SpriteKit?

Je veux créer une corde comme celle qui est montrée dans cette video . Quelle est la meilleure façon de développer une corde comme celle-ci?

J'ai déjà essayé de commencer et je pense que la meilleure façon de le faire est de faire beaucoup de petites "cordes" et de les relier avec un joint d'épingle (est-ce vraiment le meilleur?!?). Mais je ne savais pas comment commencer.

Peut-être que quelqu'un peut me donner un exemple de code pour ça .. THX 🙂

Je suis l'auteur de cette video. En raison de la forte request pour le code source, je l'ai publié sur le Github.

Vous pouvez le find ici

J'ai aussi fait une corde avec des pièces uniques connectées avec des PinJoints. Je pense que c'est le seul moyen d'afficher une "corde". Je pense que dans la video c'est la même chose, vous pouvez voir les liens de la string unique. Vous n'avez même pas besoin d'autant d'éléments articulés, laissez simplement l'image-object chevaucher un peu le corps physique pour le rendre très réel. Heres ma méthode d'exemple … juste replace les noms d'image n choses …

+(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 = [NSSsortingng ssortingngWithFormat:@"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:[NSSsortingng ssortingngWithFormat:@"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:[NSSsortingng ssortingngWithFormat:@"ropeitem_%d", countJointElements - 1]].physicsBody bodyB:rightAnchor.physicsBody anchor:rightAnchor.position]; [game.physicsWorld addJoint:jointLast]; } 

en swift

  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_" + Ssortingng(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 nameSsortingng = "ropeitem_" + Ssortingng(index - 1) var node = game.childNodeWithName(nameSsortingng) 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 nameSsortingng = NSSsortingng(format: "ropeitem_%d", countJointElements - 1) var node = game.childNodeWithName(nameSsortingng) var jointLast = SKPhysicsJointPin.jointWithBodyA(node!.physicsBody!, bodyB: rightAnchor.physicsBody, anchor: rightAnchor.position) game.physicsWorld.addJoint(jointLast) } 

Je viens de sortir ma propre version de corde inspirée par Mraty mais avec une solution de contournement pour l'effet "bug" élastique.

Voici mon interface de corde:

 #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 

Code d'implémentation

 #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 

Code de scène:

 #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 

Notez l'appel [rope adjustRingPositions] de [scene didSimulatePhysics]. C'est ma solution réelle pour le bug élastique.

Le code de démonstration complet est ici