SpriteKit - The child of my node does not appear to be changing its zPosition attribute - objective-c

my issue is, I've got a "2.5D" game where characters that come into contact with one another need to dynamically change their zPosition. I'm using physics bodies to achieve this.
It works OK with the characters, however, the shield they are holding (a child object) is not following suit even though I'm logging a correct change of zPosition attribute.
if (((firstNode.categoryBitMask & dudeCategory) != 0) && ((secondNode.categoryBitMask & shieldGuyCategory) !=0))
{
if ((firstNode.node.position.y < secondNode.node.parent.position.y))
{
[secondNode.node.parent enumerateChildNodesWithName:#"shieldNode" usingBlock:^(SKNode *node, BOOL *stop){
NSLog(#"Ooh I Say! Shield zPos = %f", node.zPosition);
SKAction *zPos = [SKAction runBlock:^{
node.zPosition = node.zPosition -200;
NSLog(#"And now, %# zPos = %f", node.name, node.zPosition);
NSLog(#"But static NPC zPos = %f", firstNode.node.zPosition);
NSLog(#"And finally, Shield-holder Guy zPos = %f", secondNode.node.parent.zPosition);
}];
[node runAction:zPos];
}];
secondNode.node.parent.zPosition = secondNode.node.parent.zPosition -200;
}
}
Halp!

Instead of setting the zPosition using SKAction runBlock:, set it directly.
[secondNode.node.parent enumerateChildNodesWithName:#"shieldNode" usingBlock:^(SKNode *node, BOOL *stop){
node.zPosition -= 200;
}];

So, it turns out I was setting the zPosition for the shield in its method. Removing that restores normal behaviour when the parent changes zPosition, having it declared explicitly seems to override the nested behaviour.

Related

How to check whether some SKPhysicsBody in contained in another

I've reviewed similar questions and do believe I'm asking about different thing.
I have a player node which has it's own body and I want to check when it's been moved to some part of the level (exit node). Examples of this may be putting some dynamic item in a box, or parking a car at the specific place.
In my case player is SKSpriteNode with physicsBody created from similar texture (bodyWithTexture) and exit is SKNode with no visual and physicsBody created from polygon path (4 points, non rectangle).
I have a code which does something I wan't, but I do believe there maybe some cases it's doing something I don't want and there are better ways to do it.
int contactsCount = 0;
- (void)didBeginContact:(SKPhysicsContact *)contact{
if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) & exitCategory) {
contactsCount++;
if (contactsCount == 4) {
[player runAction:[SKAction colorizeWithColor:[UIColor greenColor] colorBlendFactor:1.0 duration:1.0] completion:^{
self.physicsWorld.speed = 0;
}];
}
return;
}
[player runAction:[SKAction colorizeWithColor:[UIColor redColor] colorBlendFactor:1.0 duration:1.0] completion:^{
self.physicsWorld.speed = 0;
}];
}
- (void)didEndContact:(SKPhysicsContact *)contact{
if ((contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask) & exitCategory) {
contactsCount--;
return;
}
}

SpriteKit Removing sprites to redraw

Currently in my application I've been adopting a technique to remove/re-draw certain sprites.
in example, the app is a poker app. So when a call/raise/check is made, there is a chip that is placed in-front of the player with an SKLabelNode containing the bet,check etc.. However, removing the previous to then re-add the new is inconsistent and causes a lot of EXC_BAD_ACCESS errors. Now, I guess I could program it to nest search for that node and alter the value instead of redrawing. However, it's used in multiple occasions and will at some point rely on removing the child from it's parent.
What I'm asking is what is the best technique to achieve this without the possibility of inconsistent crashes..?
-(void)removePreviousChips:(NSString *)player {
NSString *string = [NSString stringWithFormat:#"chipLabel:%#", player];
SKNode *node = [tableSprite childNodeWithName:string];
[node removeFromParent];
}
-(void)updateChipStacksAndPlayerPositionsWith:(NSDictionary *)dict {
// [tableSprite removeAllChildren];
for (int i = 0; i < 8; i++) {
//loop through seats taken if value then draw.
if (![[dict valueForKey:_seatNames[i]] isEqualToString:#""]) {
[self drawAvatarWithPlayerName:[dict valueForKey:_seatNames[i]] withSeatPosition:[[seatPositions valueForKey:_seatNames[i]] CGPointValue] withChipStack:[[dict valueForKey:#"startingChips"] intValue]];
}
}
if ([self displayStartGameButton]) {
[startGameBG runAction:[SKAction fadeAlphaTo:1 duration:0.5]];
} else {
[startGameBG runAction:[SKAction fadeAlphaTo:0 duration:0.5]];
}
}
Them two examples are consistent ways to crash my app.
EDIT
for example, a better approach would be to detect whether the node is present before the requirement to remove it from it's parent and redraw it. However, something to detect it's presence is not working out for me
SKNode *node = [tableSprite childNodeWithName:[dict valueForKey:_seatNames[i]]];
if (node) {
NSLog(#"node for %# exists", [dict valueForKey:_seatNames[i]]);
} else {
NSLog(#"node for %# doesn't exist", [dict valueForKey:_seatNames[i]]);
}

sprite kit - objective c: slow fps when I create a lot nodes

I wanted to create a space background so I make a for loop to create the stars. Here is the code:
for (int i = 0; i<100; i++) {
SKShapeNode *star= [SKShapeNode shapeNodeWithPath:Path.CGPath];
star.fillColor = [UIColor whiteColor];
star.physicsBody = nil;
int xposition = arc4random()%960;
int yposition = arc4random()%640;
star.position = CGPointMake(xposition, yposition);
float size = (arc4random()%3 + 1)/10.0;
star.xScale = size;
star.yScale = size;
star.alpha = (arc4random()%10 + 1 )/ 10.0;
star.zPosition = -2;
[self addChild:star];
}
But it takes a lot from my cpu. when the code is activated the cpu at top 78%.(I check the code in the iPhone simulator);
Somebody know how to fix it? thanks.
Your physics bodies continue to calculate even when off of the screen. You will need to remove them once they go out of the frame, otherwise everything will slow to a crawl. (And to echo what others have stated you will eventually need a real device).
From this document: Jumping Into Sprite Kit
You can implement the "Did Simulate Physics" method to get rid of the stars that fell from the bottom of the screen like so:
-(void)didSimulatePhysics
{
[self enumerateChildNodesWithName:#"star" usingBlock:^(SKNode *node, BOOL *stop) {
if (node.position.y < 0)
[node removeFromParent];
}];
}
Note that you will first need to set the name of your star shapes by using the name property like so:
star.name = "star"

How to reference the SKSpriteNode that's running an SKAction runblock?

I have a method that creates a random amount of SKSpriteNodes that are supposed to fall from the roof, sit on the ground a little while, then fade out. They need an SKPhysicsBody so they'll fall and bounce properly, but when I create a lot, it's using a lot of CPU, so I was trying to remove their physicsbody when they'd been sitting on the ground a little while.
I can't figure out how to use a runBlock to just say "do (blah) to the object calling this runBlock", is there a way?
for (int i=0; i < howManyDollars; i++) {
SKSpriteNode *bills = [SKSpriteNode spriteNodeWithImageNamed:#"bills.png"];
int startX = arc4random() % (int)self.size.width/3;
int startY = self.size.height;
bills.position = CGPointMake(startX, startY);
bills.physicsBody = [SKPhysicsBody bodyWithRectangleOfSize:CGSizeMake(bills.size.width/2, bills.size.height/2)];
bills.physicsBody.affectedByGravity = YES;
[self addChild:bills];
SKAction *wait1 = [SKAction waitForDuration:1.5];
SKAction *wait2 = [SKAction waitForDuration:2];
SKAction *removeDynamics = [SKAction runBlock:^(void) {
// this is the spot I am confused at.
//bills.physicsBody = nil;
}];
SKAction *fade = [SKAction fadeOutWithDuration:15];
SKAction *remove = [SKAction removeFromParent];
[bills runAction:[SKAction sequence:#[wait1, removeDynamics, wait2, fade, remove]]];
}
You have it already:
SKAction *removeDynamics = [SKAction runBlock:^(void) {
bills.physicsBody = nil;
NSLog(#"bills is: %# (%p)", bills, bills);
}];
The cool thing about blocks is that they retain (copy) the objects in local scope. So if you create a block like the above and dereference the bills object, then for each block it will use the corresponding bills object that runs the action.
I've added the log statement so you can see for yourself that each block when executed is referencing a different object even though the variable name is the same and even though one would think based on the traditional sequential programming model that each bills object would have long been gone or referencing only the last object created.

Handling Double Tap in Cocos2d

This question is too primitive, but I have been trying for the last 8 hours, and it is eating away my energy ( and confidence levels too :))
In my class, I have registered for Targeted touches.
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:-1 swallowsTouches:YES];
I would like to check if the user is performing single tap or double tap. In ccTouchBegan method, first gets a touch with singleTap, then gets a touch with doubleTap. I found a very interesting solution for this one, in one of the Cocos2d Forums. ( I could not locate it now.. )
The solution goes like this.
switch (touch.tapCount) {
case 2:
[NSObject cancelPreviousPerformRequestsWithTarget:self selector:#selector(handleSingleTouch:) object:singleTouchLocation];
// Handle Double Touch here
break;
case 1:
self.singleTapLocation = ccp(location.x,location.y);
self.singleTouchLocation = touch;
[self performSelector:#selector(handleSingleTouch:) withObject:touch afterDelay:0.3
];
break;
}
Everything looks to be working fine. Essentially, you schedule a single touch handler with a delay and see if it is actually a double tap. If it is a double tap, then cancel singletap handler and perform logic for double tap.
But my problem is, In the single touch handler (handleSingleTouch), I am adding a CCSprite to the UI. This action is not working. No sprite gets added. (Although the method is called.). In fact this works, If I call the selector with no delay, but with delay, the sprite is not added.
I am not a Objective C expert, So, I apologize if the question is too primitive.
Edit 1: The original thread.
Edit 2: Posting handleSingleTouch.. only relevant code.
-(void) handleSingleTouch:(UITouch * ) touch {
CGPoint location = [touch locationInView: [touch view]];
CGPoint glLocation = [MainSceneLayer locationFromTouch:touch];
//Single tap
CCLOG(#"Single tap: Adding marker image");
if(zoomedOut) {
CGPoint globalCoordinates = [quadConvertor convertLocalToGlobal:location
inActiveQuadrant:activeQuadrant];
if( [self isTouchValidForSelectedItem:globalCoordinates] == Matched) {
[self addMarkerImages:glLocation];
} else if([self isTouchValidForSelectedItem:globalCoordinates] == NotMatched) {
[missedLabel stopAllActions];
for (ImageQuadrants* quad in quadrants) {
if(quad.quadrantNumber == activeQuadrant) {
missedLabel.position = ccp((quad.center.x * -1)+glLocation.x , (quad.center.y * -1)+glLocation.y);
//markers.position = ccp((quad.center.x * -1)+touchPosition.x , 320);
}
}
id blinkAction = [CCBlink actionWithDuration:1 blinks:3];
id makeInvible = [CCCallFunc actionWithTarget:self selector:#selector(makeInvisible:)];
id seq = [CCSequence actions:blinkAction, makeInvible, nil];
[missedLabel runAction:seq];
} else {
[alreadyAccountedLabel stopAllActions];
for (ImageQuadrants* quad in quadrants) {
if(quad.quadrantNumber == activeQuadrant) {
alreadyAccountedLabel.position = ccp((quad.center.x * -1)+glLocation.x , (quad.center.y * -1)+glLocation.y);
//markers.position = ccp((quad.center.x * -1)+touchPosition.x , 320);
}
}
id blinkAction = [CCBlink actionWithDuration:1 blinks:3];
id makeInvible = [CCCallFunc actionWithTarget:self selector:#selector(makeInvisible1:)];
id seq = [CCSequence actions:blinkAction, makeInvible, nil];
[alreadyAccountedLabel runAction:seq];
}
}
swipeStartPoint = [touch locationInView:touch.view];
}
i dont know if thats a typo in your question only but your delay method is all wrong. There is no withDelay: argument. it should look like this:
[self performSelector:#selector(handleSingleTouch:) withObject:touch afterDelay:0.1];
EDIT:
Since your problem is most probably with the touch getting lost. Try [touch retain] before calling the delayed method. Another way is changing the handleSingleTouch: method to take two floats x and y or a CCPoint as arguments instead of a UITouch. Then you create the floats before the delayed method and pass them in the delayed method. This way you will avoid a memory leak from retaining a touch as well since most probably you cant release it after calling the delayed method.
hope this helps