Подтвердить что ты не робот

Как создать веревку в SpriteKit?

Я хочу создать веревку, подобную той, которая показана в этом видео. Каков наилучший способ развития такой веревки?

Я уже пытался начать, и я думаю, что лучший способ сделать это - сделать много маленьких "веревочных" частей и связать их с штыревым суставом (это действительно лучший!!?). Но я не знал, как начать.

Может кто-то может дать мне пример кода для этого.. спасибо:)

4b9b3361

Ответ 1

Я автор этого видео. Из-за большого спроса на исходный код я выпустил его на Github.

Вы можете найти здесь

Ответ 2

Я также сделал веревку с отдельными частями, связанными с PinJoints. Я думаю, что это единственный способ показать "веревку". Я думаю, что в видео это то же самое, вы можете видеть одноцелевые ссылки. Вам даже не нужны так много сочлененных элементов, просто позвольте спрайту немного перекрывать физическое тело, чтобы он выглядел очень реально. Heres мой примерный метод... просто замените имена изображений 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];
}

Ответ 3

в быстрой

 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)


}

Ответ 4

Я только что выпустил свою собственную версию каната, вдохновленную mraty, но с обходным решением для "ошибки" эффекта упругого эффекта.

Здесь мой интерфейс каната:

#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

Обратите внимание на вызов [rope adjustRingPositions] из [scene didSimulatePhysics]. Это мое фактическое исправление для упругой ошибки.

Полный демо-код здесь