【问题标题】:How to create a rope in SpriteKit?如何在 SpriteKit 中创建绳索?
【发布时间】:2013-12-28 06:56:29
【问题描述】:

我想创建一根绳子,就像video 中显示的那样。 开发这种绳索的最佳方法是什么?

我已经尝试过开始,我认为最好的方法是制作许多小的“绳索”部件并用销接头将它们连接起来(这真的是最好的吗?!?)。但我不知道如何开始。

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

【问题讨论】:

  • 我喜欢背景音乐。
  • 你的意思是限位接头吗?
  • 使用 SKPhysicsJointPin。 Google Box2d 绳索,用 sprite kit 做同样的事情。

标签: ios objective-c xcode game-physics sprite-kit


【解决方案1】:

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

你可以找到它here

【讨论】:

    【解决方案2】:

    我还用 PinJoints 连接了单个部分的绳索。我认为这是展示“绳索”的唯一方法。我认为在视频中它是相同的,你可以看到单链链接。你甚至不需要这么多的关节元素,只需让精灵与物理体重叠一点,使其看起来非常真实。这是我的示例方法...只需替换图像名称 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];
    }
    

    【讨论】:

    • fwiw,我会修改为:leftAnchor.physicsBody.mass = CGFLOAT_MAX;
    【解决方案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's,但有一个解决弹性效果“错误”的方法。

      这是我的绳索界面:

      #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
      

      注意来自 [scene didSimulatePhysics] 的 [rope adjustRingPositions] 调用。这是我对弹性错误的实际修复。

      完整的演示代码是here

      【讨论】:

      • 你可以添加相关代码而不是链接吗?
      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2023-03-16
      • 1970-01-01
      • 1970-01-01
      • 2014-06-16
      • 1970-01-01
      • 2015-10-24
      • 1970-01-01
      相关资源
      最近更新 更多