4

Want to drag a SKSpriteNode (follow your finger movements) with SKPhysics (collision enabled)

2 failed solutions:

1)

a) Solution: Change the position of the SKSpriteNode

b) Problem: The SKSpriteNode will "magically" pass through a wall unexpectedly when user drags the spriteNode quickly through the wall (Physics seems not to work)

c) code:

@property (nonatomic) CGPoint translationBy;

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    [self selectNodeForTouch:positionInScene];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    CGPoint previousPosition = [touch previousLocationInNode:self];

    CGPoint translation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);

    [self panForTranslation:translation];
}

- (void)panForTranslation:(CGPoint)translation {
    CGPoint position = [_selectedNode position];
    if([[_selectedNode name] isEqualToString:PLAYER_NAME]) {
        [_selectedNode setPosition:CGPointMake(position.x + translation.x, position.y + translation.y)];
    }
}

2) After searching around here found this advice by @LearnCocos2D "When you use physics, stick to moving the physics body through force, impulse or changing the body velocity directly." So I tried:

a) Solution: Apply impulse on the sprite node.

b) Problem: The sprite node will keep on moving even if I didn't drag the sprite node. (Behaving like a floating boat. Expect it to act like a car once the drag is stopped the sprite node will stop; once drag begins it will follow the finger.)

c) Code:

-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    [self selectNodeForTouch:positionInScene];
}

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    CGPoint previousPosition = [touch previousLocationInNode:self];

    CGPoint translation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);

    CGVector forceVector = CGVectorMake(translation.x, translation.y);
    [self.player.physicsBody applyImpulse:forceVector];
    //[self.player.physicsBody setVelocity:CGVectorMake(0, 0)];

}

-->>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>

Updated:

From the comment: "i) You could determine the distance of the node to the finger location, then set the velocity every update: so that it will exactly be on the finger's position the next frame. ii) If both finger and node position are identical this will cancel out any movement, otherwise the node will stick to your finger while performing all physics collisions and will continue to push objects between itself and the finger in cases where it is obstructed."

a) Question: How to disable flick?

b) Problem: The sprite node will move very far away with a minor (accidental) flick.

c) code:

@property (nonatomic) CGPoint translationBy;

- (void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
    UITouch *touch = [touches anyObject];
    CGPoint positionInScene = [touch locationInNode:self];
    CGPoint previousPosition = [touch previousLocationInNode:self];
    //    1) You could determine the distance of the node to the finger location,

    CGPoint xyTranslation = CGPointMake(positionInScene.x - previousPosition.x, positionInScene.y - previousPosition.y);
    if (fabsf(positionInScene.x - previousPosition.x) < kPlayerMovementThreshold) {
        xyTranslation.x  = 0;
    }
    if (fabsf(positionInScene.y - previousPosition.y) < kPlayerMovementThreshold) {
        xyTranslation.y = 0;
    }
    self.translationBy = xyTranslation;
}

static const CGFloat kPlayerMovementSpeed = 55.0f;

static const CGFloat kPlayerMovementThreshold = 1.0f;

-(void)update:(CFTimeInterval)currentTime{
     // ii) then set the velocity every update: so that it will exactly be on the finger's position the next frame.

    CGFloat dx = self.translationBy.x*kPlayerMovementSpeed;
    CGFloat dy = self.translationBy.y*kPlayerMovementSpeed;
    CGVector velocityVector = CGVectorMake(dx,dy);
    [self.player.physicsBody setVelocity:velocityVector];
}
Community
  • 1
  • 1
user1872384
  • 6,886
  • 11
  • 61
  • 103
  • you can add a bool should_move, inside touchesBegan set that should_move to true and inside touchesEnded set should_move to false, now inside update type if (!should_move) [self.player.physicsBody setVelocity:CGVectorMake(0, 0)];, and now your player will respond only when you have the finger on the screen. – Julio Montoya Jul 15 '14 at 16:10
  • Opps... I forgot to mention that the touchesEnded method is not called since my finger is still on the screen. (however the sprite node still moves --> cause by the impulse) – user1872384 Jul 15 '14 at 16:12
  • Now you should work with the difference between positions, if (fabsf(positionInScene.x - previousPosition.x) < 1)[self.player.physicsBody setVelocity:CGVectorMake(0, 0)];, basically if the value is less than 1 your finger is not being moved, there you go :] – Julio Montoya Jul 15 '14 at 16:17
  • 2
    You could determine the distance of the node to the finger location, then set the velocity every update: so that it will exactly be on the finger's position the next frame. If both finger and node position are identical this will cancel out any movement, otherwise the node will stick to your finger while performing all physics collisions and will continue to push objects between itself and the finger in cases where it is obstructed. – CodeSmile Jul 15 '14 at 16:19
  • @JulioMontoya can you please elaborate more? As in where to set if (fabsf(positionInScene.x - previousPosition.x) < 1)[self.player.physicsBody setVelocity:CGVectorMake(0, 0)]; – user1872384 Jul 16 '14 at 05:17
  • @LearnCocos2D thanks for your reply. I've updated the question above. Still unable to make it work. Am I missing anything? – user1872384 Jul 16 '14 at 06:38
  • Got it to work :D. have to set the self.translationBy = CGPointZero; and also set a upper kPlayerMovementThreshold limit. Thanks for all your help. – user1872384 Jul 16 '14 at 10:20

1 Answers1

2

What I do is make an SKNode, let's call it 'a', with a very small physics body attached to it. This new node is connected via an SKPhysicsJointSpring to the node I want to move, let's call that 'b'. The new node a tracks my finger movement, and the spring makes sure the target node b follows that. Because the node b is positioned using physics, the collision detection works as expected.

Emiel
  • 1,474
  • 1
  • 13
  • 16
  • How do you move node `a`? I tried to do this by setting `a.position` but that would stretch `a` away from `b` without moving `b`. Then the spring would relax by moving `a` back to `b`, rather than leading `b` in the direction of `a` like we want. It seems like setting `position` was somehow bypassing the physics model. Or maybe I'm mistaken. – bcattle Feb 03 '17 at 23:17