我一直在尝试使用sprite kit,为我的想法构建原型.我一直用一个SKPhysicsJointPin将一串物理体连在一起,制作一根绳子(实际上更像是一条自行车链条,但它已经足够好了).在场景中也有很多球,当我点击它们时我可以放下它们.这导致以下结果:
然而,当我丢掉更多球时,链条似乎无法处理它,并且“打破”:
这是一个movie showing the phenomenon
发生了什么?文档从未建议SKPhysicsJointPin具有有限的最大强度或弹性或类似.这是sprite工具包中的“bug”,还是我使用了错误的方法?
解决方法
我遇到了一个类似于绳子模拟的弹性错误,最终可能会找到一种解决方法.
这是我的绳索界面:
#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]调用.这是弹性bug的实际解决方法.