user3453934
user3453934

Reputation: 13

Make an elastic band type slingshot in spritekit

I'm working on a game where I have a line across the bottom of the screen that I use to launch things up into the air. It should behave like a rubber band or a slingshot. I have hacked together something that works, but it's kind of a bad solution and I'm hoping someone can suggest another way. My way basically involved redrawing a mutablepath by repeated calls to the draw method during the touchesMoved method. Again, I know this is a bad way of doing it, so sorry for the horrible code.

-(void)drawLine:(CGPoint)location
{
[_powerLine removeFromParent];

CGPoint pointTL = CGPointMake(19, 131);
CGPoint pointTR = CGPointMake(308, 131);
CGPoint pointBL = CGPointMake(location.x-10, location.y);
CGPoint pointBR = CGPointMake(location.x+10, location.y);

UIBezierPath *lineShape = [UIBezierPath bezierPath];
[lineShape moveToPoint:pointTL];
[lineShape addLineToPoint:pointBL];
[lineShape addLineToPoint:pointBR];
[lineShape addLineToPoint:pointTR];

_powerLine = [SKShapeNode node];
_powerLine.path = lineShape.CGPath;
_powerLine.lineWidth = 2.0;
_powerLine.strokeColor = [SKColor colorWithRed:0.0 green:0 blue:0 alpha:1];

[self addChild:_powerLine];

CGMutablePathRef powerLinePath = CGPathCreateMutable();
CGPathMoveToPoint(powerLinePath, nil, pointTL.x, pointTL.y);
CGPathAddLineToPoint(powerLinePath, nil, pointBL.x, pointBL.y);
CGPathAddLineToPoint(powerLinePath, nil, pointBR.x, pointBR.y);
CGPathAddLineToPoint(powerLinePath, nil, pointTR.x, pointTR.y);
_powerLine.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:powerLinePath];
_powerLine.physicsBody.categoryBitMask = WFPhysicsCategoryPowerline;
_lastBR = pointBR;
_lastBL = pointBL;
}

I'm hoping there is a better way to do this other than constantly redrawing it when the person pulls the line down to shot the object up in the air. I looked into spring joints but couldn't convince them to work. The other problem I had with spring joints was how to get the image to stretch to match where the line should be. This approach solves trying to stretch an image by simply eliminating the image. It would be nice to use springs so that I could avoid having to hand code the physics of this.

Anyone have thoughts on how to do this?

Upvotes: 1

Views: 2184

Answers (1)

Andrew
Andrew

Reputation: 2468

I'm not sure if you're concerned about how to draw a convincing elastic band onscreen, or how to emulate the physics of one. If it's the former I can't help you much, but if it's the latter you could try something like this! You should be able to copy and paste it into an existing sprite kit app to play around with it, just initialize it and call SKView's presentScene: with it.

(header file)
#import <SpriteKit/SpriteKit.h>

@interface SlingScene : SKScene

@end

(implementation file)
#import "SlingScene.h"

@interface SlingScene ()

@property (nonatomic, strong) SKAction *slingAction;

@end

@implementation SlingScene

- (instancetype)initWithSize:(CGSize)size {
    self = [super initWithSize:size];
    if (self) {
        CGPoint center = CGPointMake(self.size.width/2.0, self.size.height/2.0);

        self.slingAction = [SKAction sequence:
                            @[[SKAction waitForDuration:0.1],
                              [SKAction runBlock:
                               ^{  
                                   [self.physicsWorld removeAllJoints];
                               }   
                               ]]];

        // Create a square, which will be slung by the spring
        SKSpriteNode *square =
            [SKSpriteNode spriteNodeWithColor:[SKColor whiteColor]
                                         size:CGSizeMake(60.0, 60.0)];
        square.position = CGPointMake(center.x, center.y - 2*square.size.height);
        square.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:square.size];
        square.physicsBody.collisionBitMask = 0;
        square.name = @"square";

        // Create a post to anchor the square to
        SKShapeNode *post = [SKShapeNode node];
        post.path = CGPathCreateWithEllipseInRect(
                CGRectMake(0.0, 0.0, 60.0, 60.0), NULL);
        post.fillColor = [SKColor brownColor];
        post.strokeColor = [SKColor brownColor];
        post.position = CGPointMake(center.x-30.0, center.y-30.0);
        post.physicsBody =
            [SKPhysicsBody bodyWithCircleOfRadius:60.0 center:center];

        // Give the post a near-infinite mass so the square won't tug at it
        // and move it around
        post.physicsBody.mass = 1000000;
        post.physicsBody.affectedByGravity = NO; 

        // Set their collision bit masks to the same value to allow them to pass
        // through each other
        post.physicsBody.collisionBitMask = 0;
        square.physicsBody.collisionBitMask = 0;

        // Add them to the scene
        [self addChild:post];
        [self addChild:square];

        // Connect them via a spring
        SKPhysicsJointSpring *spring =
            [SKPhysicsJointSpring jointWithBodyA:post.physicsBody
                                           bodyB:square.physicsBody
                                         anchorA:center
                                         anchorB:square.position];
        spring.damping = 0.4;
        spring.frequency = 1.0;
        [self.physicsWorld addJoint:spring];

        // Lower gravity from the default {0.0, -9.8} to allow the
        // square to be slung farther
        self.physicsWorld.gravity = CGVectorMake(0.0, -4.0);
    }
    return self;
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    // Move the square to the touch position
    SKSpriteNode *square = (SKSpriteNode *)[self childNodeWithName:@"square"];
    CGPoint location = [[touches anyObject] locationInNode:self];
    [square runAction:[SKAction moveTo:location duration:0.1]];
}

- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
    // Sling the square by
    //   1. allowing the spring to accelerate it, and
    //   2. removing the spring altogether
    [self runAction:self.slingAction];
}

@end

Another method might be to compute the x and y positions relative to a specific point, and apply an impulse in the opposite direction. For example, if you find dx = -100.0 and dy = -100.0, you could use applyImpulse:CGVectorMake(-dx, -dy) to launch it up and right. Using the spring joint gives you some acceleration, however.

Upvotes: 2

Related Questions