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!
Related
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.
We have line(ray) which moves to bounds and reflects when bound is reached. These images demonstrates the dynamic of movement and reflections.
And i'd like to implement it in SpriteKit(obj-c is prefer) but don't understand from which point I should start.
I found how to implement it. Hope it'll be useful for others
#import "GameScene.h"
static const float GUIDE_MASS = .0015;
static const int SEGMENTS_COUNT = 10;
static const int SEGMENT_LENGTH = 5;
#interface GameScene(private)
-(void) createGuidesAndShot;
#end
#implementation GameScene
SKShapeNode *shot;
NSMutableArray* shotSegments;
-(void)didMoveToView:(SKView *)view {
[self setBackgroundColor:[SKColor whiteColor]];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
/* Called when a touch begins */
}
-(void)update:(CFTimeInterval)currentTime {
CGMutablePathRef pathToDraw = CGPathCreateMutable();
if (shotSegments!=nil) {
bool isFirst = YES;
for (SKNode* segment in shotSegments) {
if(isFirst){
CGPathMoveToPoint(pathToDraw, NULL,
segment.position.x,
segment.position.y);
isFirst =NO;
} else {
CGPathAddLineToPoint(pathToDraw, NULL,
segment.position.x,
segment.position.y);
}
}
shot.path = pathToDraw;
}
}
-(void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (shotCategory|screenBoundsCategory)){
}
}
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
shotSegments = [NSMutableArray new];
self.physicsWorld.gravity = CGVectorMake(0.0f, 0.0f);
SKPhysicsBody* borderBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
self.physicsBody = borderBody;
self.physicsBody.friction = 0.0f;
self.physicsBody.categoryBitMask = screenBoundsCategory;
self.physicsBody.contactTestBitMask = shotCategory;
self.physicsWorld.contactDelegate = self;
[self createGuidesAndShot];
}
return self;
}
-(void) createGuidesAndShot{
for (int i = 0; i<SEGMENTS_COUNT*SEGMENT_LENGTH; i+=SEGMENT_LENGTH) {
SKShapeNode* guide = [SKShapeNode shapeNodeWithCircleOfRadius:1];
guide.position = CGPointMake(self.frame.size.width/2+i,
self.frame.size.height/2-i);
// guide.fillColor = [SKColor blackColor];
guide.name = [NSString stringWithFormat:#"guide%i", i];
[self addChild:guide];
[shotSegments addObject:guide];
guide.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:guide.frame.size.width/2];
guide.physicsBody.friction = 0.0f;
guide.physicsBody.restitution = 1.0f;
guide.physicsBody.linearDamping = 0.0f;
guide.physicsBody.allowsRotation = NO;
guide.physicsBody.categoryBitMask = shotCategory;
guide.physicsBody.contactTestBitMask = screenBoundsCategory;
guide.physicsBody.collisionBitMask = screenBoundsCategory;
guide.physicsBody.mass = GUIDE_MASS;
[guide.physicsBody applyImpulse:CGVectorMake(0.1f, -0.1f)];
}
shot = [SKShapeNode node];
[shot setStrokeColor:[UIColor redColor]];
[self addChild:shot];
}
#end
I have prepared my game to be published to the app store. Before I do that, I wanted to turn off the node count and FPS and did not want those two things to be displayed. I have a GameScene.m and a TitleScene.m. I tried view.showsFPS = NO; and view.showsNodeCount = NO; in my GameScene.m and it works fine. In my TitleScene.m I tried self.view.showsNodeCount = NO; and self.view.showsFPS = NO;, but it still shows the NodeCount and FPS. Here is my code for TitleScene.m:
#import "TitleScene.h"
#import "GameScene.h"
#implementation TitleScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
self.view.showsNodeCount = NO;
self.view.showsFPS = NO;
SKTexture *YellowLabelTexture = [SKTexture textureWithImageNamed:#"YellowLabel.png"];
SKTexture *BlueLabelTexture = [SKTexture textureWithImageNamed:#"BlueLabel.png"];
SKTexture *GreenLabelTexture = [SKTexture textureWithImageNamed:#"GreenLabel.png"];
SKTexture *RedLabelTexture = [SKTexture textureWithImageNamed:#"RedLabel.png"];
SKTexture *WhiteLabelTexture = [SKTexture textureWithImageNamed:#"WhiteLabel.png"];
SKSpriteNode *background = [SKSpriteNode spriteNodeWithImageNamed:#"awsome.png"];
background.size = CGSizeMake(640, 1136);
background.position = CGPointMake(0,0);
NSArray *anim = [NSArray arrayWithObjects:YellowLabelTexture, BlueLabelTexture, GreenLabelTexture, RedLabelTexture, WhiteLabelTexture, nil];
SKSpriteNode *labelNode = [SKSpriteNode spriteNodeWithImageNamed:#"WhiteLabel.png"];
labelNode.position = CGPointMake(self.size.width / 2, self.size.height / 2 * 1.5);
SKSpriteNode *startButtonNode = [SKSpriteNode spriteNodeWithImageNamed:#"playButton.png"];
startButtonNode.position = CGPointMake(self.size.width / 2, self.size.height / 3);
SKAction *actionAnimate = [SKAction animateWithTextures:anim timePerFrame:.3 resize:YES restore:NO];
SKAction *actionRepeat = [SKAction repeatActionForever:actionAnimate];
[labelNode runAction:actionRepeat];
[self addChild:background];
[self addChild:labelNode];
[self addChild:startButtonNode];
}
return self;
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
GameScene* gameScene = [[GameScene alloc] initWithSize:self.size];
gameScene.scaleMode = SKSceneScaleModeAspectFill;
[self.view presentScene:gameScene transition:[SKTransition doorsOpenHorizontalWithDuration:1.5]];
}
Is there something that I am doing wrong? Thanks!
The default SpriteKit template sets node count and fps after the initialization of the scene in ViewController.m, you have to remove these lines.
Comment showsFPS and ShowsNodeCount in your ViewController.m file;
//skView.showsFPS = YES;
//skView.showsNodeCount = YES;
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?
I am implementing a simple game for iOS.
I am trying to use Sprite Kit for development.
However, I don't know why the detection of contact did not happen.
Can anyone help me fix this problem?
Here is the code that I cannot get the expected results with:
#import "TesttingScene.h"
#interface TesttingScene()<SKPhysicsContactDelegate>
#property (nonatomic) SKTexture *ballText;
#end
#implementation TesttingScene
-(id)initWithSize:(CGSize)size{
self = [super initWithSize:size];
if (self) {
self.physicsWorld.gravity = CGVectorMake(0, 0);
self.physicsWorld.contactDelegate = self;
SKSpriteNode *hitBoxx = [[SKSpriteNode alloc] initWithColor:[UIColor clearColor] size:CGSizeMake(self.frame.size.width/3, self.frame.size.height/3)];
hitBoxx.anchorPoint = CGPointMake(0, 0);
hitBoxx.position = CGPointMake(self.size.width/2, self.size.height/2);
hitBoxx.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:hitBoxx.frame.size];
hitBoxx.physicsBody.dynamic = YES;
hitBoxx.physicsBody.categoryBitMask = abPlayerHitBoxCategory;
hitBoxx.physicsBody.contactTestBitMask = adsViewCategory;
hitBoxx.physicsBody.collisionBitMask = 0;
hitBoxx.physicsBody.usesPreciseCollisionDetection = YES;
self.ballText = [SKTexture textureWithImageNamed:#"FinalBossSkill1SS"];
}
return self;
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
SKTexture *t1 = [SKTexture textureWithRect:CGRectMake(0, 0, 0.5, 1) inTexture:self.ballText];
SKTexture *t2 = [SKTexture textureWithRect:CGRectMake(0.5, 0, 0.5, 1) inTexture:self.ballText];
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithTexture:t1];
ball.position = CGPointMake(self.size.width, self.size.height/2);
ball.name = #"FinalBossSkill1Ball";
ball.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ball.size];
ball.physicsBody.dynamic = YES;
ball.physicsBody.categoryBitMask = adsViewCategory;
ball.physicsBody.contactTestBitMask = abPlayerAttackBoxCategory | abPlayerHitBoxCategory;
ball.physicsBody.collisionBitMask = 0;
ball.physicsBody.usesPreciseCollisionDetection = YES;
SKAction *moveTo = [SKAction moveToX:-ball.size.width duration:1.0];
SKAction *flash = [SKAction animateWithTextures:#[t1,t2] timePerFrame:0.1];
SKAction *moveBall = [SKAction repeatAction:flash count:moveTo.duration/flash.duration];
SKAction *group = [SKAction group:#[moveTo, moveBall]];
[self addChild:ball];
[ball runAction:[SKAction sequence:#[group, [SKAction removeFromParent]]]];
}
-(void)didBeginContact:(SKPhysicsContact *)contact{
NSLog(#"Contact"); // <~~~~this msg doesn't appear in console when the ball pass the hitboxx.
}
You will need to define bit mask category, right below your imports.
static const uint32_t abPlayerHitBoxCategory = 0x1 << 0;
static const uint32_t adsViewCategory = 0x1 << 1;