Collision detection not working? - ios

I'm trying to make a very simple SpriteKit app for iOS, but there's a problem:
#import "MyScene.h"
static const uint32_t laserCategory = 0x1 << 0;
static const uint32_t enemyCategory = 0x1 << 0;
#implementation MyScene
- (id) initWithSize: (CGSize) size
{
if (self = [super initWithSize: size])
{
self.backgroundColor = [SKColor colorWithRed: 1.0 green: 1.0 blue: 1.0 alpha: 1.0];
self.physicsWorld.gravity = CGVectorMake(0,0);
SKSpriteNode *ship = [SKSpriteNode spriteNodeWithImageNamed: #"Ship"];
ship.position = CGPointMake (CGRectGetMidX (self.frame), 50);
[self addChild: ship];
SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed: #"Enemy"];
enemy.position = CGPointMake (CGRectGetMidX (self.frame), 440);
[self addChild: enemy];
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: enemy.size];
enemy.physicsBody.dynamic = YES;
enemy.physicsBody.categoryBitMask = enemyCategory;
enemy.physicsBody.contactTestBitMask = laserCategory;
enemy.physicsBody.collisionBitMask = 0;
SKAction *moveEnemy = [SKAction moveByX: 0.0 y: -500.0 duration: 5.0];
[enemy runAction: moveEnemy];
} return self;
}
- (void) touchesBegan: (NSSet *) touches withEvent: (UIEvent *) event
{
SKSpriteNode *laser = [SKSpriteNode spriteNodeWithImageNamed: #"Laser"];
laser.position = CGPointMake (CGRectGetMidX (self.frame), 100);
[self addChild: laser];
laser.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: laser.size];
laser.physicsBody.dynamic = YES;
laser.physicsBody.categoryBitMask = laserCategory;
laser.physicsBody.contactTestBitMask = enemyCategory;
laser.physicsBody.collisionBitMask = 0;
SKAction *moveLaser = [SKAction moveByX: 0.0 y: 2000.0 duration: 5.0];
[laser runAction: moveLaser];
}
- (void) laser: (SKSpriteNode *) laser didCollideWithEnemy: (SKSpriteNode *) enemy
{
[laser removeFromParent];
[enemy removeFromParent];
}
- (void) didBeginContact: (SKPhysicsContact *) contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if ((firstBody.categoryBitMask & laserCategory) != 0 && (secondBody.categoryBitMask & enemyCategory) != 0)
{
[self laser: (SKSpriteNode *) firstBody.node didCollideWithEnemy: (SKSpriteNode *) secondBody.node];
}
}
#end
This is my "MyScene.m" file, and everything works just fine. The only thing that doesn't work is the collision detection between the laser object and the enemy object. They just pass right through each other. Any solutions? Any other tips are welcome, as well.
Thanks.

You have set the same bit mask for two different category:
static const uint32_t laserCategory = 0x1 << 0;
static const uint32_t enemyCategory = 0x1 << 0;
After that the if statement in didBeginContact: method is wrong:
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
you should change one of those to 0x1 << 1:
static const uint32_t laserCategory = 0x1 << 0;
static const uint32_t enemyCategory = 0x1 << 1;
//Extended
You are also missing set up contact delegate to phasic world, add this code after you set up gravity in initWithSize:
self.physicsWorld.gravity = CGVectorMake(0,0);
self.physicsWorld.contactDelegate = self;
I assume you added delegate declaration to MyScene class:
<SKPhysicsContactDelegate>

Related

Not detecting collision between boundary frame and SKNode in SpriteKit

Here is my code in my GameScene. I have a simple sprite which falls down under gravity and bounces of the edges of the screen and a particle emitter. All I need is that I should be able to detect collisions between the SKNode and the physicsbody i.e. the frame.
Code Used
#import "GameScene.h"
#interface GameScene () <SKPhysicsContactDelegate>
#property (strong) UITouch *rightTouch;
#property (strong) UITouch *leftTouch;
#property (strong) SKNode *spinnyNode;
#property (assign, nonatomic) int count;
#end
#implementation GameScene {
}
static const uint32_t birdCategory = 1 << 0;
static const uint32_t worldCategory = 1 << 1;
- (void)didMoveToView:(SKView *)view {
self.backgroundColor = [UIColor colorWithRed:134/255.0 green:50/255.0 blue:148/255.0 alpha:1.0];
self.scaleMode = SKSceneScaleModeAspectFit;
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody.categoryBitMask = worldCategory;
NSString *burstPath = [[NSBundle mainBundle] pathForResource:#"fire" ofType:#"sks"];
SKEmitterNode *burstNode = [NSKeyedUnarchiver unarchiveObjectWithFile:burstPath];
burstNode.position = CGPointMake(0, 0);
[self addChild:burstNode];
self.spinnyNode = [self childNodeWithName:#"ball"];
self.spinnyNode.physicsBody.angularVelocity = 10.0;
self.spinnyNode.hidden = YES;
self.spinnyNode.physicsBody.categoryBitMask = birdCategory;
self.spinnyNode.physicsBody.contactTestBitMask = 0x0;
// self.spinnyNode = ball;
SKNode *leftPaddle = [self childNodeWithName:#"bottom"];
leftPaddle.physicsBody.angularVelocity = 10.0;
}
- (void)touchDownAtPoint:(CGPoint)pos {
self.count = self.count+1;
CGPoint p = pos;
NSLog(#"/n %f %f %f %f", p.x, p.y, self.frame.size.height, self.frame.size.width);
if (self.count == 1)
{
self.spinnyNode.position = p;
self.spinnyNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:40];
self.spinnyNode.physicsBody.velocity=CGVectorMake(203, 10);
self.spinnyNode.hidden = NO;
self.spinnyNode.physicsBody.restitution = 0.8;
self.spinnyNode.physicsBody.linearDamping = 0.0;
self.spinnyNode.physicsBody.angularDamping = 0.3;
self.spinnyNode.physicsBody.friction = 0.1;
self.spinnyNode.physicsBody.angularVelocity = -100.0;
}
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
NSLog(#"contact detected");
NSString *nameA = contact.bodyA.node.name;
NSString *nameB = contact.bodyB.node.name;
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
//Your first body is the block, secondbody is the player.
//Implement relevant code here.
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
for (UITouch *t in touches) {[self touchDownAtPoint:[t locationInNode:self]];
}
}
-(void)update:(CFTimeInterval)currentTime {
// Called before each frame is rendered
}
#end
This is my first time working with SpriteKit. So please be patient and help me out.
If you want to get the callback to didBeginContact when they touch you will need to set the contact bit mask like this:
self.spinnyNode.physicsBody.contactTestBitMask = worldCategory;
currently you are setting it to 0x0 which means not to report any contacts to the delegate. You also need to set the delegate on the scene:
self.physicsWorld.contactDelegate = self;
UPDATE
You also need to put the lines:
self.spinnyNode.physicsBody.contactTestBitMask = worldCategory;
self.spinnyNode.physicsBody.categoryBitMask = birdCategory;
into the touchDownAtPoint method after you create the physicsBody with:
self.spinnyNode.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:40];
otherwise they will be reset when you create the physicsBody.

Objective C - SpriteKit - Unable to detect contact between ball and paddle node

I am trying to detect the collision between the ball node and either one of the paddle nodes but the message to confirm the collision is not being fired.
Could somebody help me understand where I am going wrong?
//categories for detecting contacts between nodes
static const uint32_t ballCategory = 0x1 << 0;
static const uint32_t paddleCategory = 0x1 << 1;
#interface GameScene ()
#property BOOL contentCreated;
#property(nonatomic) UITouch *playerOnePaddleControlTouch;
#property(nonatomic, weak) UITouch *paddleTouch;
#property(nonatomic) SKSpriteNode *paddleOneNode;
#property(nonatomic) SKSpriteNode *paddleTwoNode;
#property(nonatomic) SKSpriteNode *ballNode;
#property(nonatomic) SKLabelNode *playerOneScoreNode;
#property(nonatomic) SKLabelNode *playerTwoScoreNode;
#property(nonatomic) NSInteger playerOneScore;
#property(nonatomic) NSInteger playerTwoScore;
#end
#implementation GameScene
- (void)didMoveToView:(SKView *)view
{
if (!self.contentCreated)
{
[self createSceneContents];
self.contentCreated = YES;
}
}
- (void) createSceneContents
{
self.backgroundColor = [SKColor blackColor];
self.scaleMode = SKSceneScaleModeAspectFit;
[self addChild: [self newGameNode]];
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsWorld.contactDelegate = self;
// Create border around screen
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0;
// Create Paddle One
SKSpriteNode *paddleOne = [self newPaddle];
paddleOne.position = CGPointMake(CGRectGetMidX(self.frame)/8, CGRectGetMidY(self.frame));
paddleOne.name = #"paddleOne";
paddleOne.physicsBody.categoryBitMask = paddleCategory;
paddleOne.physicsBody.contactTestBitMask = ballCategory;
[self addChild:paddleOne];
// Create Paddle Two
SKSpriteNode *paddleTwo = [self newPaddle];
paddleTwo.position = CGPointMake((CGRectGetMaxX(self.frame) - CGRectGetMidX(self.frame) / 8), CGRectGetMidY(self.frame));
paddleTwo.name = #"paddleTwo";
paddleTwo.physicsBody.categoryBitMask = paddleCategory;
paddleTwo.physicsBody.contactTestBitMask = ballCategory;
[self addChild:paddleTwo];
// Create ball
SKSpriteNode *ball = [self newBall];
ball.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
[self addChild:ball];
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.frame.size.width/2];
ball.physicsBody.friction = 1.0f; //normally 1.0f
ball.physicsBody.restitution = 1.0f; //normally 1.0f
ball.physicsBody.linearDamping = 0.0f; //normally 0.0f
ball.physicsBody.allowsRotation = NO;
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = paddleCategory;
[ball.physicsBody applyImpulse:CGVectorMake(1.0f, -1.0f)];
// Create score labels
self.playerOneScoreNode = [SKLabelNode labelNodeWithFontNamed:#"Helvetica"];
self.playerTwoScoreNode = [SKLabelNode labelNodeWithFontNamed:#"Helvetica"];
self.playerOneScoreNode.fontColor = self.playerOneScoreNode.fontColor = [SKColor whiteColor];
self.playerOneScoreNode.fontSize = self.playerTwoScoreNode.fontSize = 90;
self.playerOneScoreNode.position = CGPointMake((CGRectGetWidth(self.frame))* 0.25, (CGRectGetHeight(self.frame)) - 80);
self.playerTwoScoreNode.position = CGPointMake((CGRectGetWidth(self.frame)) * 0.75, (CGRectGetHeight(self.frame)) - 80);
[self addChild:self.playerOneScoreNode];
[self addChild:self.playerTwoScoreNode];
// Set Scores to 0
self.playerOneScore = 7;
self.playerTwoScore = 0;
self.playerOneScoreNode.text = [NSString stringWithFormat:#"%ld",self.playerOneScore];
self.playerTwoScoreNode.text = [NSString stringWithFormat:#"%ld",self.playerTwoScore];
}
- (SKSpriteNode *)newPaddle
{
SKSpriteNode *paddle = [[SKSpriteNode alloc]initWithColor:[SKColor whiteColor] size:CGSizeMake(16,64)];
return paddle;
}
- (SKSpriteNode *)newBall
{
SKSpriteNode *ball = [[SKSpriteNode alloc]initWithColor:[SKColor redColor] size:CGSizeMake(16, 16)];
return ball;
}
- (SKLabelNode *) newGameNode
{
SKLabelNode *gameNode = [SKLabelNode labelNodeWithFontNamed:#"Chalkduster"];
gameNode.text = #" Pong";
gameNode.fontSize = 50;
gameNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
gameNode.fontColor = [SKColor blueColor];
return gameNode;
}
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
self.paddleTouch = [touches anyObject];
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self];
SKNode *paddleOne = [self childNodeWithName:#"paddleOne"];
SKNode *paddleTwo = [self childNodeWithName:#"paddleTwo"];
if (touchPoint.x < CGRectGetMidX(self.frame)) {
paddleOne.position = CGPointMake(paddleOne.position.x, touchPoint.y);
}
else
{
paddleTwo.position = CGPointMake(paddleTwo.position.x, touchPoint.y);
}
}
- (void)touchesMoved:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event
{
if (self.paddleTouch) {
UITouch *touch = [touches anyObject];
CGPoint touchPoint = [touch locationInNode:self];
CGPoint previousPoint = [touch previousLocationInNode:self];
SKSpriteNode *paddleOne = (SKSpriteNode*)[self childNodeWithName:#"paddleOne"];
SKSpriteNode *paddleTwo = (SKSpriteNode*)[self childNodeWithName:#"paddleTwo"];
int paddleOneY = paddleOne.position.y + (touchPoint.y - previousPoint.y);
int paddleTwoY = paddleTwo.position.y + (touchPoint.y - previousPoint.y);
if (touchPoint.x < CGRectGetMidX(self.frame)) {
paddleOne.position = CGPointMake(paddleOne.position.x, paddleOneY);
}
else
{
paddleTwo.position = CGPointMake(paddleTwo.position.x, paddleTwoY);
}
}
}
// React to collision's between nodes/bodies
// Currently not working....need to understand this set of code.
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody;
SKPhysicsBody *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if (firstBody.categoryBitMask == ballCategory && secondBody.categoryBitMask == paddleCategory)
{
NSLog(#"Ball has touched Paddle");
}
}
#end
Your paddleOne and paddleTwo does not have SKPhysicsBody.
Add these (change size of physics body if needed)
paddleOne.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: paddleOne.size];
paddleTwo.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize: paddleTwo.size];

Sprite not keeping track of count

The following code I'm using is to end the game when I my number of balls in the scene reaches zero. Now I'm not getting any error when I put the code in but it doesn't do any thing as of counting the balls and reacting to that count. I am fairly new to sprite kit so I understand that this is a problem that comes so easy to others but this is the first time of worked with reacting to the count of a certain sprite in a scene so please help.
SKSpriteNode *ball;
int numberOfBalls = 3;
#implementation EasyScene
-(void)didBeginContact:(SKPhysicsContact *)contact {
if ([self isGameWon]) {
EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
[self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
}
}
-(BOOL)isGameWon {
int numberOfBalls = 3;
for (SKNode* node in self.children) {
if ([node.name isEqual: ball]) {
numberOfBalls = 0;
}
}
return numberOfBalls =0;
}
- (void) addBalls:(CGSize)size {
for (int i = 0; i < 3; i++) {
//create brick sprite from image
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball-image"];
ball.name = #"ball";
//resize balls
ball.size = CGSizeMake(self.size.width/5.5, self.size.width/5.5);
//position and space out balls
int xPos = size.width/3 * (i+.5);
int yPos = self.size.height - (self.size.width/7);
ball.position = CGPointMake(xPos, yPos);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = bottomEdgeCategory;
[self addChild:ball];
}
}
In the following code I got rid of the Global Variable and tested all nodes for the specified name of #"ball" and return YES if none were found:
static const uint32_t ballCategory = 1; //00000000000000000000000000000001
static const uint32_t edgeCategory = 2; //00000000000000000000000000000010
static const uint32_t bottomEdgeCategory = 4; //00000000000000000000000000000100
SKSpriteNode *ball;
#implementation EasyScene
-(void)didBeginContact:(SKPhysicsContact *)contact {
//create placeholder for the "non ball" object
SKPhysicsBody *notTheBall;
SKPhysicsBody *theBall;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
notTheBall = contact.bodyB;
theBall = contact.bodyA;
} else {
notTheBall = contact.bodyA;
theBall = contact.bodyB;
}
if (notTheBall.categoryBitMask == bottomEdgeCategory) {
NSLog(#"hit bottom edge");
// SKAction *playSFX = [SKAction playSoundFileNamed:#"gameover.mp3" waitForCompletion:NO];
// [self runAction:playSFX];
// EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
// [self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
// [GameState sharedInstance].score = 0;
// [gameMusic pause];
[theBall.node removeFromParent];
}
if ([self isGameWon]) {
EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
[self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
}
}
-(BOOL)isGameWon {
unsigned count = 0;
for (SKNode* node in self.children)
if ([node.name isEqual:ball])
count++;
return count == 0;
}
- (void) addBalls:(CGSize)size {
for (int i = 0; i < 3; i++) {
//create brick sprite from image
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball-image"];
ball.name = #"ball";
//resize balls
ball.size = CGSizeMake(self.size.width/5.5, self.size.width/5.5);
//position and space out balls
int xPos = size.width/3 * (i+.5);
int yPos = self.size.height - (self.size.width/7);
ball.position = CGPointMake(xPos, yPos);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = bottomEdgeCategory;
[self addChild:ball];
}
}
Remove the ball global variable, as it's not needed.
Test all nodes for the specified name of #"ball" and return YES if none were found:
-(BOOL)isGameWon {
for (SKNode* node in self.children)
if ([node.name isEqualToString:#"ball"])
return NO;
return YES;
}
I think it should be
return numberOfBalls == 0
in your isgameWon method
you forget an extra =

Prevent player from falling through the ground - Sprite Kit

I've been trying to work on a simple Sprite Kit game that involves dodging red balls. I'm using the built-in gravity mechanism, but I'm having trouble preventing the player from falling through the ground. I've looked up a solution (set ground.physicsBody.dynamic = NO), but the player still falls through. What exactly do I need to do?
Edit: The green and brown texture is the ground. Right now the player is set to not being dynamic, so it is 'flying'
Here is my code in the MyScene.m file:
//
// MyScene.m
// DodgeMan
//
// Created by Cormac Chester on 3/8/14.
// Copyright (c) 2014 Testman Industries. All rights reserved.
//
#import "MyScene.h"
#import "EndGameScene.h"
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//Sets player location
playerLocX = 50;
playerLocY = 100;
//Sets player score
score = 0;
//Set Background
self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];
//Set Ground
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:#"ground"];
ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
ground.xScale = 0.5;
ground.yScale = 0.5;
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.dynamic = NO;
//Player
self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:#"character"];
self.playerSprite.position = CGPointMake(playerLocX, playerLocY);
//Set Player Physics
self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
self.playerSprite.physicsBody.dynamic = YES;
self.playerSprite.physicsBody.categoryBitMask = playerCategory;
self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
self.playerSprite.physicsBody.collisionBitMask = 0;
self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;
//Score Label
self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial-BoldMT"];
self.scoreLabel.text = #"0";
self.scoreLabel.fontSize = 40;
self.scoreLabel.fontColor = [SKColor blackColor];
self.scoreLabel.position = CGPointMake(50, 260);
//Pause Button
self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"pauseButton"];
self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
self.pauseButton.name = #"pauseButton";
//Add nodes
[self addChild:ground];
[self addChild:self.playerSprite];
[self addChild:self.scoreLabel];
//[self addChild:self.pauseButton];
//Sets gravity
self.physicsWorld.gravity = CGVectorMake(0,-2);
self.physicsWorld.contactDelegate = self;
}
return self;
}
-(void)addBall
{
SKSpriteNode *redBall = [SKSpriteNode spriteNodeWithImageNamed:#"locationIndicator"];
int minY = redBall.size.height / 2;
int maxY = self.frame.size.height - redBall.size.height / 2;
int rangeY = maxY - minY;
int actualY = (arc4random() % rangeY) + minY;
NSLog(#"Actual Y: %i", actualY);
//Initiates red ball offscreen
if (actualY >= 75)
{
//Prevents balls from spawning in the ground
redBall.position = CGPointMake(self.frame.size.width + redBall.size.width/2, actualY);
[self addChild:redBall];
}
redBall.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:redBall.size.width/2];
redBall.physicsBody.dynamic = YES;
redBall.physicsBody.categoryBitMask = redBallCategory;
redBall.physicsBody.contactTestBitMask = playerCategory;
redBall.physicsBody.collisionBitMask = 0;
redBall.physicsBody.affectedByGravity = NO;
redBall.physicsBody.usesPreciseCollisionDetection = YES;
//Determine speed of red ball
int minDuration = 3.0;
int maxDuration = 5.0;
int rangeDuration = maxDuration - minDuration;
int actualDuration = (arc4random() % rangeDuration) + minDuration;
// Create the actions
SKAction *actionMove = [SKAction moveTo:CGPointMake(-redBall.size.width/2, actualY) duration:actualDuration];
SKAction *actionMoveDone = [SKAction removeFromParent];
SKAction *ballCross = [SKAction runBlock:^{
score++;
self.scoreString = [NSString stringWithFormat:#"%i", score];
self.scoreLabel.text = self.scoreString;
NSLog(#"Score was incremented. Score is now %d", score);
}];
[redBall runAction:[SKAction sequence:#[actionMove, ballCross, actionMoveDone]]];
}
- (void)updateWithTimeSinceLastUpdate:(CFTimeInterval)timeSinceLast
{
self.lastSpawnTimeInterval += timeSinceLast;
if (self.lastSpawnTimeInterval > 0.5) {
self.lastSpawnTimeInterval = 0;
[self addBall];
}
}
-(void)update:(CFTimeInterval)currentTime
{
/* Called before each frame is rendered */
// Handle time delta.
//Prevents bad stuff happening
CFTimeInterval timeSinceLast = currentTime - self.lastUpdateTimeInterval;
self.lastUpdateTimeInterval = currentTime;
if (timeSinceLast > 1) { // more than a second since last update
timeSinceLast = 1.0 / 120.0;
self.lastUpdateTimeInterval = currentTime;
}
[self updateWithTimeSinceLastUpdate:timeSinceLast];
}
NSDate *startTime;
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
/* Called when a touch begins */
[super touchesBegan:touches withEvent:event];
//Starts Timer
startTime = [NSDate date];
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//Pauses Scene
if ([node.name isEqualToString:#"pauseButton"])
{
NSLog(#"Pause button pressed");
}
}
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
/* Called when a touch ends */
[super touchesEnded:touches withEvent:event];
NSTimeInterval elapsedTime = [startTime timeIntervalSinceNow];
NSString *elapsedTimeString = [NSString stringWithFormat:#"Elapsed time: %f", elapsedTime];
NSLog(#"%#", elapsedTimeString);
for (UITouch *touch in touches)
{
//Gets location of touch
CGPoint location = [touch locationInNode:self];
NSLog(#"Touch Location X: %f \n Touch Location Y: %f", location.x, location.y);
//Prevents destination from being in the ground
if (location.y < 88)
{
location.y = 87.5;
}
//Moves and animates player
//int velocity = elapsedTime * -3000;
int velocity = 800.0/1.0;
NSLog(#"Velocity: %i", velocity);
float realMoveDuration = self.size.width / velocity;
SKAction *actionMove = [SKAction moveTo:location duration:realMoveDuration];
[self.playerSprite runAction:[SKAction sequence:#[actionMove]]];
}
NSLog(#"Touch ended");
}
//Collision between ball and player
- (void)redBall:(SKSpriteNode *)redBall didCollideWithPlayer:(SKSpriteNode *)playerSprite
{
NSLog(#"Player died");
[redBall removeFromParent];
[playerSprite removeFromParent];
SKTransition *reveal = [SKTransition crossFadeWithDuration:0.5];
SKScene *endGameScene = [[EndGameScene alloc] initWithSize:self.size gameEnded:YES];
[self.view presentScene:endGameScene transition: reveal];
}
- (void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
//Red ball collides with the player
if ((firstBody.categoryBitMask & redBallCategory) != 0 && (secondBody.categoryBitMask & playerCategory) != 0)
{
[self redBall:(SKSpriteNode *) firstBody.node didCollideWithPlayer:(SKSpriteNode *) secondBody.node];
}
}
#end
you definitely can't set its dynamic to no brother. you need gravity effect that player. (he is not effected by physic world so he is flying right now. we need him to fall down to ground aren't we? :)
So here is the simple solution. idea is that you create a "invisible rectangle block" on the ground surface that has physic body. and you need to set its dynamic to no in order to prevent it falling down
so this block is a node obviously, and its size : as high as the ground , and as wide as the screen. and you need to adjust the position a little bit to put its upper bound right on the ground surface.
good luck
i actually drew a picture but i can't post it here because of my reputation :(
Your problem is with physicsBody's categoryBitMask and collisionTestBitMask.
Your bitwise declarations :
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
This has actually set the following bit patterns (i've shortened to 8 bits for the example) :
redBallCategory - 00000001 and
playerCategory - 00000010
However in the following code, you tell the player to only collide with collision bit mask - 00000000;
self.playerSprite.physicsBody.collisionBitMask = 0;
So your first problem is here. The player will not collide with any category that you have defined.
Your second problem is you have not given the ground a categoryBitMask, or collisionBitMask. By default this means all bits are set, IE the ground's collisionBitMask is equal to 11111111;
There will be no collision between these two physics bodies.
Try this - i have simply added a third physics category, and edited your code slightly to set the ground categoryBitMask / collisionBitMask, and your player collisionBitMask.
static const uint32_t redBallCategory = 0x1 << 0;
static const uint32_t playerCategory = 0x1 << 1;
static const uint32_t groundCategory = 0x1 << 2;
#implementation MyScene
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
/* Setup your scene here */
//Sets player location
playerLocX = 50;
playerLocY = 100;
//Sets player score
score = 0;
//Set Background
self.backgroundColor = [SKColor colorWithRed:0.53 green:0.81 blue:0.92 alpha:1.0];
//Set Ground
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:#"ground"];
ground.position = CGPointMake(CGRectGetMidX(self.frame), 34);
ground.xScale = 0.5;
ground.yScale = 0.5;
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.categoryBitMask=groundCategory;
ground.physicsBody.collisionBitMask=playerCategory|redBallCategory;
ground.physicsBody.dynamic = NO;
//Player
self.playerSprite = [SKSpriteNode spriteNodeWithImageNamed:#"character"];
self.playerSprite.position = CGPointMake(playerLocX, playerLocY);
//Set Player Physics
self.playerSprite.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.playerSprite.size];
self.playerSprite.physicsBody.dynamic = YES;
self.playerSprite.physicsBody.categoryBitMask = playerCategory;
self.playerSprite.physicsBody.contactTestBitMask = redBallCategory;
self.playerSprite.physicsBody.collisionBitMask = groundCategory|redBallCategory;
self.playerSprite.physicsBody.usesPreciseCollisionDetection = YES;
//Score Label
self.scoreLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial-BoldMT"];
self.scoreLabel.text = #"0";
self.scoreLabel.fontSize = 40;
self.scoreLabel.fontColor = [SKColor blackColor];
self.scoreLabel.position = CGPointMake(50, 260);
//Pause Button
self.pauseButton = [SKSpriteNode spriteNodeWithImageNamed:#"pauseButton"];
self.pauseButton.position = CGPointMake(self.frame.size.width / 2, self.frame.size.height - 40);
self.pauseButton.name = #"pauseButton";
//Add nodes
[self addChild:ground];
[self addChild:self.playerSprite];
[self addChild:self.scoreLabel];
//[self addChild:self.pauseButton];
//Sets gravity
self.physicsWorld.gravity = CGVectorMake(0,-2);
self.physicsWorld.contactDelegate = self;
}
return self;
}
Just:
self.playerSprite.physicsBody.dynamic = NO;
should work.
Your problem occurs due to scaling. For some reason in sprite kit scaling an image doesn't change it's size when used in following code. Judging by your pictures, your physics body rectangle for your ground is actually twice as big as you think and already engulfing the player, which is why there would by no collision detection. This is from recent experience with a very similar style game.
Do you have an edge loop physcis body around the scene? Collision flags and category flags set correctly so the player collides with the ground?
i had same problem and i soved it simply...
On the update method i've putted an if statement:
if(player.position.y<your_closest_value_near_ground){
player.position.y == your_Closest_value_near_ground
}
The comparition differs by the anchor point you have.. hope it helps someone

How can I stop walking of a man if there is a wall in Sprite Kit?

I am practicing with Sprite Kit in iOS 7.
Now I am getting a problem while a man walking from one point to another point by SKAction moveTo.
If there is any wall in the screen, I want to stop walking before the wall.
I used the code in skscen.h file like below:
typedef NS_ENUM(uint32_t, CollisionType)
{
CollisionTypePlayer = 0x1 << 0,
CollisionTypeWall = 0x1 << 1,
CollisionTypeRiver = 0x1 << 3,
CollisionTypeBush = 0x1 << 4
};
#import <SpriteKit/SpriteKit.h>
#interface THMyScene : SKScene <SKPhysicsContactDelegate>
#property (nonatomic) SKSpriteNode *manContainer;
//Wall sprite
#property (nonatomic) SKSpriteNode *wallNode1;
#end
and in the scene.m file given below:
#import "THMyScene.h"
#interface THMyScene()
#property (nonatomic) SKTextureAtlas *manAtlas;
#property (nonatomic) NSMutableArray *manImagesArray;
#end
#implementation THMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
// Configure physics for the world.
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f); // no gravity
self.physicsWorld.contactDelegate = self;
[self createWall];
[self createMan];
}
return self;
}
-(void) createMan
{
_manAtlas = [SKTextureAtlas atlasNamed:#"secondLevelMan"];
_manImagesArray = [[NSMutableArray alloc]init];
for(int i = 1; i <= _manAtlas.textureNames.count ; i++)
{
NSString *imageName = [NSString stringWithFormat:#"man%d.png",i];
SKTexture *tmpTexture = [SKTexture textureWithImageNamed:imageName];
[_manImagesArray addObject:tmpTexture];
}
_manContainer = [SKSpriteNode spriteNodeWithTexture:[_manImagesArray objectAtIndex:0]];
_manContainer.position = CGPointMake(50, 250);
[self addChild:_manContainer];
_manContainer.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_manContainer.size];
_manContainer.physicsBody.allowsRotation = YES;
_manContainer.physicsBody.dynamic = YES;
_manContainer.physicsBody.usesPreciseCollisionDetection = YES;
_manContainer.physicsBody.categoryBitMask = CollisionTypePlayer;
_manContainer.physicsBody.contactTestBitMask = CollisionTypeWall;
_manContainer.physicsBody.collisionBitMask = CollisionTypeRiver;
}
-(void) createWall
{
//Create wall 1
_wallNode1 = [SKSpriteNode spriteNodeWithImageNamed:#"rock-1"];
_wallNode1.name = #"stonewall";
_wallNode1.position = CGPointMake(400 , 700);
[self addChild:_wallNode1];
_wallNode1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_wallNode1.size];
_wallNode1.physicsBody.dynamic = NO;
_wallNode1.physicsBody.categoryBitMask = CollisionTypeWall;
_wallNode1.physicsBody.contactTestBitMask = 0;
_wallNode1.physicsBody.collisionBitMask = CollisionTypePlayer;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint locationTouch = [touch locationInNode:self];
CGPoint manLocation = _manContainer.position;
CGFloat rotatingAngle = [self pointPairToBearingDegrees:manLocation secondPoint:locationTouch];
CGFloat distance = [self distanceBetweenTwoPoints:manLocation andSecondpoint:locationTouch];
SKAction *animationAction = [SKAction repeatActionForever:[SKAction animateWithTextures:_manImagesArray timePerFrame: 0.0833]];
float duration = distance / 300;
SKAction *movePoint = [SKAction moveTo:locationTouch duration:duration];
SKAction *rotate = [SKAction rotateToAngle:rotatingAngle duration:0.0f];
SKAction *sequenceAction = [SKAction sequence:#[rotate, movePoint]];
[_manContainer runAction:[SKAction group:#[sequenceAction, animationAction]] withKey:#"runAnimation"];
}
- (CGFloat) pointPairToBearingDegrees:(CGPoint)startingPoint secondPoint:(CGPoint) endingPoint
{
CGPoint originPoint = CGPointMake(endingPoint.x - startingPoint.x, endingPoint.y - startingPoint.y); // get origin point to origin by subtracting end from start
float bearingRadians = atan2f(originPoint.y, originPoint.x); // get bearing in radians
return bearingRadians;
}
-(CGFloat)distanceBetweenTwoPoints:(CGPoint)firstPoint andSecondpoint:(CGPoint)secondPoint
{
CGFloat dx = secondPoint.x - firstPoint.x;
CGFloat dy = secondPoint.y - firstPoint.y;
return sqrt(dx*dx + dy*dy );
}
- (void) didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
}
#end
try something like this:
-(void)update:(CFTimeInterval)currentTime {
int distance = 10; // distance to wall when to stop
if ((man.position.x > wall.position.x + wall.size.width/2 + distance) &&
(man.position.x < wall.position.x - wall.size.width/2 - distance)
if ((man.position.y > wall.position.y + wall.size.height/2 + distance) &&
(man.position.y < wall.position.y - wall.size.height/2 - distance)
//do what you want like remove some or all actions of man
} }
//change your code this would work
- (void) didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
[firstBody.node removeAllActions];
firstBody.node.speed=0;
}
else
{
firstBody = contact.bodyB;
secondBody = contact.bodyA;
[secondBody.node removeAllActions];
secondBody.node.speed=0;
}
}
//
remember every node in spritekit have speed property which control sprite animation speed

Resources