ios objective-c xcode game-physics sprite-kit

ios - Cómo crear una cuerda en SpriteKit?



objective-c xcode (4)

Yo soy el autor de ese video. Debido a la gran demanda del código fuente, lo he lanzado en el Github.

Puedes encontrarlo aquí

Quiero crear una cuerda como la que se muestra en este video . ¿Cuál es la mejor manera de desarrollar una cuerda como esta?

Ya he intentado comenzar y creo que la mejor manera de hacerlo es hacer muchas partes pequeñas de "cuerda" y conectarlas con una junta de pasador (¿es realmente la mejor?!?). Pero no sabía cómo comenzar.

Tal vez alguien me puede dar un código de ejemplo para eso ... THX :)


También hice una cuerda con partes individuales conectadas con PinJoints. Creo que es la única forma de mostrar una "cuerda". Creo que en el video es lo mismo, puedes ver los enlaces de una sola cadena. Ni siquiera necesitas tantos elementos articulados, solo deja que el sprite se superponga un poco al cuerpo de la física para que se vea muy real. Aquí está mi método de ejemplo ... simplemente reemplace los nombres de las imágenes n cosas ...

+(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]; }


en rápido

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) }


Acabo de lanzar mi propia versión de cuerda inspirada en la de Mraty, pero con una solución para el efecto elástico "error".

Aquí está mi interfaz de cuerda:

#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

Código de implementación:

#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

Código de escena:

#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

Observe la llamada [rope adjustRingPositions] de [scene didSimulatePhysics]. Esa es mi solución real para el error elástico.

El código de demostración completo está aquí