Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to create a rope in SpriteKit?

I want to create a rope like the one that is shown in this video. What is the best way to develop a rope like this?

I've already tried to start and I think the best way to do it, is to make many little "rope"-parts and connect them with a pin joint (is that really the best one?!?). But I didn't know how to start.

Maybe someone can give me some example code for that.. THX :)

like image 355
user1940136 Avatar asked Dec 28 '13 06:12

user1940136


People also ask

What is the difference between SceneKit and SpriteKit?

You can assume SpriteKit is at Top of the SceneKit, As using SceneKit you can add 3D models into Augmented Reality while SpriteKit is used to add extra sprites onto the model. In short SpriteKit is revolution in Gaming.

What language does SpriteKit use?

SpriteKit is one of the best ways to make games on iOS. It's easy to learn, powerful, and is fully supported by Apple. Swift is an easy language to get started with, especially if you are a beginner to the iOS platform.

Does SpriteKit use Metal?

SceneKit, SpriteKit and Core Image all have some integration with Metal shaders and Metal rendering. There may be times when you don't want to write a full-blown 3D Metal app, but you want to take advantage of SceneKit.


4 Answers

I am the author of that video. Because of the big demand for the source code I have released it on the Github.

You can find it here

like image 146
mraty Avatar answered Oct 19 '22 10:10

mraty


I've also done a rope with single parts connected with PinJoints. I think it's the only way to display a "rope". I think in the video it's the same, you can see the single chain links. You don't even need so much jointed elements, just let the sprite overlap a bit the physics body to make it look very real. Heres my example method... just replace the image names n stuff...

+(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];
}
like image 42
zinne Avatar answered Oct 19 '22 10:10

zinne


in 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_" + 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)


}
like image 44
MandelDuck Avatar answered Oct 19 '22 08:10

MandelDuck


I just released my own rope version inspired by mraty's but with a workaround for the elastic effect "bug".

Here's my rope interface:

#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

Implementation code:

#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

Scene Code:

#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

Notice the [rope adjustRingPositions] call from [scene didSimulatePhysics]. That is my actual fix for the elastic bug.

Complete demo code is here

like image 33
ALTN Avatar answered Oct 19 '22 08:10

ALTN