Sprite kit doesn't find nodeAtPoint after moving parent SKNode - ios

Hi.
I'm having this weird problem in Sprite Kit. I'm using nodeAtPoint and categoryBitMask to detect whether the player is touching the ground when calling a jump method.
Everything's working as it should. But then — in order to reveal some optional buttons in a drawer — when I move the parent node with SKAction moveTo:CGPoint (I have both the ground and player as children of an SKNode), the player don't jump. I NSLog the pointBelowPlayer, and it is the same as before, but the blockNode.physicsBody is null! Might this be a bug in Sprite Kit, or am I missing something basic about inheritance and position?
The method for jumping:
-(void)playerJump {
// Player jump if on ground
CGPoint pointBelowPlayer = CGPointMake(_player.position.x, _player.position.y-_player.size.height);
SKNode *blockNode = [self nodeAtPoint:pointBelowPlayer];
if (blockNode.physicsBody.categoryBitMask == groundCategory){
[_player.physicsBody applyImpulse:CGVectorMake(0, 120.0f)];
}
}
The method for moving the parent node:
-(void)toggleDrawer {
if (bDrawerVisible) {
[_actionLayer runAction:[SKAction moveTo:CGPointMake(0, 0) duration:1]];
bDrawerVisible = false;
} else {
[_actionLayer runAction:[SKAction moveTo:CGPointMake(0, 200) duration:1]];
bDrawerVisible = true;
}
}
Creation of the SpriteNodes:
-(id)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
_actionLayer = [SKNode new];
_player = [self createPlayer];
[_actionLayer addChild:_player];
_ground = [self createGround];
[_actionLayer addChild:_ground];
}
-(SKSpriteNode *)createPlayer {
SKSpriteNode *player = [SKSpriteNode spriteNodeWithImageNamed:#"redSquare.png"];
player.name = #"player";
player.scale = 0.5;
player.position = CGPointMake(screenWidth/3, player.size.height*2);
player.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:player.size];
player.physicsBody.categoryBitMask = playerCategory;
player.physicsBody.collisionBitMask = groundCategory;
player.physicsBody.contactTestBitMask = noteCategory;
return player;
}
-(SKSpriteNode *)createGround {
SKSpriteNode *ground = [SKSpriteNode spriteNodeWithImageNamed:#"ground.png"];
ground.name = #"ground";
ground.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:ground.size];
ground.physicsBody.dynamic = NO;
ground.physicsBody.categoryBitMask = groundCategory;
ground.physicsBody.collisionBitMask = playerCategory;
ground.xScale = screenWidth/ground.size.width;;
ground.position = CGPointMake(self.size.width/2, 0);
return ground;
}

Ok i solve the problem...
if you have more then one Node, on the position you tap, you will get null if the deepest node have no name, or the name of the deepest Node.
So use this to go through all nodes are found:
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInNode:self];
NSArray *buttonNodes = [self nodesAtPoint:location];
for (SKNode *node in buttonNodes)
{
NSLog(#"%#", node.name);
}
Additional you can use NSLog(#"%f", node.zPosition); to get the zPosition to see witch one is on top.

Node at point might be another node (maybe your background?).
Use node.name to name the nodes and you may check if node if the same by comparing names with equalToString: method.

Related

Spritekit - UITouch effect does not work when SKparticles are taking place over the area

I have a bubble, when touched will pop. This pop effects work fine with the code below. However when a spritekit particle effect is used which covers the bubble, the touch action does not work. When the particle effect finishes, the touch action will work. Does the particle effect have a non-touch field? and is there anyway around this? I attempted to change the z-position but that did not work.
- (void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
UITouch* touch = [touches anyObject];
CGPoint positionInScene = [touch locationInNode:self];
SKLabelNode *touchedNode = (SKLabelNode *)[self nodeAtPoint:positionInScene];
if ([[touchedNode name] isEqualToString:#"bubbleBall1"]) {
ballNode.position = bubble1.position;
[_gameNode addChild:ballNode];
[bubble1 removeFromParent];
[self playBubblePopSound];
}
}
This is the particle effect im using in the physics contact area.
SKEmitterNode *starCollectParticle = [SKEmitterNode emitterNamed:#"starCollect"];
starCollectParticle.position = star1Node.position;
starCollectParticle.zPosition = 0;
//starCollectParticle.particleLifetime = 2;
[self addChild:starCollectParticle];
It was the z.position. I change the z.position of the particles. This did not work. However also changing the z.position of the SKSpritenode did the trick.
-(void)addBubble1:(CGPoint)pos{
gameObjects = [SKTextureAtlas atlasNamed:#"GameObjects.atlas"];
bubble1 = [SKSpriteNode spriteNodeWithTexture:[gameObjects textureNamed:kBubble]];
bubble1.size = bubble1.texture.size;
bubble1.size = CGSizeMake(70,70);
bubble1.position = pos;
bubble1.name = bubble1CategoryName;
**bubble1.zPosition = 1;**
bubble1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:bubble1.frame.size.width/2];
bubble1.physicsBody.affectedByGravity = NO;
bubble1.physicsBody.dynamic = NO;
bubble1.physicsBody.usesPreciseCollisionDetection = YES;
bubble1.physicsBody.categoryBitMask = bubble1Category;
bubble1.physicsBody.contactTestBitMask = ballCategory | completeObjectNodeCategory | completeObjectBoxNodeCategory;
bubble1.physicsBody.collisionBitMask = completeObjectNodeCategory | lineNodeCategory;
[_gameNode addChild:bubble1];
}
SKEmitterNode *starCollectParticle = [SKEmitterNode emitterNamed:#"starCollect"];
starCollectParticle.position = star1Node.position;
**starCollectParticle.zPosition = 0;**
//starCollectParticle.particleLifetime = 2;
[self addChild:starCollectParticle];

didBeginContact method not working as intended

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.

Game increases node count drastically and freezes

I have created two nodes, a projectile node and a target node, each with their respective categories and when they both contact each other, both are removed and depending on the projectile, even more projectiles appear.
The projectiles also are removed when they touch the edge of the screen.
Everything works fine until after a "wave" of targets (I created a method to occasionally spawn 5 targets in a set design) appear and scroll across the screen and projectiles are all over the place contacting more and more targets, now I would understand the node count increasing, but the amount is no where near the amount, the game then lags to the point of the FPS being 2 FPS and the game eventually freezing and the node count stuck to 700.
didBeginContact Method
static inline Enemies *nodeFromBody(SKPhysicsBody *body1, SKPhysicsBody *body2, uint32_t category) {
Enemies *node = nil;
if (body1.categoryBitMask & category) {
node = (Enemies *)body1.node;
}
else if (body2.categoryBitMask & category) {
node = (Enemies *)body2.node;
}
return node;
}
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
SKSpriteNode *projectile = nil;
SKSpriteNode *offScreen = nil;
Enemies *target = nil;
firstBody = contact.bodyA;
secondBody = contact.bodyB;
projectile = nodeFromBody(firstBody, secondBody, projectileCategory);
offScreen = nodeFromBody(firstBody, secondBody, offScreenCategory);
target = nodeFromBody(firstBody, secondBody, targetsCategory);
if (projectile && target) {
if ([projectile.name isEqualToString:#"firstShot"]) {
NSLog(#"firstShot");
[projectile removeAllActions];
[projectile removeFromParent];
[ProjectilePatterns pattern1:self spawnPoint:target.position];
[target removeAllActions];
[target removeFromParent];
[Enemies wave1:self spawnPoint:CGPointMake(CGRectGetMidX(self.frame) - 525, CGRectGetMidY(self.frame) - 50)];
}
}
enemyClassMethod
// These are the regular targets, the other method that spawns the wave is essentially creating new enemies and returns them positioned in certain positions.
+(Enemies *)wave1:(SKScene *)scene spawnPoint:(CGPoint)location {
SKAction *delay = [SKAction waitForDuration:5];
SKAction *remove = [SKAction removeFromParent];
SKAction *delayAndRemove = [SKAction sequence:#[delay, remove]];
Enemies *enemy1 = [[Enemies alloc] init];
enemy1 = [Enemies spriteNodeWithColor:[SKColor blackColor] size:CGSizeMake(30, 30)];
enemy1.position = location;
enemy1.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:enemy1.size];
enemy1.physicsBody.categoryBitMask = targetsCategory;
enemy1.physicsBody.contactTestBitMask = projectileCategory;
enemy1.physicsBody.collisionBitMask = 0;
[scene addChild:enemy1];
[enemy1 runAction:delayAndRemove];
[enemy1.physicsBody applyImpulse:CGVectorMake(12, 0)];
return enemy1;
}
Projectile Pattern
+(ProjectilePatterns *)pattern1:(SKScene *)scene spawnPoint:(CGPoint)location {
SKAction *rotate = [SKAction rotateByAngle:1 duration:0.1];
SKAction *rotateForever = [SKAction repeatActionForever:rotate];
ProjectilePatterns *patternBomb = [[ProjectilePatterns alloc] init];
patternBomb = [ProjectilePatterns spriteNodeWithImageNamed:#"contactBomb"];
patternBomb.position = CGPointMake(location.x + 10, location.y);
patternBomb.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:patternBomb.size];
patternBomb.physicsBody.categoryBitMask = projectileCategory;
patternBomb.physicsBody.contactTestBitMask = targetsCategory | offScreenCategory;
patternBomb.physicsBody.collisionBitMask = 0;
patternBomb.physicsBody.friction = 0;
patternBomb.physicsBody.linearDamping = 0;
patternBomb.name = #"p1";
ProjectilePatterns *patternBomb2 = [[ProjectilePatterns alloc] init];
patternBomb2 = [ProjectilePatterns spriteNodeWithImageNamed:#"contactBomb"];
patternBomb2.position = CGPointMake(location.x - 10, location.y);
patternBomb2.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:patternBomb2.size];
patternBomb2.physicsBody.categoryBitMask = projectileCategory;
patternBomb2.physicsBody.contactTestBitMask = targetsCategory | offScreenCategory;
patternBomb2.physicsBody.collisionBitMask = 0;
patternBomb2.physicsBody.friction = 0;
patternBomb2.physicsBody.linearDamping = 0;
patternBomb2.name = #"p1";
[scene addChild:patternBomb];
[patternBomb runAction:rotateForever];
[patternBomb.physicsBody applyImpulse:CGVectorMake(5, 0)];
[scene addChild:patternBomb2];
[patternBomb2 runAction:rotateForever];
[patternBomb2.physicsBody applyImpulse:CGVectorMake(-5, 0)];
return patternBomb;
return patternBomb2;
}
I should also note that the scene's physicsWorld gravity is (0,0). I did this so the impulses movement would be constant.

Can't seem to figure what's wrong here

I've used this same exact code before, and it worked perfect every time. However, I'm trying to use it to move the paddle sprite along a horizontal path at position 100 for y and it updates with the location of my touch but for some reason it's not moving at all. I can't spot what's wrong. Can someone take a look and let me know?
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for (UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newPosition = CGPointMake(location.x, 100);
//stop the paddle from going too far
if (newPosition.x < self.paddle.size.width/ 2) {
newPosition.x = self.paddle.size.width/ 2;
}
if (newPosition.x > self.size.width - (self.paddle.size.width/ 2)) {
newPosition.x = self.size.width - (self.paddle.size.width/ 2);
}
self.paddle.position = newPosition;
}
}
-(void) addPlayer:(CGSize)size {
SKSpriteNode *paddle = [SKSpriteNode spriteNodeWithImageNamed:#"paddle"];
//resize sprite
paddle.size = CGSizeMake(125, 31.25);
//position sprite
paddle.position = CGPointMake(size.width/2, 100);
//add physics body to paddle
paddle.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:paddle.frame.size];
//change to static so wont be moved by physics
paddle.physicsBody.dynamic = NO;
//add sprite to scene
[self addChild:paddle];
}
-(instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]){
self.backgroundColor = [SKColor colorWithRed:(29.0f/255) green:(29.0f/255) blue:(29.0f/255) alpha:1.0];
//change gravity
self.physicsWorld.gravity = CGVectorMake(0, 0);
//add physics body to scene
self.physicsBody = [SKPhysicsBody bodyWithEdgeLoopFromRect:self.frame];
[self addPlayer:size];
[self addBricks:size];
[self addBall:size];
}
return self;
}

How to do impulse physics

Okay guys, I have two sprites one the enemy who moves to pick up items around the map as the items appear and it does that using SKActions then also I have the wall which he shouldn't go through. anyway. all I am trying to do is to make him bounce out if he hits the wall. How would I go about that?
Here is the code below:
// for the Enemy wall I have this code:
- (void) EnemyBelongings {
EnemyWall = [SKSpriteNode spriteNodeWithImageNamed:#"EnemyWall#2x"];
EnemyWall.name = #"hisWall";
EnemyWall.position = CGPointMake(512, 260);
EnemyWall.xScale = 0.09;
EnemyWall.yScale = 0.09;
EnemyWall.physicsBody.categoryBitMask = PhysicsCategoryEnemyWall;
EnemyWall.physicsBody.contactTestBitMask = PhysicsCategoryEnemy;
EnemyWall.physicsBody.collisionBitMask = 0;
[self addChild:EnemyWall];
}
// For the enemy character I have this code
- (void) Enemy {
_Enemy = [SKSpriteNode spriteNodeWithImageNamed:#"enemy"];
_Enemy.position = CGPointMake(520, _Enemy.size.height/1.50);
_Enemy.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:self.size.width];
_Enemy.physicsBody.usesPreciseCollisionDetection = YES;
_Enemy.physicsBody.categoryBitMask = PhysicsCategoryEnemy;
_Enemy.physicsBody.contactTestBitMask = PhysicsCategoryEnemyWall;
_Enemy.physicsBody.collisionBitMask = 0;
[self addChild:_Enemy];
}
// For the enemy movement I have this code
- (void) EnemySpawner {
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
[_Enemy runAction:wait];
SKAction *actionXMove2 = [SKAction moveToX:ForEnemy.position.x duration:0.14];
SKAction *actionYMove2 = [SKAction moveToY:ForEnemy.position.y duration:0.14];
[_Enemy runAction:actionYMove2];
[_Enemy runAction:actionXMove2];
}];
}
-(void)didBeginContact:(SKPhysicsContact *)contact
{
if (contact.bodyA.categoryBitMask == PhysicsCategoryEnemy && contact.bodyB.categoryBitMask
== PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyA.node; // a is the enemy here
// Code here
SKNode *wall = contact.bodyB.node; // b is the enemy's wall here
// Code here
}
else if (contact.bodyB.categoryBitMask == PhysicsCategoryEnemy &&
contact.bodyA.categoryBitMask == PhysicsCategoryEnemyWall)
{
SKNode *enemy = contact.bodyB.node;
// Code here
SKNode *wall = contact.bodyA.node;
// Code here
}
}
You need to applyImpulse on the node's physicsBody to make it simulate naturally and interact with other physicsBodies.
Please look at my answer here on how to do the same.
Copy the rwAdd, rwSub, rwMult, rwLength and the rwNormalize methods from this tutorial by Ray Wenderlich.
Then, try using this code:
[ForEnemy runAction:appear2 completion:^{
SKAction *wait = [SKAction waitForDuration:1.0];
CGPoint offset = rwSub(location, ForEnemy.position);
CGPoint direction = rwNormalize(offset);
float forceValue = 200; //Edit this value to get the desired force.
CGPoint shootAmount = rwMult(direction, forceValue);
CGVector impulseVector = CGVectorMake(shootAmount.x, shootAmount.y);
[_Enemy.physicsBody applyImpulse:impulseVector];
}];
PhysicsWorld should simulate bounces automatically. Just set your nodes' physics properties accordingly. Look for the 'restitution' property. It will determine the bounciness of your objects.
And don't use SKAction to move your enemies. This will just "teleport" your nodes around. They don't get to have any real velocity in the physicsWorld (and hence don't bounce). Instead, apply forces to your bodies (or set velocity vector manually).

Resources