SpriteKit detect end of Contact - objective-c

I'm trying to figure out how to detect when the contact between two bodies ends. I'm working on a car game something like alpine crawler and only when the rear wheel is touching the ground the car is able to accelerate.
This is my code for now but it doesn't work proper:
- (void) didBeginContact:(SKPhysicsContact *)contact
{
if (([contact.bodyB.node.name isEqualToString:#"rearWheel"] &&
[contact.bodyA.node.name isEqualToString:#"ground"])) {
isWheelOnGround = YES;
}
}
-(void) didEndContact:(SKPhysicsContact *)contact {
if (([contact.bodyB.node.name isEqualToString:#"rearWheel"] &&
[contact.bodyA.node.name isEqualToString:#"ground"])) {
isWheelOnGround = NO;
}
}

You need to implement the contact delegate correctly and set bitmasks on bodies for the contact notifications to fire up. To do that, put this beneath your imports:
typedef NS_OPTIONS(uint32_t, CNPhysicsCategory) {
CNPhysicsCategoryWheel = 1 << 0, // 0001 = 1
CNPhysicsCategoryGround = 1 << 1, // 0010 = 2
};
#interface YourSceneNameHere() <SKPhysicsContactDelegate>
#end
Then, as you initialize, add the scene as the contact delegate:
self.physicsWorld.contactDelegate = self;
Now, to apply those masks to your bodies - the category they are in, and what category of bodies they will be sending contact notifications for:
wheel.physicsBody.categoryBitMask = CNPhysicsCategoryWheel;
wheel.physicsBody.contactTestBitMask = CNPhysicsCategoryGround;
ground.physicsBody.categoryBitMask = CNPhysicsCategoryGround;
ground.physicsBody.contactTestBitMask = CNPhysicsCategoryWheel;
In the contact method, you have no control over which body is bodyA, and which bodyB:
- (void) didBeginContact:(SKPhysicsContact *)contact {
uint32_t contactTest = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (contactTest == (CNPhysicsCategoryWheel | CNPhysicsCategoryGround)) {
isWheelOnGround = YES;
}
}
Similarly for didEndContact. Good luck!
For another example of this and a thorough explanation, refer to the 'Working with collisions and contacts' section of the SK Programming Guide.

Related

Obj-C and SpriteKit - Changing a sprite value that is created randomly

I'm making a game using SpriteKit and Objective-C.
I have four different texture drops (Blue, Green, Orange and Red) that falls down on screen randomly.
In my ANBDropNode class I have this method:
+(instancetype)dropOfType:(ANBDropType)type {
ANBDropsNode *drop;
if (type == ANBDropTypeBlue) {
drop = [self spriteNodeWithImageNamed:#"bluedrop"];
} else if (type == ANBDropTypeGreen) {
drop = [self spriteNodeWithImageNamed:#"greendrop"];
} else if (type == ANBDropTypeOrange) {
drop = [self spriteNodeWithImageNamed:#"orangedrop"];
} else if (type == ANBDropTypeRed){
drop = [self spriteNodeWithImageNamed:#"reddrop"];
}
[drop setupPhysicsBody];
return drop;
}
And in my GamePlayScene these two:
-(void)addDrops {
NSUInteger randomDrop = [ANBUtil randomWithMin:0 max:4];
self.drop = [ANBDropsNode dropOfType:randomDrop];
float y = self.frame.size.height + self.drop.size.height;
float x = [ANBUtil randomWithMin:10 + self.drop.size.width
max:self.frame.size.width - self.drop.size.width - 10];
self.drop.position = CGPointMake(x, y);
[self addChild:self.drop];
}
-(void)update:(NSTimeInterval)currentTime {
if (self.lastUpdateTimeInterval) {
self.timeSinceDropAdded += currentTime - self.lastUpdateTimeInterval;
}
if (self.timeSinceDropAdded > 1) {
[self addDrops];
self.timeSinceDropAdded = 0;
}
self.lastUpdateTimeInterval = currentTime;
}
The question is (and it may sound a little dumber, I know): before the drop hits the ground it has already changed it value. If ANBDropNode *drop is a bluedrop, before it hits the ground the method randomly create another drop and change it value for greendrop, for example. But I don't want this behavior. I want the drop to continue with its value until it reaches the ground so I can detect its color in my didBeginContact method.
Sorry in advance for any english mistakes, as I'm not an native english speaker.
From your question I understand that the reason you are keeping a reference to the drop (self.drop) is to check what is its colour when it hits the ground.
So you could just delete that, and create a new SKSpriteNode object each time, instead of just changing the reference of the current property.
If you have any other reason for keeping a reference to that drop, just still keep a reference.
Note that doing any of the above, will not affect the code below.
I think that you were in the right direction (when asking about didBeginContact) but took the wrong approach/mindset, since there is no need to keep a reference when using didBeginContact, because you can get the nodes in contact from this method.
Anyway, here is the code + explanations
// MyScene.m
// First, conform to SKPhysicsContactDelegate, so you can get didBeginContact 'calls'
#interface MyScene () <SKPhysicsContactDelegate>
#end
#implementation MyScene
// Now add the following constant, that you'll use as the physics body category bit masks
static const uint32_t groundCategory = 0x01 << 0;
static const uint32_t dropsCategory = 0x01 << 1;
// Somewhere in your initialisation, set yourself to be the physicsWorld
// contact delegate, so you'll receive the didBeginContact 'calls',
// And also call setupGround method, that we will create in here as well
-(id)initSceneWithSize:(CGSize)size {
...
...
self.physicsWorld.contactDelegate = self;
[self setupGround];
...
}
// Here we will create the ground node, so we can detect when a drop
// Hits the ground.
// The reason that, in the below code, I am setting just the ground,
// and not the whole borders of the screen, is because the drops
// are added above the screen 'borders', and if we would make
// a 'frame' node, and not a 'ground' node, we will also receive
// contact delegate calls, when the nodes first enter the scene
// and hits the top 'border' of the frame
-(void)setupGround {
SKNode *groundNode = [SKNode node];
groundNode.strokeColor = [SKColor clearColor];
groundNode.fillColor = [SKColor clearColor];
// Not sure if the above two are mandatory, but better be safe than sorry...
// Here we set the physics body to start at the bottom left edge of the screen
// and be the width of the screen, and the size of 1 points
// Then, we also set its category bit mask
CGRect groundRect = CGRectMake(0, 0, self.size.width, 1);
groundNode.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:groundRect];
groundNode.physicsBody.categoryBitMask = groundCategory;
[self addChild:groundNode];
}
// Next we will modify your current method of creating drops, to also have
// their name property to holds the corresponding colour name
+(instancetype)dropOfType:(ANBDropType)type {
ANBDropsNode *drop;
if (type == ANBDropTypeBlue) {
drop = [self spriteNodeWithImageNamed:#"bluedrop"];
drop.name = #"Blue";
} else if (type == ANBDropTypeGreen) {
drop = [self spriteNodeWithImageNamed:#"greendrop"];
drop.name = #"Green";
} else if (type == ANBDropTypeOrange) {
drop = [self spriteNodeWithImageNamed:#"orangedrop"];
drop.name = #"Orange";
} else if (type == ANBDropTypeRed){
drop = [self spriteNodeWithImageNamed:#"reddrop"];
drop.name = #"Red";
}
[drop setupPhysicsBody];
return drop;
}
// In your setupPhysicsBody method of the drop, add the following to define
// the drop's bit mask, contact test, and collision.
// Make sure you are adding them AFTER setting the physics body, and not before.
// Since you revealed no code regarding this method, I will assume 'self' is
// referring to the drop, since you call this method on the drop.
-(void) setupPhysicsBody {
...
...
...
self.physicsBody.categoryBitMask = dropsCategory;
self.physicsBody.contactTestBitMask = groundCategory;
self.physicsBody.collisionBitMask = 0;
// The above code sets the drop category bit mask, sets its contactTestBitMask
// to be of the groundCategory, so whenever an object with drop category, will
// 'touch' and object with groundCategory, our didBeginContact delegate will
// get called.
// Also, we've set the collision bit mask to be 0, since we only want to
// be notified when a contact begins, but we don't actually want them both to
// 'collide', and therefore, have the drops 'lying' on the ground.
...
...
...
}
// Now we can set the didBeginContact: delegate method.
// Note that, as the name of the method suggests, this method gets called when a
// Contact is began, meaning, the drop is still visible on screen.
// If you would like to do whatever you want to do, when the drop leaves the screen,
// just call the didEndContact: delegate method
-(void)didBeginContact:(SKPhysicsContact *)contact {
// SKPhysicsContact have two properties, bodyA and bodyB, which represents
// the two nodes that contacted each other.
// Since there is no certain way to know which body will always be our drop,
// We will check the bodies category bit masks, to determine which is which
ANBDropsNode *drop = (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) ? (ANBDropsNode *)contact.bodyB.node : (ANBDropsNode *)contact.bodyA.node;
// In the above we are checking the category bit masks,
// Note that we set groundCategory to be 1, and dropsCategory to be 2,
// So we are checking which is higher.
// If bodyA bit mask is lower than bodyB bit mask, that means the bodyA is
// the ground, and bodyB is the drop, so we set the drop to be bodyB's node
// Else, we set it to be bodyA's node.
// Now you can easily get the drop colour from the name property we've set in
// the beginning. you can do some sort of if-else statement, that check
// 'if isEqualToString'. Here I just NSLog the colour
NSLog(#"%#", drop.name);
}
Good luck mate.
You can simply associate a property with the ANBDropsNode class which can be set when the drop is instantiated.
In the ANBDropsNode.h file,
#interface ANBDropsNode
#property (strong, nonatomic) NSString *dropColor; //This property will hold the value associated with the color.
Then in the dropOfType class method:
+(instancetype)dropOfType:(ANBDropType)type {
NSString *strDropColor;
if (type == ANBDropTypeBlue) {
strDropColor = #"bluedrop";
} else if (type == ANBDropTypeGreen) {
strDropColor = #"greendrop";
} else if (type == ANBDropTypeOrange) {
strDropColor = #"orangedrop";
} else if (type == ANBDropTypeRed){
strDropColor = #"reddrop";
}
ANBDropsNode *drop = [self spriteNodeWithImageNamed:strDropColor];
drop.dropColor = strDropColor;
[drop setupPhysicsBody];
return drop;
}
Now, in your collision detection delegate method, you can find out the color of the node by simply referring the dropColor property.

SpriteKit Detect End of Contact After Detecting Begin of Contact

This is what I try to accomplish:
Two sprite nodes in the scene, and self is an edge loop.
If nodeA touches nobeB, and stop. >> Win
If nodeA touches self. >> Lose
If nodeA touches nobeB but didn't stop and touches self. >> Lose
Therefore I need something that works like this:
typedef NS_OPTIONS(aero, SpriteNodeCategory)
{
SpriteNodeCategoryA = 1 << 0,
SpriteNodeCategoryB = 1 << 1,
};
-(void)didBeginContact:(SKPhysicsContact *)contact
{
aero collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (SpriteNodeCategoryA|SpriteNodeCategoryB)) {
//something here I don't know...
}
if (collision == (SpriteNodeCategoryA|SpriteNodeCategoryB)) {
NSLog(#"FAIL");
}
}
You have another method delegate (SKPhysicsContactDelegate) which do the job you asking about:
- (void)didEndContact:(SKPhysicsContact *)contact
Okay so this is going to be a little difficult.
First of, you can use "didSimulatePhysics" to get the update of your simulation
-(void)didSimulatePhysics
{
if (_yourNode.physicsBody.angularVelocity == 0 && newGame) {
if (_yourNode.userData[#"winningCondition"]) {
[self win];
};
}
}
this does is that it updates and see if you have a winning condition - which is gotten from collision (think: reverse engineering)
P.S. _yourNode.physicsBody.angularVelocity == 0 meaning your node is completely still
-(void)didBeginContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CNPhysicsCategoryNodeA|CNPhysicsCategoryNodeB)) {
_yourNode.userData = [#{#"winningCondition":#(YES)} mutableCopy];
}
if (collision == (CNPhysicsCategoryNodeA|CNPhysicsCategoryEdge)) {
[self lose];
}
}
-(void)didEndContact:(SKPhysicsContact *)contact
{
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (CNPhysicsCategoryNodeA|CNPhysicsCategoryNodeB)) {
_yourNode.userData = [#{#"winningCondition":#(NO)} mutableCopy];
}
}
So basically when A touches B, it gives you a winning condition through adding a "userData" to your node.
The rest is pretty straight forward. I think you get it.

SpriteKit - Using a loop to create multiple sprites. How do I give each sprite a different variable name?

I am using SpriteKit.
The code below basically makes a lattice of dots on the screen. However, I want to call each 'dot' a different name based on its position, so that I can access each dot individually in another method. I'm struggling a little on this, so would really appreciate if someone could point me in the right direction.
#define kRowCount 8
#define kColCount 6
#define kDotGridSpacing CGSizeMake (50,-50)
#import "BBMyScene.h"
#implementation BBMyScene
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
/* Setup your scene here */
// Background
self.backgroundColor = [SKColor colorWithRed:0.957 green:0.957 blue:0.957 alpha:1]; /*#f4f4f4*/
CGPoint baseOrigin = CGPointMake(35, 385);
for (NSUInteger row = 0; row < kRowCount; ++row) {
CGPoint dotPosition = CGPointMake(baseOrigin.x, row * (kDotGridSpacing.height) + baseOrigin.y);
for (NSUInteger col = 0; col < kColCount; ++col) {
SKSpriteNode *dot = [SKSpriteNode spriteNodeWithImageNamed:#"dot"];
dot.position = dotPosition;
[self addChild:dot];
//6
dotPosition.x += kDotGridSpacing.width;
}
}
}
return self;
}
Here is an image of what appears on screen when I run the above code...
http://cl.ly/image/3q2j3E0p1S1h/Image1.jpg
I simply want to be able to call an individual dot to do something when there is some form of user interaction, and I'm not sure how I would do that without each dot having a different name.
If anyone could help I would really appreciate it.
Thanks,
Ben
- (void)update:(NSTimeInterval)currentTime {
for(SKNode *node in self.children){
if ([node.name containsString:#"sampleNodeName"]) {
[node removeFromParent];
}
}
}
Hope this one helps!
You can set the name property of each node inside the loop.
Then you can access them with self.children[index].
If you want to find a specific node in your children, you have to enumerate through the array.
Update:
To clarify how to search for an item by iterating, here is a helper method:
- (SKNode *)findNodeNamed:(NSString *)nodeName
{
SKNode *nodeToFind = nil;
for(SKNode *node in self.children){
if([node.name isEqualToString:nodeName]){
nodeToFind = node;
break;
}
}];
return nodeToFind;
}

For loop in Sprite Kit seems to be emptying an array?

I'm just starting to wrap my brain around Sprite Kit and I am encountering a very strange error when attempting to change the property of a node in a for loop im using.
I have two SKSpriteNode objects, one is the child of a SKScene (BLATheBugs) and the other is a child of the first (BLAEmptySpaces). I have a grid laid out with BLAEmptySpaces, and BLATheBugs on top of those empty spaces which are supposed to take UITouch, and move to an empty space if its bool isOccpupied property == False. When the scene is set up, the SKScene triggers a method in TheBugs:
-(void) spawnEmptySpacesInitialize
{
[self addChild:[self spawnEmptySpaces]];
}
which in turn triggers:
-(BLAEmptySpaces *) spawnEmptySpaces
{
emptySpace = [[BLAEmptySpaces alloc] init];
emptySpace.numberOfEmptySpacesNeeded = 12;
[emptySpace spawnEmptySpaces];
[emptySpace positionTheEmptySpaces];
return emptySpace;
}
which finally triggers a method in the EmptySpaces object:
-(BLAEmptySpaces *) spawnEmptySpaces
{
_emptySpacesArray = [NSMutableArray new];
for (int x = 0; x < _numberOfEmptySpacesNeeded; x++)
{
_anEmptySpace = [[BLAEmptySpaces alloc] initWithImageNamed:#"BlueLight.png"];
_anEmptySpace.zPosition = 50;
[_emptySpacesArray addObject:_anEmptySpace];
[self addChild: _anEmptySpace];
}
return self;
}
everything seems fine, (except for needing the additional "addChild" in the EmptySpaces object to get them to be drawn on the screen which i have also been trying to fix) but when i call the method to move TheBugs:
-(void) moveLeftOneSpace
{
NSLog(#"%d", emptySpace.emptySpacesArray.count);
for (emptySpace in emptySpace.emptySpacesArray)
{
NSLog(#"cycle");
if (emptySpace.isOccupied == NO)
{
for (_yellowBug in yellowBugArray)
{
if (_positionOfFingerTouchX > _yellowBug.position.x - variableOne && _positionOfFingerTouchX < _yellowBug.position.x + variableTwo && _positionOfFingerTouchY > _yellowBug.position.y - variableOne && _positionOfFingerTouchY < _yellowBug.position.y + variableTwo && emptySpace.position.x == _yellowBug.position.x - 80 && emptySpace.position.y == _yellowBug.position.y)
{
_yellowBug.position = CGPointMake(_yellowBug.position.x - spaceBetweenBugs, _yellowBug.position.y);
emptySpace.isOccupied = YES;
NSLog(#"execute");
}
}
}
}
}
It at first tells me there are 12 objects in the array and runs the operation. if I try to move any piece again, it tells me there are now NO objects in the array (yellowBugArray). It is also probably worth noting that it will not let me access emptySpace.anEmptySpace. Throws me an error.
Sorry for the long post, but hopefully somewhere in here is the cause of my problem.
Thank you very much guys!

Collision detection Objective-C (cocos2d)

I´m making an iphone app in objective-c with cocos2d, in the code below I try to detect a collision and then run an animation. (The box1 is moved by touch)
When the "[self getChildByTag:d]" and "box1" collide AND overlap I get the "JUMP NOW!" displayed but I don't get the jump itself, but when the box1 is moved away from the "[self getChildByTag:d]" the jump occurs.
I understand that this probably has to do with the fact that the action is called many times, but please explain to me exactly what happens and please help me with a solution!
- (void)update:(ccTime)dt {
for (int d = lowestAvailableTag; d <= highestAvailableTag; d++) {
if ([self getChildByTag:d].position.y < (box1.position.y+45)&&
[self getChildByTag:d].position.x > (box1.position.x-45) &&
[self getChildByTag:d].position.x < (box1.position.x+45) ) {
NSLog(#"JUMP NOW!");
if ([self getChildByTag:d].position.x < 150) {
[[self getChildByTag:d] runAction:
[CCJumpTo actionWithDuration:1.5
position:ccp(240, 140) height:110 jumps:1]];
}
}
}
}
//albar
You can add some BOOL flag to detect if your jump occured. Smth like:
- (void) update:(ccTime)dt
{
if( jumpOccured == false )
{
BOOL needToJump = // your jump condition
if( needToJump == true )
{
// your jump code
jumpOccured = true;
}
}
}
by the way, if you have many possible collisions, you can use box2d to detect them