SKSpriteNode doesn't run Action after changing parent - sprite-kit

after testing almost everything, i hope that someone of you got an idea.
It's a complex app, so i hope i get all relevant parts:
I want to "page" through SKNode's, containing some SKSpriteNodes.
After paging,the childnodes don't react on runAction:.
The Paging:
To page, i use a UIPanGestureRecognizer.
The current "page" = SKNode *currentForegroundNode.
The next "page" = SKNode *nextForegroundNode.
in (recognizer.state == UIGestureRecognizerStateChanged) {..} i'm moving the current & next page. (working)
in (recognizer.state == UIGestureRecognizerStateEnded) {...}
SKAction *moveNextForegroundToFront = [SKAction moveToX:0 duration:0.2f];
[self.nextForegroundNode runAction:moveNextForegroundToFront completion:^{
[self.currentForegroundNode removeAllChildren];
[self.nextForegroundNode enumerateChildNodesWithName:#"node_1" usingBlock:^(SKNode *node, BOOL *stop) {
//Node is kind of class SKSpriteNode
[node removeFromParent];
[self.currentForegroundNode addChild:node];
}
}];
[self.nextForegroundNode removeAllChildren];
[self.nextForegroundNode removeFromParent];
self.nextForegroundNode = nil;
}
After this, everything look's good. BUT i can't run any Actions on the child-nodes of self.currentForegroundNode with iOS7! With iOS8 installed, it works fine as expected.

Related

Collision Detection with an NSMutable array of Objects

I'm attempting to make a game with objects that bounce between the left and right walls of the screen until they reach the bottom.
I create my objects inside these methods which stores the objects in an NSMutable array and is called when the game begins.
-(void)createContent
{
self.backgroundColor = [SKColor colorWithRed:0.54 green:0.7853 blue:1.0 alpha:1.0];
world = [SKNode node];
[self addChild:world];
crabs = [NSMutableArray new];
[self performSelector:#selector(createCrabs) withObject:nil afterDelay:1];
}
-(void)createCrabs {
crab = [HHCrab crab];
[crabs addObject:crab];
[world addChild:crab];
crab.position = CGPointMake(world.scene.frame.size.width/12 , world.scene.frame.size.height/3.2);
[crab moveLeft];
//Next Spawn
[self runAction:[SKAction sequence:#[
[SKAction waitForDuration:10],
[SKAction performSelector:#selector(createCrabs) onTarget:self],
]]];
}
This will endlessly create new objects, however a problem begins with collision detection. Originally I had my collision detection set up like this:
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
[crab stop];
[crab moveRight];
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
[crab stop];
[crab moveLeft];
}
}
But, after more than one object is on the map, when the original object collides with a wall it begins to glitch and stop moving. This results in a pile up which bugs the new objects being spawned.
I've also tried to use the update CurrentTime method to see if the collision detection would improve, however as predicted, only one object will move at a time while the rest will stay still.
-(void)didBeginContact:(SKPhysicsContact *)contact {
SKPhysicsBody *firstBody, *secondBody;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask)
{
firstBody = contact.bodyA;
secondBody = contact.bodyB;
} else {
firstBody = contact.bodyB;
secondBody = contact.bodyA;
}
if (firstBody.categoryBitMask==crabCategory && secondBody.categoryBitMask == leftWallCategory) {
NSLog(#"Crab Hit Left Wall");
self.crabTouchLeftWall = YES;
} else if (firstBody.categoryBitMask == crabCategory && secondBody.categoryBitMask == rightWallCategory) {
NSLog(#"Crab Hit Right Wall");
self.crabTouchRightWall = YES;
}
}
-(void)update:(CFTimeInterval)currentTime {
/* Called before each frame is rendered */
for (HHCrab *crabNode in crabs){
if (self.crabTouchLeftWall){
[crabNode stop];
[crabNode moveRight];
self.crabTouchLeftWall = NO;
}
if (self.crabTouchRightWall){
[crabNode stop];
[crabNode moveLeft];
self.crabTouchRightWall = NO;
}
}
}
How can I fix this so when one object collides with the wall, it does not effect the movement of the other objects, only itself?
There are a couple of suggestions I have.
As you are creating multiple instances of crab and adding them into the crabs array, I suggest you give each crab a unique name property. Your can do this by having a running int counter. For example:
#implementation GameScene {
int nextObjectID;
}
Then when you create a crab instance:
nextObjectID++;
[crab setName:[NSString stringWithFormat:#"crab-%i",nextObjectID]];
I personally prefer coding my didBeginContact like this:
- (void)didBeginContact:(SKPhysicsContact *)contact {
uint32_t collision = (contact.bodyA.categoryBitMask | contact.bodyB.categoryBitMask);
if (collision == (crabCategory | leftWallCategory)) {
// figure out which crab in the array made the contact...
for(HHCrab *object in crabs) {
if(([object.name isEqualToString:contact.bodyB.node.name]) || ([object.name isEqualToString:contact.bodyA.node.name])) {
NSLog("I am %#",object.name);
}
}
}
}
Your code self.crabTouchLeftWall = YES; is not the right way to go. You should create a crab class property and set that to YES. Reason being that each crab instance needs to have these properties. The way you currently have it, all crab instances all share the same BOOLs self.crabTouchLeftWall and self.crabTouchRightWall.
You are checking for the BOOL values contacts in the update method and then running [crabNode stop];. You could do this directly when the contact is registered in the didBeginContact method.
The reason I earlier suggested you use unique names for each crab instance is because when it comes time to remove them from your array, you need to be able to point to which specific crab instance needs removing. Having a unique name just makes things easier.
Be aware that moving nodes manually like that can produce odd results. When using physics engine in Spritekit you have to let it to do calculation by itself, meaning you have to move objects by using forces. This not applies for contact detection, and you can move nodes around if you only need contact detection. But if you need more than just contact detection eg. to simulate collisions and restrict nodes from penetrating each other, then I suggest you to use forces appropriately (applyImpulse:, applyForce: are suitable methods).
You could probably solve your issue by moving the code from your update: method to didSimulatePhysics method, but that is not a solution...Because even if you move everything manually after simulation is done, you can make a mistake and for example manually initiate collision by positioning your nodes to overlap each other. So, the advice is, if you are using phyisics use it all, or don't use it at all and move nodes manually or using actions.In the case you decide not to use physics engine, you can handle collisions by using methods like CGRectIntersectsRect and similar.
And to point once again , which is not related to your question, but it can be useful: You can use physics engine for contact detection even if you move your nodes, for example by SKAction methods. Read more here.

Cocos2d: gesture recognizers and CCMenu

I have been following this tutorial on integrating UIKit with a CCLayer. Basically all I want to do is to add gesture recognizers handlers to my layer and trigger my game actions according to those.
However I do have a problem (which doesn't seem new) as the CCMenu items I added to the layer are not absorbing the clicks/taps.
In brief: I do have a layer where I integrated all the code suggested by Ray and it works perfectly except that the CCMenu doesn't absorbe clicks.
I read the post but I am not quiet comfortable with the idea of modifying CCNode and adding the method to verify if the touch is in space etc..
I thought that the easier way (for me) would be to just pass the touch on the CCMenu if the touch is over the CCMenu area.
Here a code snippet:
- (void)handleTap:(UITapGestureRecognizer *)tapRecognizer{
UIView * view = [[CCDirector sharedDirector] view];
CGPoint point = [self convertYTouch:[tapRecognizer locationInView:view] ];
if (CGRectContainsPoint([pauseMenu boundingBox], point)) {
[myMenu HowDoIPassTheTouchToTheMenu]
}
else{
//Handle single tap
}
}
Any idea on how I can pass the touch to the menu?
I tried to play around with TouchDispatcher and priority but can't get it to work.
EDIT: I wrote this method but doesn't seem to help much
-(void) activateItemForTouch:(CGPoint)point
{
if( state_ != kCCMenuStateWaiting || !visible_ || ! enabled_)
return;
for( CCNode *c = self.parent; c != nil; c = c.parent )
if( c.visible == NO )
return;
CCLOG(#"in activate item for touch");
selectedItem_ = [self itemForTouchLocation:point];
[selectedItem_ selected];
[selectedItem_ unselected];
[selectedItem_ activate];
state_ = kCCMenuStateWaiting;
}
-(CCMenuItem *) itemForTouchLocation: (CGPoint) touchLocation
{
touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
CCMenuItem* item;
CCARRAY_FOREACH(children_, item){
// ignore invisible and disabled items: issue #779, #866
if ( [item visible] && [item isEnabled] ) {
CGPoint local = [item convertToNodeSpace:touchLocation];
CGRect r = [item rect];
r.origin = CGPointZero;
if( CGRectContainsPoint( r, local ) )
return item;
}
}
return nil;
}
EDIT BIS:
I also tried to implement the UIGestureRecognizerDelegate protocol but even if I set gestureRecognizer to FALSE it never passes the gesture/touch to the menu.
-(BOOL) gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch{
CCLOG(#"should receive");
return FALSE;
}

Removing animation sprite frames from layer?

Let's say I have a character in a game and its class is like this.
#interface Player
{
CCSprite* stand;
CCAnimation* run;
}
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
[self runAction:runAniate];
}
-(void) playStandAction
{
stand.visible = YES;
[self stopAllActions];
}
The player has ability to stand or run.
But one problem is, after playStandAction is called, stand animation is visible and running animation stopped, but one frame of running animation still there!
( Now you see 'stand sprite' AND 'one of running animation frame' together. )
How can I make running animation not visible?
P.s Can anyone throw me a better way of managing animation in one character? This is totally disaster as animations added.
-(void) playStandAction
{
//Make the animation object.visible = NO; here
stand.visible = YES;
[self stopAllActions];
}
and in
-(void) playRunAction
{
// Create CCAnimate* object from CCAnimation object (run)
//Make the animation object.visible = YES; here
stand.visible = NO;
[self runAction:runAniate];
}
Use method with parameter restoreOriginalFrame and pass it yes
I don't know which method you are calling for creating CCAnimate object...
Like this:
[CCAnimate actionWithAnimation:animation restoreOriginalFrame:YES]];
And don't call runAction on layer. I would prefer you to runAction on sprite itself...
You don't need to hide and show 2 different objects...
Hope this helps. :)

Cocos2d game termination problem when a moving sprite goes inside the bounding box of a animating still sprite(a ball get into a hole)

Let me explain it in depth, when ever if(CGRectContainsPoint([hole1 boundingBox], ball1.position)) condition goes true, i do lots of stuffs, like unscheduled, a selector, destroying a ball body calling an animation (please refer Code below)etc. This work properly most of the time. But sometimes when ball is really near to hole(just touching the hole but but not enough to make the above condition true), or is been throws towards the hole really fast speed, then application got terminated. I have checked, by commenting many actions which are been performed in this section, but got nothing helpful, application keep terminating when some efforts are been done to make it terminate.
if(CGRectContainsPoint([hole1 boundingBox], ball1.position))
{
ballBody->SetLinearVelocity(b2Vec2(0.0f,0.0f));
ballBody->SetAngularVelocity(0.0f);
[self unschedule:#selector(tick:)];
self.isTouchEnabled = NO;
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
// create the sprite sheet
CCSpriteSheet *spriteSheet;
GolfBallsAppDelegate *appDelegate = (GolfBallsAppDelegate *)[[UIApplication sharedApplication] delegate];
if([appDelegate.ballValue isEqualToString:#"cricketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"cricket_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"ironball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"iron_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"golfball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"golf_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"soccerball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"soccer_ball_strip.png"];
}
else if([appDelegate.ballValue isEqualToString:#"basketball"])
{
spriteSheet = [CCSpriteSheet spriteSheetWithFile:#"basket_ball_strip.png"];
}
spriteSheet.position = ccp(hole1.position.x,60);
[self addChild:spriteSheet];
float frameWidth = 96;
float frameHeight = 84;
CCSprite *sprite = [CCSprite spriteWithTexture:spriteSheet.texture rect:CGRectMake(0, 0, frameWidth, frameHeight)];
[spriteSheet addChild:sprite];
//if(animation)
{
// create the animation
CCAnimation *spriteAnimation = [CCAnimation animationWithName:#"potting" delay:0.1f];
int frameCount = 0;
for (int x = 0; x < 6; x++)
{
// create an animation frame
CCSpriteFrame *frame = [CCSpriteFrame frameWithTexture:spriteSheet.texture rect:CGRectMake(x*frameWidth,0*frameHeight,frameWidth,frameHeight) offset:ccp(0,0)];
[spriteAnimation addFrame:frame];
frameCount++;
// stop looping after we've added 14 frames
if (frameCount == 6)
{
//[self removeChild:spriteSheet cleanup:YES];
break;
}
}
// create the action
CCAnimate *spriteAction = [CCAnimate actionWithAnimation:spriteAnimation];
//CCRepeatForever *repeat = [CCRepeatForever actionWithAction:spriteAction];
// run the action
[sprite runAction:spriteAction];
//[sprite runAction:repeat];
}
[self schedule:#selector(loading) interval:0.5];
[self schedule:#selector(holeFinish) interval:1];
//[self removeChild:spriteSheet cleanup:YES];
}
Any suggestion will be highly appreciated.
EDIT: What i found is, problem with folling lines [self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
(MAY be). but as it not occurs always, (as I mentioned), thus it's being ridiculous.
I think your problem will be that you are trying to delete a body when the b2World is 'locked', (When the world is busy working out collisions).
Try flagging the object as ready for deletion, and deleting it at the start of your next loop:
Replace:
[self removeChild:ball1 cleanup:YES];
world->DestroyBody(ballBody);
with
ball1.isDead = YES;
And at the start of your next game loop:
for (Ball b in balls)
{
if (b.isDead)
world->DestroyBody(b.ballBody);
}

Removing sprite from layer not removing rect with it (Cocos2d)?

I am making a game in Cocos2d. Everything is okay, so far. I used Ray Wenderlich's tutorial to get collision detection to work. It works, but whenever an 'enemy' spawns where a bullet was deleted (because the bullet that was deleted hit a target, therefore, was deleted), the enemy is automatically deleted, too. I think it's because it doesn't remove the rect that was declared for the sprite. Note, it also can go through more than one enemy, even though the bullet is deleted. Any help is appreciated. Thanks!
EDIT:
I found out what the problem was. I had the shoot method set in a schedule:#selector method, with no set interval. That meant that it would fire bullets 60fps fast. So I was getting TWO bullets with ONE click. They were so close together, that it took me a while to notice it. I won't make that mistake again!!!
Are you using the following code? (from How To Make A Simple iPhone Game with Cocos2D Tutorial)
- (void)update:(ccTime)dt {
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
if (targetsToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
whenever an 'enemy' spawns where a bullet was deleted, the enemy is automatically deleted, too.
It sounds that the bullet is removed from the layer, but it is not removed from _projectiles array.
[_projectiles removeObject:projectile];
Are you sure that this code works?
Rect is not a seperate entity from your bullet. Rect is the property associated with the bullet. As soon as your "bullet is deleted" the rect will no longer be valid.
What you should be looking at is your collision checking code.
You probably want to surround your bullet collision check code with a condition like:
if(bullet exists)
{
check for collision
}
Since you haven't posted code I could only post pseudo code here. Maybe if you post your collision checking code I could show you in more detail.