didBeginContact method not working as intended - ios

I have two nodes and a boolean. Simple enough. When node A contacts Node B and the boolean is 0, nothing happens. However if the boolean is 1, Node A is removed through the didBeganContact method.
Extremely simple, however I have an annoying problem on when I want Node A removed.
Node B is a rectangle and node A is a square going in the middle of the rectangle, the boolean is called and turned into 1 when I tap and hold the Node B using the touchesBegan method. Now before Node A contacts Node B, I tap and hold Node B and when Node A contacts, its removed, but when Node A is already in the middle, and I tap Node B, nothing happens and I don't know why.
Rectangle Method
-(void)rectangle
{
SKSpriteNode *rectangle = [[SKSpriteNode alloc] init];
rectangle = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:CGSizeMake(75, 150)];
rectangle.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
rectangle.name = #"rect";
rectangle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
rectangle.physicsBody.categoryBitMask = rectangleCategory;
rectangle.physicsBody.contactTestBitMask = fallingSquareCategory;
rectangle.physicsBody.collisionBitMask = 0;
[self addChild:rectangle];
}
touchesBeganMethod
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 1;
}
}
touchesEnded
-(void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 0;
}
}
square Method
-(void)square
{
SKAction *move = [SKAction moveToY:CGRectGetMidY(self.frame) duration:1.75];
SKSpriteNode *fallingSquare = [[SKSpriteNode alloc] init];
fallingSquare = [SKSpriteNode spriteNodeWithColor:[UIColor yellowColor] size:CGSizeMake(75, 75)];
fallingSquare.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMaxY(self.frame));
fallingSquare.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:fallingSquare.size];
fallingSquare.physicsBody.categoryBitMask = fallingSquareCategory;
fallingSquare.physicsBody.contactTestBitMask = rectangleCategory
fallingSquare.physicsBody.collisionBitMask = 0;
[self addChild:fallingSquare];
[fallingSquare runAction:move];
}
didBeginContact
static inline SKSpriteNode *nodeFromBody(SKPhysicsBody *body1, SKPhysicsBody *body2, uint32_t category) {
SKSpriteNode *node = nil;
if (body1.categoryBitMask & category) {
node = (SKSpriteNode *)body1.node;
}
else if (body2.categoryBitMask & category) {
node = (SKSpriteNode *)body2.node;
}
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
SKSpriteNode *R1 = nil;
SKSpriteNode *fallingS = nil;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
R1 = nodeFromBody(firstBody, secondBody, rectangleCategory);
fallingS = nodeFromBody(firstBody, secondBody, fallingSquareCategory);
if (R1 && fallingS && radBool == 1)
{
[fallingS removeFromParent];
}
}

I believe your issue is the "begin" part of didBeginContact. It only gets called the first time they contact and not every loop. Because the bool was not set to YES when they first contacted it will never be evaluated again.
I believe I ran into this issue once before and the solution was to create a new physical body when you touch it. This "should" trigger didBeginContact the next go around. You might also be able to change a property on the physical body, but if I recall correctly I didn't get that to work and had to init a new physical body.
For example try updating your touchesBegan with this
touchesBeganMethod
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"rect"])
{
radBool = 1;
node.physicsBody = nil;
node.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:rectangle.size];
node.physicsBody.categoryBitMask = rectangleCategory;
node.physicsBody.contactTestBitMask = fallingSquareCategory;
node.physicsBody.collisionBitMask = 0;
}
}
Hope that works for you.

Related

Different touches that not react with each other objective c

I'm making simple game for two players on Ipad, just like ping pong, when one finger is on screen player can react with his paddle, but when second finger is on his paddle, he cant move his paddle, but he move first paddle instead, I set up some NSLog and it says that both of them moves, but that isn't right,here are sample of my code:
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
//First player
CGPoint touchLocation = [touch locationInNode:self];
SKPhysicsBody* body = [self.physicsWorld bodyAtPoint:touchLocation];
if (body && [body.node.name isEqualToString: paddleCategoryName]) {
NSLog(#"Began touch on first paddle");
self.isFingerOnPaddle = YES;
}
//Second player
CGPoint secondTouchLocation = [touch locationInNode:self];
SKPhysicsBody* secondbody = [self.physicsWorld bodyAtPoint:secondTouchLocation];
if (secondbody && [secondbody.node.name isEqualToString: secondPaddleCategoryName]) {
NSLog(#"Began touch on second paddle");
self.isSecondFingerOnPaddle = YES;
}
}
}
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
for (UITouch *touch in touches) {
//for first player
if (self.isFingerOnPaddle) {
CGPoint touchLocation = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
SKSpriteNode* paddle = (SKSpriteNode*)[self childNodeWithName: paddleCategoryName];
int paddleY = paddle.position.y + (touchLocation.y - previousLocation.y);
paddleY = MAX(paddleY, paddle.size.height/2);
paddleY = MIN(paddleY, self.size.height - paddle.size.height/2);
paddle.position = CGPointMake(paddle.position.x, paddleY);
NSLog(#"First paddle moving");
}
//for second player
if (self.isSecondFingerOnPaddle) {
CGPoint touchLocation = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
SKSpriteNode* secondPaddle = (SKSpriteNode*)[self childNodeWithName: secondPaddleCategoryName];
int secondPaddleY = secondPaddle.position.y + (touchLocation.y - previousLocation.y);
secondPaddleY = MAX(secondPaddleY, secondPaddle.size.height/2);
secondPaddleY = MIN(secondPaddleY, self.size.height - secondPaddle.size.height/2);
secondPaddle.position = CGPointMake(secondPaddle.position.x, secondPaddleY);
NSLog(#"Second paddle moving");
}
}
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
self.isFingerOnPaddle = NO;
self.isSecondFingerOnPaddle = NO;
}
What I do wrong, and what do I need to change to my code work, like it should
A better approach would be to sub class paddle and do your touch code inside the paddle subclass. This eliminates having to loop through touch arrays to see if your paddle is being touched, plus it makes the code a lot neater and easier to read. The only thing is you will have to style your node set up a little differently, because touch moved will not work outside of the paddle.
Here is how you style it
1) Subclass Paddle to SKNode
2) Create a child of SKSpriteNode for your actual paddle gfx
3) Paddle frame should be set at the width of your scene, with a height that is allowed for the player to touch (probably the height of the paddle gfx)
4) override the touch code inside Paddle, all touch code in the set should only relate to what is being touched by the paddle
5) Do all of your sliding logic inside these overrides
Now you have Paddle defined for the behavior you are looking for, you can add it to the scene where ever you like.
The nice part about this method is you eliminate a lot of duplicate code, (Since the paddles on both sides behave differently) and if you want to add some fun, you can add even more paddles to both sides to provide more variation. (Each player has a paddle in the middle of the play field, and the bottom, which can be moved independantly)
your logic is wrong, just add a touchArray property to store first touch and second touch, set it when touch begin then determine when touches moved.
-(void)touchesBegan:(NSSet*)touches withEvent:(UIEvent*)event {
/* Called when a touch begins */
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
SKSpriteNode *node = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
if (node && [node.name isEqualToString: paddleCategoryName]) {
NSLog(#"Began touch on first paddle");
[self.touchArray replaceObjectAtIndex:0 withObject:touch];
}else if (node && [node.name isEqualToString: secondPaddleCategoryName]) {
NSLog(#"Began touch on second paddle");
[self.touchArray replaceObjectAtIndex:1 withObject:touch];
}
}
}
-(void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event {
for (UITouch *touch in touches) {
CGPoint touchLocation = [touch locationInNode:self];
UITouch *firstTouch = self.touchArray[0];
UITouch *secondTouch = self.touchArray[1];
SKSpriteNode *paddle = [[SKSpriteNode alloc] init];
if (touch == firstTouch) {
CGPoint previousLocation = [touch previousLocationInNode:self];
SKSpriteNode* paddle = (SKSpriteNode*)[self childNodeWithName: paddleCategoryName];
int paddleY = paddle.position.y + (touchLocation.y - previousLocation.y);
paddleY = MAX(paddleY, paddle.size.height/2);
paddleY = MIN(paddleY, self.size.height - paddle.size.height/2);
paddle.position = CGPointMake(paddle.position.x, paddleY);
NSLog(#"First paddle moving");
}else if (touch == secondTouch) {
CGPoint touchLocation = [touch locationInNode:self];
CGPoint previousLocation = [touch previousLocationInNode:self];
SKSpriteNode* secondPaddle = (SKSpriteNode*)[self childNodeWithName: secondPaddleCategoryName];
int secondPaddleY = secondPaddle.position.y + (touchLocation.y - previousLocation.y);
secondPaddleY = MAX(secondPaddleY, secondPaddle.size.height/2);
secondPaddleY = MIN(secondPaddleY, self.size.height - secondPaddle.size.height/2);
secondPaddle.position = CGPointMake(secondPaddle.position.x, secondPaddleY);
NSLog(#"Second paddle moving");
}
}
}
-(void)touchesEnded:(NSSet*)touches withEvent:(UIEvent*)event {
for (UITouch *touch in touches) {
if (touch == self.touchArray[0]) {
[self.touchArray replaceObjectAtIndex:0 withObject:#""];
}else if (touch == self.touchArray[1]) {
[self.touchArray replaceObjectAtIndex:1 withObject:#""];
}
}
self.isFingerOnPaddle = NO;
self.isSecondFingerOnPaddle = NO;
}

SKShapeNode Detect Two Lines Intersection

I am working on a application and i am drawing line based on user touch of user finger. Once the touch end event received the line is converted to last path. A new line is draw with name "Current path" node when a new touch began event received. I added a physics body for both the line with opposite contact bit mask but i am not able to receive collision event.
Following is my code:
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
currentPath = CGPathCreateMutable();
currentPathNode = [self newLineNodeWithFillColor : CURRENT_LINE_COLOR];
CGPathMoveToPoint(currentPath, NULL, positionInScene.x, positionInScene.y);
currentPathNode.path = currentPath;
[self addChild:currentPathNode];
uint32_t contactBitMask = circleCategory | lastPathCategory;
[self addPhysicsBodyForLine:currentPathNode withCategoryBitMask:drawPathCategory withContactBitMask:contactBitMask];
}
- (void)touchesMoved:(NSSet*)touches withEvent:(UIEvent*)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
CGPathAddLineToPoint(currentPath, NULL, positionInScene.x, positionInScene.y);
currentPathNode.path = currentPath;
uint32_t contactBitMask = lastPathCategory;
[self addPhysicsBodyForLine:currentPathNode withCategoryBitMask:drawPathCategory withContactBitMask:contactBitMask];
}
- (void)touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event
{
if(lastPath == nil){
lastPath = CGPathCreateMutable();
}
CGPathAddPath(lastPath, nil, currentPath);
[lastPathNode removeFromParent];
if(currentPathNode != nil){
[currentPathNode removeFromParent];
currentPathNode = nil;
}
lastPathNode = [self newLineNodeWithFillColor : LAST_LINE_COLOR];
lastPathNode.path = lastPath;
[self addChild:lastPathNode];
[self addPhysicsBodyForLine:lastPathNode withCategoryBitMask:lastPathCategory withContactBitMask:drawPathCategory];
CGPathRelease(currentPath);
}
- (void) addPhysicsBodyForLine:(SKShapeNode*)node withCategoryBitMask:(uint32_t)category withContactBitMask:(uint32_t)contactBitMask{
node.physicsBody = [SKPhysicsBody bodyWithEdgeChainFromPath:node.path];
node.physicsBody.categoryBitMask = category;
node.physicsBody.contactTestBitMask = contactBitMask;
node.physicsBody.collisionBitMask = contactBitMask;
node.physicsBody.dynamic = YES;
node.physicsBody.usesPreciseCollisionDetection = YES;
}
But collision is not Detected? Any Solution.
Collisions do not work that way. You will only register a collision if you use physics to move a node's position. Creating a new physics body over (or across) an already existing physics body will not register a collision.
You can use -(BOOL)intersectsNode:(SKNode *)node every time a new path is drawn to check if the new node intersects any other node.

sprite kit: verify if touch is inside a node

I'm trying to figure out how to detect if I touch a node in sprite kit. I thought it was function like this in UIView:
[myView pointInside:point withEvent:nil];
But so far I can not have found an alternative to this. What I'm trying to accomplish is to know I have touch the sprite node in the screen.
I'll really appreciate if you can help me to I acomplish this
Here is what I have in my code. I'm adding animated node:
-(void)addMyNodeAnimated
{
NSMutableArray *myNodeArray = [NSMutableArray array];
NSArray *animatedFrames = [NSArray new];
SKTextureAtlas *AnimatedAtlas = [SKTextureAtlas atlasNamed:#"pc2"];
int numImages = (int)AnimatedAtlas.textureNames.count;
for (int i=1; i <= numImages; i++) {
NSString *textureName = [NSString stringWithFormat:#"%#-%d", #"pc2", i];
SKTexture *temp = [AnimatedAtlas textureNamed:textureName];
[myNodeArray addObject:temp];
}
animatedFrames = myNodeArray;
SKTexture *temp = animatedFrames[0];
SKSpriteNode *animationNode = [SKSpriteNode spriteNodeWithTexture:temp];
animationNode.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));
animationNode.userInteractionEnabled = YES;
animationNode.name = #"AnimationNode";
[self addChild:animationNode];
[animationNode runAction:[SKAction repeatActionForever:
[SKAction animateWithTextures:animatedFrames
timePerFrame:0.1f
resize:NO
restore:YES]] withKey:#"AnimationRuning"];
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
CGPoint _touchLocation = [[touches anyObject] locationInNode:self];
SKNode *node = [self nodeAtPoint:_touchLocation];
if (node != nil)
{
NSLog(#"node name %#", node.name);
}
}
When I touch the node in the screen the node returns null.
Any of know why is this?
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
if ([node.name isEqualToString:#"yourSpriteName"] && [node.name isEqualToString:#"yourEffectName"]) {
//Whatever you want.
}
}

SpriteKit Clicker Game Select Spawn Area for Clickable things?

I'm New to SpriteKit and build a simple CLickerGame in SpriteKit. Now i have the game finish but the thing which should spawn to click on it, spawns from time to time outside the screen. Where can i select the arc4random area where the thing should spawn?
#implementation MyScene
-(void)CreateTestObject
{
SKSpriteNode *TestObject = [SKSpriteNode spriteNodeWithImageNamed:#"TestObject"];
int maxX = CGRectGetMaxX(self.frame);
float ranX = (arc4random()%maxX) + 1;
int maxY = CGRectGetMaxY(self.frame);
float ranY = (arc4random()%maxY) + 1;
TestObject.position = CGPointMake(ranX, ranY);
TestObject = CGSizeMake(75, 75);
TestObject = #"Object";
[self addChild:TestObject];
}
-(id)initWithSize:(CGSize)size
{
if (self = [super initWithSize:size])
{
bg = [SKSpriteNode spriteNodeWithImageNamed:#"bg.png"];
bg.position = CGPointMake(160, 284);
bg.size = CGSizeMake(self.frame.size.width, self.frame.size.height);
[self addChild:bg];
[self CreateTestObject];
}
return self;
}
-(void)selectNodeForTouch:(CGPoint)touchLocation
{
SKSpriteNode *touchedNode = (SKSpriteNode *)[self nodeAtPoint:touchLocation];
if([[touchedNode name] isEqualToString:#"TestObject"])
{
SKSpriteNode *TestObject = (SKSpriteNode *)[self childNodeWithName:#"TestObject"];
TestObject.name = #"DisabledTestObject";
SKAction *grow = [SKAction scaleTo:1.2 duration:0.1];
SKAction*shrink = [SKAction scaleTo:0 duration:0.07];
SKAction *removeNode = [SKAction removeFromParent];
SKAction *seq = [SKAction sequence:#[grow, shrink, removeNode]];
[TestObject runAction:seq];
[self CreateTestObject];
[self runAction:[SKAction playSoundFileNamed:#"Sound.aif" waitForCompletion:NO]];
}
}
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
for (UITouch *touch in touches)
{
//CGPoint location = [touch locationInNode:self];
UITouch *touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
[self selectNodeForTouch:positionInScene];
}
}
#end
Modify the max values for your (x,y) coordinates of any object being added to your view.
For example, on an iPhone 5 display in landscape mode and the spawned object being of size width=100, height=100, the max x value should be no more than 518. Width of screen being 568 less half the width of your object (50).
The same logic applies for the y value. The max y value should be 270 given the same object dimensions.
The above assumes that you have not changed your object's anchor point from (0.5,0.5) to another value.

Passing iOS sprites from one class to another

I have a Spritekit project in xcode on my main screen. However I would really like to manage it from another class so as to not cluster my main. So I added a new class to manage and handle it. Sadly I can't seem to get it to show in my main (or I might be doing it wrong). My myscene.m consists of pretty much the same code including the spaceship and this is where I moved the code too ( I didn't bother adding the methods like touchesBegan)
// joysitck.m
// controlpad
//
#import "joysitck.h"
#implementation joysitck
{
BOOL controlButtonOn;
}
float controlx=200;
float controly=200;
float touchX = 0.0;
float touchY=0.0;
- (instancetype)init
{
if (self = [super init]) {
self.name = #"controlPadNode";
SKSpriteNode *controlPadNode = [SKSpriteNode spriteNodeWithImageNamed:#"game-controller-frontb"];
controlPadNode.position = CGPointMake(controlx,controly);
[controlPadNode setScale:0.5];
controlPadNode.name = #"controlPadNode";
controlPadNode.zPosition = 1.0;
[self addChild:controlPadNode];
}
return self;
}
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//if control pad touched
if ([node.name isEqualToString:#"controlPadNode"]) {
touchX = location.x;
touchY = location.y;
controlButtonOn= true;
}
}
//when touch moves
- (void) touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//if control pad touched
if ([node.name isEqualToString:#"controlPadNode"]) {
touchX = location.x;
touchY = location.y;
controlButtonOn= true;
}
}
- (void) touchesEnded:(NSSet *)touches withEvent:(UIEvent *)event {
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
SKNode *node = [self nodeAtPoint:location];
//if control pad touched
if ([node.name isEqualToString:#"controlPadNode"]) {
controlButtonOn= false;
}
}
//update method
-(void)update:(NSTimeInterval)currentTime {
if (controlButtonOn) {
//the control pad
SKNode *controlNode = [self childNodeWithName:#"controlPadNode"];
SKNode *node = [self childNodeWithName:#"spaceShipNode"];
float angle = atan2f (touchY - controly, touchX - controlx) ;
SKAction *moveShip=[SKAction moveByX:6*cosf(angle) y:0 duration:0.005];
[node runAction: moveShip];
}
}
#end
You haven't added joystick node to the scene.
Add this code to MyScene's initWithSize method (before [self addship]):
joysitck *joysitckNode = [[joysitck alloc] init];
[self addChild:joysitckNode];

Resources