bug with regular collisions in spriteKit - ios

Im making a very simple jumping game. When my "hero" collides with the ground it calls didBeginContact and make a jump. Pretty basic but it doesn't work well because after a while (sometimes sooner, sometimes later...) it stop working as you can see. I would like to know if Im doing something wrong or if there is a better way to do it, or if there is a known bug in spriteKit Id never heard of...
The image is a gif. If can't see movement open it in a new tab
#interface XYZWorld1()
#property (nonatomic) SKSpriteNode *hero;
#property (nonatomic) SKSpriteNode *groundPhysics;
#end
#implementation XYZWorld1
static const uint32_t ground = 0x2 << 2;
static const uint32_t bee = 0x3 << 3;
Method to create the ground:
-(SKSpriteNode*)createGroundxPos:(float)x yPos:(float)y width:(CGFloat)width height:(CGFloat)height
{
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithColor:[UIColor colorWithHue:1 saturation:1 brightness:1 alpha:1] size:CGSizeMake(width, height)];
SKPhysicsBody *groundFisica =[SKPhysicsBody bodyWithRectangleOfSize:ground.size];
[ground setName:#"ground"];
[groundFisica setDynamic:NO];
[groundFisica setAffectedByGravity:NO];
[groundFisica setAllowsRotation:NO];
[groundFisica setRestitution:0];
[groundFisica setUsesPreciseCollisionDetection:YES];
[groundFisica setCategoryBitMask:ground];
[groundFisica setContactTestBitMask:bee];
[groundFisica setCollisionBitMask:bee];
ground.physicsBody = groundFisica;
[ground setPosition:CGPointMake(x, y)];
return ground;
}
Method to create hero
-(SKSpriteNode*)crearHero
{
SKTexture *temp = _heroWalkingFrames[0];
SKSpriteNode *hero = [SKSpriteNode spriteNodeWithTexture:temp];
hero.name = #"hero";
hero.zPosition = 1;
if(IS_IPHONE_5){
[hero setPosition:CGPointMake(100, 280)];
}
else {
[hero setPosition:CGPointMake(100, 220)];
}
[hero setSize:CGSizeMake(25, 25)];
hero.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(23, 24)];
[hero.physicsBody setDensity:0.95];
[hero.physicsBody setDynamic:YES];
[hero.physicsBody setAffectedByGravity:YES];
[hero.physicsBody setAllowsRotation:NO];
[hero.physicsBody setUsesPreciseCollisionDetection:YES];
[hero.physicsBody setRestitution:0];
[hero.physicsBody setVelocity:CGVectorMake(0, 0)];
return hero;
}
Method to preload all the stuff before the game starts:
-(void)preloadAssets
{
//hero
_heroWalkingFrames = [self cargarArrayAtlas:#"astroJump.atlas" imageName:#"astronautaJump_000"];
_hero = [self crearHero];
_hero.physicsBody.categoryBitMask = bee;
_hero.physicsBody.collisionBitMask = bee | cubico | ground | bicho ;
_hero.physicsBody.contactTestBitMask = bee | cubico | ground | bicho ;
//Ground
_groundPhysics = [self createGroundxPos:100 yPos:142 width:42 height:23.25];
}
Method called when two bodies first contact each other:
-(void) didBeginContact:(SKPhysicsContact *)contact
{
//if hero collides with ground
if((contact.bodyA.categoryBitMask == bee && contact.bodyB.categoryBitMask == ground) ||
(contact.bodyA.categoryBitMask == ground && contact.bodyB.categoryBitMask == bee) )
{
if(_jumping == NO && _groundPhysics.position.y+_groundPhysics.size.height/2<=_hero.position.y-11.5)
{
[self jumping];
}
}
}
Method called to do the jump:
-(void)jumping{
//Play sound file
[self runAction:self.jumpSound withKey:#"jumpSound"];
if(IS_IPHONE_5){
_diferencia=(164 * 9)/_hero.position.y;
}else{
_diferencia=(164 * 7)/_hero.position.y;
}
[_hero.physicsBody applyImpulse:CGVectorMake(0,_diferencia)];
_jumping = YES;
[_hero removeActionForKey:#"astroJump"];
[self astroJump];
}
-(void)astroJump
{
//runAction animation method
[_hero runAction:[SKAction animateWithTextures:_heroWalkingFrames
timePerFrame:0.05f
resize:NO
restore:NO] withKey:#"astroJump"];
return;
}
the boolean (self.jumping) from the if sentence, in update and initGameContent:
-(void)update:(CFTimeInterval)currentTime
{
if(_hero.physicsBody.velocity.dy < 0 )_jumping = NO;
}
-(void)initGameContent
{
_jumping = NO;

You might want to make sure you reset your players position to be above the ground when contact with the ground is detected. Otherwise the player could still be in contact with the ground when [self jumping]; is called which can sometimes result in the player being "stuck". I've seen similar issues in my games when doing collision detection. The code below shows what you could do to resolve this issue.
if (_jumping == NO && _groundPhysics.position.y+_groundPhysics.size.height/2<=_hero.position.y-11.5)
{
_hero.position.y = _groundPhysics.position.y + _groundPhysics.size.height/2 + 1;
[self jumping];
}

I finally found a way to fix it. I've changed the size of physicbody.size.y to the same as his spriteNode.y and the bug has gone.

Related

spritekit didBeginContact not called

didBeginContact doesnt get called for some reason. How can I fix this? Thanks! I set the category bitmasks and the skphysicsContactDelegate, yet it is still not registering contacts. I've been stuck at this for some time now.
#import "MyScene.h"
#import "FuelNode.h"
#import "SKSpriteNode+DebugDraw.h"
typedef NS_OPTIONS(uint32_t, CollisionCategory) {
CollisionCategoryPlayer = 1 << 0,
CollisionCategoryFuel = 1 << 1,
};
#interface MyScene() <SKPhysicsContactDelegate>
#end
#implementation MyScene
{
SKNode *_playerNode;
SKNode *_backgroundNode;
SKNode *_foreGround;
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size]) {
self.physicsWorld.contactDelegate = self;
_backgroundNode = [self createBackground];
[self addChild:_backgroundNode];
_foreGround = [SKNode node];
[self addChild:_foreGround];
//add a fuelNode
FuelNode *fuel = [self createFuelAtPosition:CGPointMake(160, 440)];
[_foreGround addChild:fuel];
_playerNode = [self createPlayer];
[_foreGround addChild:_playerNode];
SKAction *actionMove = [SKAction moveToY:-100 duration:3.0];
[fuel runAction:actionMove];
NSLog(#"yea");
}
return self;
}
-(SKNode *)createPlayer
{
CGSize playerPhysicsBody;
//Create player
SKNode *playerNode = [SKNode node];
SKSpriteNode *player = [SKSpriteNode spriteNodeWithImageNamed:#"wship-3.png"];
player.position = CGPointMake(self.size.width/2, 120);
[playerNode addChild:player];
//Add physics
playerPhysicsBody = CGSizeMake(player.size.width/2, player.size.height/2);
playerNode.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:playerPhysicsBody];
playerNode.physicsBody.dynamic = NO;
//Setup collision settings
playerNode.physicsBody.usesPreciseCollisionDetection = YES;
playerNode.physicsBody.categoryBitMask = CollisionCategoryPlayer;
playerNode.physicsBody.collisionBitMask = 0;
playerNode.physicsBody.contactTestBitMask = CollisionCategoryFuel;
[player attachDebugRectWithSize:playerPhysicsBody];
return playerNode;
}
-(SKNode *)createBackground
{
//Create background
SKNode *bgNode = [SKNode node];
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"purple"];
bg.anchorPoint = CGPointZero;
[bgNode addChild:bg];
return bgNode;
}
- (FuelNode *)createFuelAtPosition:(CGPoint)position
{
// 1
FuelNode *node = [FuelNode node];
[node setPosition:position];
[node setName:#"NODE_FUEL"];
// 2
SKSpriteNode *sprite;
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"fuelBlue"];
[node addChild:sprite];
// 3
CGSize contactSize = CGSizeMake(sprite.size.width/2, sprite.size.height/2);
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:contactSize];
// 4
node.physicsBody.dynamic = NO;
//Setup collision settings
node.physicsBody.categoryBitMask = CollisionCategoryFuel;
node.physicsBody.collisionBitMask = 0;
//node.physicsBody.contactTestBitMask = CollisionCategoryPlayer;
[sprite attachDebugRectWithSize:contactSize];
//SKAction *actionMove = [SKAction moveToY:-100 duration:3.0];
//[node runAction:actionMove];
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
BOOL fuelCollision = NO;
SKNode *other = (contact.bodyA.node != _playerNode) ? contact.bodyA.node : contact.bodyB.node;
NSLog(#"collision");
fuelCollision = [(GameObjectNode *)other collisionWithPlayer:_playerNode];
}
#end
In order to enable contact detection, you need to set
node.physicsBody.dynamic = YES;
Have a look at the documentation as well.
Fixed! In the create player method I have mistakenly set the position of the player sprite instead of setting the position of the player node!

SK Game - didBeginContact/didEndContact isn't getting called

I'm making a drag and drop game, and attempting to use physicsBodies to detect contacts between 1 static object and the object being dragged by the user.
I've searched on here for hours, and watched tutorials online, but all I keep seeing is contact detection working with dynamic nodes (such as projectiles). I don't need collisions because nothing happens to any of the nodes when they intersect, I just need contact detection to implement game logic.
I originally had a working solution using CGRectContainsPoint and CGRectContainsRect, but it's super buggy and after doing some research I realized that it's not the correct way to implement contact/collisions in SK.
I've got my bitMask setup like so:
//contact bit masks
typedef NS_OPTIONS(uint32_t, ContactCategory) {
ContactCategoryTile = 1 << 0, //0000
ContactCategoryKey = 1 << 1, //0010
ContactCategoryRotor = 1 << 2, //0100
};
My selected node setup like so:
#import "TileNode.h"
#import "Utilities.h"
#interface TileNode ()
#end
#implementation TileNode
+(instancetype)tileNodeAtPosition:(CGPoint)position
tileComboScore:(NSInteger)comboScore
tileArray:(NSArray*)array
randomNumber:(int)randomNumber
initialCombo:(NSInteger)initCombo
{
//custom node properties
TileNode *tileNode = [array objectAtIndex:randomNumber];
tileNode.position = position;
tileNode.size = CGSizeMake(23, 63);
//using label property from .h file and parenting to generated tileNode
tileNode.comboLabel = [SKLabelNode labelNodeWithFontNamed:#"Arial"];
tileNode.comboLabel.fontColor = [UIColor whiteColor];
tileNode.comboLabel.text = [NSString stringWithFormat: #"%d",initCombo];
tileNode.comboLabel.fontSize = 12;
tileNode.comboLabel.position = CGPointMake(13, 45);
[tileNode setupPhysicsBody];
[tileNode addChild:tileNode.comboLabel];
return tileNode;
}
-(void)setupPhysicsBody
{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
NSLog(#"tileframe %#", NSStringFromCGRect(self.frame));
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = ContactCategoryTile;
self.physicsBody.contactTestBitMask = ContactCategoryTile | ContactCategoryRotor | ContactCategoryKey;
self.physicsBody.collisionBitMask = 0;
}
My destination node type setup up like so:
#import "KeyNode.h"
#import "Utilities.h"
#implementation KeyNode
+(instancetype)keyNodeAtPosition:(CGPoint)position
{
//custom class properties for generic yellow nodes in UI
KeyNode *keyTile = [KeyNode spriteNodeWithImageNamed:#"key"];
keyTile.position = position;
keyTile.size = CGSizeMake(25, 65);
keyTile.anchorPoint = CGPointMake(0.5, 0.5);
keyTile.name = #"keyNode";
[keyTile setupPhysicsBody];
return keyTile;
}
-(void)setupPhysicsBody
{
self.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:self.frame.size];
NSLog(#"keyframe %#", NSStringFromCGRect(self.frame));
self.physicsBody.dynamic = NO;
self.physicsBody.categoryBitMask = ContactCategoryKey;
self.physicsBody.contactTestBitMask = ContactCategoryTile;
self.physicsBody.collisionBitMask = 0;
}
And my contact detection delegate method like so:
-(void)didEndContact:(SKPhysicsContact *)contact
{
NSLog(#"Working!");
//Condition for tile and key contact
if (contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryKey)
{
NSLog(#"Tile contact with key");
}
//condition for tile and tile contact
else if (contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryTile)
{
NSLog(#"Tile contact with tile");
}
//condition for tile and rotor contact
else if(contact.bodyA.categoryBitMask == ContactCategoryTile && contact.bodyB.categoryBitMask == ContactCategoryRotor)
{
NSLog(#"Tile contact with rotor");
}
}
And both nodes are being instantiated in initWithSize like so:
CGPoint position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame) - 180);
TileNode *tileNode1 = [TileNode tileNodeAtPosition:position tileComboScore:self.comboScore tileArray:tileImagesArray randomNumber:randomTileGenerator initialCombo:self.initialCombo];
[self addChild:tileNode1];
KeyNode *keyNode1 = [KeyNode keyNodeAtPosition:CGPointMake(CGRectGetMidX(self.frame) - 100, CGRectGetMidY(self.frame) - 15)];
[self addChild:keyNode1];
Any help would be greatly appreciated.
I found the answer in a thread from last year that worked. You can work around wanting static bodies to have collisions.
If you set the gravity to be non-existent and make your static nodes dynamic it works:
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsBody.dynamic = YES;

SKScene transition, memory deallocation

Ok, i've read the iOS Games book and i've searched for my question in a number of sites and although I do find that a number of people had this problem, I haven't found a solution as such.
I am building a game where I transition a few times from a SKScene to another SKScene. What happens is that even when I transition from a simple SKScene, as the example bellow, to an empty SKScene the memory does not get deallocated. I've heard that I need to remove any strong references to my SKScene, but I do not believe that my code bellow has any:
#import "LaunchScene.h"
#import "EmptyScene.h"
#implementation LaunchScene
{
float _scaleForDevice;
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_scaleForDevice = 0.5;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone) {
_scaleForDevice = 0.208335;
}
SKSpriteNode *bg;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
bg = [SKSpriteNode spriteNodeWithImageNamed:#"launchBackground-568h"];
} else {
bg = [SKSpriteNode spriteNodeWithImageNamed:#"launchBackground"];
}
bg.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
bg.zPosition = 10;
bg.name = #"launchBackground";
[self addChild:bg];
for (int i = 0; i < 5; i++) {
SKSpriteNode *launch = [SKSpriteNode spriteNodeWithImageNamed:[NSString stringWithFormat:#"launch%d",i]];
launch.position = CGPointMake(self.size.width, 0);
launch.anchorPoint = CGPointMake(0.5, 0.5);
launch.zPosition = 1000+i;
launch.name = [NSString stringWithFormat:#"launch%d",i];
[launch setScale:_scaleForDevice];
[self addChild:launch];
if (i == 0 || i == 2) {
SKAction* rotate = [SKAction rotateByAngle:-RadiansToDegrees(360) duration:10000*(i+1)];
[launch runAction:rotate];
} else if (i == 1) {
SKAction* rotate = [SKAction rotateByAngle:RadiansToDegrees(360) duration:10000*(i+1)];
[launch runAction:rotate];
}
}
SKSpriteNode *mainMenuBackground;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
mainMenuBackground = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuBackground-568h"];
} else {
mainMenuBackground = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuBackground"];
}
mainMenuBackground.position = CGPointMake(CGRectGetMidX(self.frame),
CGRectGetMidY(self.frame));
mainMenuBackground.zPosition = 5;
mainMenuBackground.name = #"mainMenuBackground";
[self addChild:mainMenuBackground];
[SKActionEffects fadeOutAndRemove:bg duration:2];
SKSpriteNode *mainMenuGround;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPhone && IS_WIDESCREEN) {
mainMenuGround = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuGround-568h"];
} else {
mainMenuGround = [SKSpriteNode spriteNodeWithImageNamed:#"mainMenuGround"];
}
mainMenuGround.position = CGPointMake(self.size.width, 0);
mainMenuGround.zPosition = 500;
mainMenuGround.anchorPoint = CGPointMake(1, 1);
mainMenuGround.name = #"mainMenuGround";
[self addChild:mainMenuGround];
SKAction *waitMainMenuGround = [SKAction waitForDuration:1];
SKAction *moveMainMenuGround = [SKAction moveToY:(self.size.height)/3 duration:0.3];
moveMainMenuGround.timingMode = SKActionTimingEaseInEaseOut;
SKAction *shakeMainMenuGround = [SKAction runBlock:^{
[SKActionEffects shakeSprite:mainMenuGround toDirection:1];
}];
SKAction *shrinkLaunch = [SKAction runBlock:^{
for (int i = 0; i < 5; i++) {
SKSpriteNode *launch = (SKSpriteNode*)[self childNodeWithName:[NSString stringWithFormat:#"launch%d",i]];
[SKActionEffects disappearAndRemove:launch];
}
}];
SKAction *presentMainMenu = [SKAction runBlock:^{
//Create and configure the scene.
EmptyScene * scene = [[EmptyScene alloc] initWithSize:self.size];
scene.scaleMode = SKSceneScaleModeAspectFill;
// Present the scene.
[self.view presentScene:scene];
}];
SKAction *group = [SKAction sequence:#[waitMainMenuGround, moveMainMenuGround, shakeMainMenuGround, shrinkLaunch, waitMainMenuGround, presentMainMenu]];
[mainMenuGround runAction:group];
}
return self;
}
#end
I have created an EmptyScene that is just that. It doesn't have anything in its init.
#import "EmptyScene.h"
#implementation EmptyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
}
return self;
}
#end
When I run just the EmptyScene from the start my memory is around 25mb. When I run the LaunchScene first, the memory goes to 95mb, and when it transitions to the EmptyScene, where one would expect the memory to go down to 25mb, it remains at 95mb. Any idea why and what I can do to solve this?

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

sprite kit collision detection with child sprite

I'm trying to detect collisions between two sprites but I'm unable to do this with a child sprite.
self.player = [[Player alloc] initWithImageNamed:#"player"];
self.player.position = CGPointMake(150, 75);
[self addChild:self.player];
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
[self.player addChild:_object];
then I have collision detetion like this
- (void)checkCollisions {
[self.map enumerateChildNodesWithName:#"enemy"
usingBlock:^(SKNode *node, BOOL *stop){SKSpriteNode *enemy = (SKSpriteNode *)node;
if (CGRectIntersectsRect(enemy.frame, _object.frame)) {
[enemy removeFromParent];
}
}]; }
*This does not work!!! but if I use :
CGRectIntersectsRect(enemy.frame, self.player.frame)
I can detect a collision with the main body. how do I do collision detection for a child of another sprite?
The child node's position and frame property are relative to it's parent, which you need to account for in your code.
SKNode has two methods that can help you with converting the position of a given SKNode to/from the coordinate spaces of another node:
convertPoint:fromNode:
convertPoint:toNode:
You can use those to convert a SKNode's position property to another coordinate space. What you want to do is convert the _object's position to the coordinate space of the enemy or visa versa and then use a temporary CGRect with that new position for your CGRectIntersectsRect check.
Here's an example :
CGPoint globalPosition = [self.player convertPoint:_object.position toNode:self.player.parent];
CGRect tempRect = CGRectMake(globalPosition.x, globalPosition.y, _object.frame.size.width, _object.frame.size.height);
if (CGRectIntersectsRect(enemy.frame, _object.frame))
{
// collision occurred
}
This code is assuming that your enemy and your player are in the same coordinate space. (have the same parent)
This happens because _object is a child node for self.player, and enemy node is a child node for self.map node. Both nodes should have same parent if you want to compare their frames.
I suggest you to use Sprite Kit's built-in collision handling mechanism:
// Object:
_object = [SKSpriteNode spriteNodeWithImageNamed:#"object"];
_object.position = CGPointMake(-40, 27);
_object.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:_object.frame.size;
_object.physicsBody.categoryBitMask = OBJECT_CATEGORY;
_object.physicsBody.contactTestBitMask = ENEMY_CATEGORY;
_object.physicsBody.collisionBitMask = 0;
[self.player addChild:_object];
// Enemy:
// init enemy: SKSpriteNode *enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
enemy.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy.frame.size;
enemy.physicsBody.categoryBitMask = ENEMY_CATEGORY;
enemy.physicsBody.contactTestBitMask = OBJECT_CATEGORY;
enemy.physicsBody.collisionBitMask = 0;
// add enemy to the scene: [self addChild:enemy];
// Define that your SKScene conforms to SKPhysicsContactDelegate protocol:
.....
#interface MyScene : SKScene <SKPhysicsContactDelegate>
....
// SKScene.m:
static uint32_t const OBJECT_CATEGORY = 0x1 << 0;
static uint32_t const ENEMY_CATEGORY = 0x1 << 1;
#implementation MyScene
- (id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
.......
self.physicsWorld.contactDelegate = self;
........
}
return self;
}
.......
- (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 & OBJECT_CATEGORY) != 0) &&
(secondBody.categoryBitMask & ENEMY_CATEGORY) != 0) {
NSLog(#"Collision detected!");
}
#end

Resources