Reputation: 496
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
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
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