Mike Mikina
Mike Mikina

Reputation: 496

SpriteKit drag and drop through wall

I did simple drag and drop in SpriteKit with physics. It works as expected, but when I use fast drag, element goes through wall.

self.runner is wall

self.runner2 is square

Wall have dynamic set to NO.

Movie shows everything: https://www.dropbox.com/s/ozncf9i16o1z80o/spritekit_sample.mov?dl=0

Tested on simulator and real device, both iOS 7.

I want to prevent square from going through wall. Any ideas?

#import "MMMyScene.h"

static NSString * const kRunnerImg = @"wall.png";
static NSString * const kRunnerName = @"runner";

static NSString * const kRunnerImg2 = @"zebraRunner.png";
static NSString * const kRunnerName2 = @"runner";

static const int kRunnerCategory = 1;
static const int kRunner2Category = 2;
static const int kEdgeCategory = 3;


@interface MMMyScene () <SKPhysicsContactDelegate>

@property (nonatomic, weak) SKNode *draggedNode;
@property (nonatomic, strong) SKSpriteNode *runner;
@property (nonatomic, strong) SKSpriteNode *runner2;

@end

@implementation MMMyScene

-(id)initWithSize:(CGSize)size {    
    if (self = [super initWithSize:size]) {
      self.backgroundColor = [SKColor colorWithRed:0.15 green:0.15 blue:0.3 alpha:1.0];

      self.runner = [SKSpriteNode spriteNodeWithImageNamed:kRunnerImg];
      self.runner.texture = [SKTexture textureWithImageNamed:kRunnerImg];
      [self.runner setName:kRunnerName];

      [self.runner setPosition:CGPointMake(160, 300)];
      [self.runner setSize:CGSizeMake(320, 75)];
      [self addChild:self.runner];

      self.runner.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(320, 75)];
      self.runner.physicsBody.categoryBitMask = kRunnerCategory;
      self.runner.physicsBody.contactTestBitMask = kRunner2Category;
      self.runner.physicsBody.collisionBitMask =  kRunner2Category;
      self.runner.physicsBody.dynamic = NO;
      self.runner.physicsBody.allowsRotation = NO;

      self.runner2 = [SKSpriteNode spriteNodeWithImageNamed:kRunnerImg2];
      [self.runner2 setName:kRunnerName2];

      [self.runner2 setPosition:CGPointMake(100, 100)];
      [self.runner2 setSize:CGSizeMake(75, 75)];

      self.runner2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(75, 75)];
      self.runner2.physicsBody.categoryBitMask = kRunner2Category;
      self.runner2.physicsBody.contactTestBitMask = kRunnerCategory;
      self.runner2.physicsBody.collisionBitMask = kRunnerCategory;
      self.runner2.physicsBody.dynamic = YES;
      self.runner2.physicsBody.allowsRotation = NO;

      [self addChild:self.runner2];

      self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
      self.physicsBody.categoryBitMask = kEdgeCategory;
      self.physicsBody.collisionBitMask = 0;
      self.physicsBody.contactTestBitMask = 0;
      self.physicsWorld.gravity = CGVectorMake(0,0);
      self.physicsWorld.contactDelegate = self;      
    }

    return self;
}

- (void)didBeginContact:(SKPhysicsContact *)contact {
  SKPhysicsBody *firstBody, *secondBody;

  firstBody = contact.bodyA;
  secondBody = contact.bodyB;

  if(firstBody.categoryBitMask == kRunnerCategory )
  {
    NSLog(@"collision");
    //self.draggedNode = nil;

  }
}

- (void)didMoveToView:(SKView *)view {
  UIPanGestureRecognizer *gestureRecognizer = [[UIPanGestureRecognizer alloc] initWithTarget:self action:@selector(handlePanFrom:)];
  [[self view] addGestureRecognizer:gestureRecognizer];
}

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

- (void)handlePanFrom:(UIPanGestureRecognizer *)recognizer {
  if (recognizer.state == UIGestureRecognizerStateBegan) {
    CGPoint touchLocation = [recognizer locationInView:recognizer.view];
    touchLocation = [self convertPointFromView:touchLocation];
    [self selectNodeForTouch:touchLocation];
  }
  else if (recognizer.state == UIGestureRecognizerStateChanged) {
    CGPoint translation = [recognizer translationInView:recognizer.view];
    translation = CGPointMake(translation.x, -translation.y);
    [self panForTranslation:translation];
    [recognizer setTranslation:CGPointZero inView:recognizer.view];
  }
}

- (void)selectNodeForTouch:(CGPoint)touchLocation {
  SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];

  if(![self.draggedNode isEqual:touchedNode]) {
    self.draggedNode = touchedNode;
    //    self.draggedNode.physicsBody.affectedByGravity = NO;
  }
}


@end

Upvotes: 2

Views: 877

Answers (2)

aytek
aytek

Reputation: 1952

While dragging nodes, if you change their position in the following ways,

draggedNode.position = CGPoint(x: position.x + translation.x, y: position.y + translation.y)

and

SKAction.move(to: CGPoint(x: 100, y: sprite.position.y), duration: 1))

nodes will lose their physics properties. That's why they pass through walls or other nodes. Therefore you need to change their velocity or apply force or impulse.

I created an example project in Github. And the video is here

Upvotes: 1

CodeSmile
CodeSmile

Reputation: 64477

You're setting the position of the node directly, this bypasses collision detection. If you move fast, the next position can be on the other side of the wall (or close enough so that physics will resolve the collision by moving the dragged node outside and above the wall).

Enabling usesPreciseCollisionDetection on the dynamic body may improve the situation but may not entirely prevent it. Actually it may not make any difference due to setting the node position directly instead of moving the body using force/impulse.

To fix this behavior you can't rely on contact events. Instead use bodyAlongRayStart:end: to determine if there's a blocking body between the node's current and the next position. Even so this will only work with your current setup, but couldn't prevent the ray successfully passing through small gaps in the wall where the dynamic body wouldn't be able to fit through.

Upvotes: 3

Related Questions