Disable a CCbutton until a CCActionSequence completes - objective-c

I have some code similar to below:
GameScene.m
CCButton *yesButton = [CCButton buttonWithTitle:"#YES"];
[yesButton setTarget: self selector#selector(yesButtonTapped:)];
[self addChild: yesButton];
-(void)yesButtonTapped: (id)sender
{
if(match == 1) {
[_hud updateBallNumber: ball];
}
}
HUDLayer.m
-(void)updateBallNumber: (Ball*)ball
{
int number = [ball getNumber];
id fadeIn = [CCActionFadeIn actionWithDuration:1];
id fadeOut = [CCActionFadeOut actionWithDuration:1];
id delay = [CCActionDelay actionWithDuration: randomDelay];
id change = [CCActionCallBlock actionWithBlock:^{
_ballLabel.string = [NSString stringWithFormat #"%i",number}];
id sequence = [CCActionSequence actions: fadeOut,delay,change,fadeIn,nil];
[ballLabel runAction:sequence];
}
It works as I would expect it to in that when the yesButton is pressed, the current number displayed in ball label fades out waits a random amount of time and then fades in a new number.
My question is how can I stop the yesButton being pressed again or ignore yesbutton presses until the sequence is complete and the new number is displayed.
I have tried using yesButton.enable, setting it at the beginning of -(void)yesButton to NO and then at the end to YES but this does not wait for the sequence to complete.

you could do that with blocks. Change updateBallNumber as
somewhere :
typedef void(^VoidBlock)();
in HUDLayer.m
-(void)updateBallNumber: (Ball*)ball withCompletion:(VoidBlock) completionBlock
{
int number = [ball getNumber];
VoidBlock atEnd = [completionBlock copy]; // MRC, not certain how this pans out in ARC
id fadeIn = [CCActionFadeIn actionWithDuration:1];
id fadeOut = [CCActionFadeOut actionWithDuration:1];
id delay = [CCActionDelay actionWithDuration: randomDelay];
id change = [CCActionCallBlock actionWithBlock:^{
_ballLabel.string = [NSString stringWithFormat #"%i",number}];
id complete = [CCActionCallBlock actionWithBlock:^{
atEnd();
[atEnd release]; // MRC
}];
id sequence = [CCActionSequence actions: fadeOut,delay,change,fadeIn,complete,nil];
[ballLabel runAction:sequence];
}
in GameScene
yesButton.enabled = NO;
[_hud updateBallNumber:ball withCompletion:^{
yesButton.enabled = YES;
}];

Related

Sprite Kit Objective C

I am creating a game using sprite kit but I seem to have trouble with the bodyWithTexture when using it with collisions. bodyWithRectangle and circleOfRadius work fine, but when i use bodyWithTexture it looks like the didBeginContact method is being called more than once.
here is an example of the code i'm using
-(SKNode *) createPlayer
{
level3Player = [SKNode node];
player3Sprite = [SKSpriteNode spriteNodeWithImageNamed:#"character.png"];
level3Player.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:player3Sprite.size.width/2];
level3Player.physicsBody.categoryBitMask = playerCategory3;
level3Player.physicsBody.contactTestBitMask = platformCategory3 | rockCategory;
[level3Player setPosition:CGPointMake(self.size.height/2, screenHeightL3 *11)];
level3Player.physicsBody.affectedByGravity = NO;
player3Sprite.physicsBody.dynamic = YES;
level3Player.zPosition = 2;
[level3Player setScale:0.6];
[level3Player addChild:player3Sprite];
return level3Player;
}
-(void) addRocksL3
{
int randomNumber = arc4random_uniform(300);
rock1 = [SKSpriteNode spriteNodeWithImageNamed:#"AsteroidFire.png"];
rock1.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:rock1.size.width/2];
rock1.position = CGPointMake(self.size.width * 3, randomNumber);
rock1.physicsBody.categoryBitMask = rockCategory;
rock1.physicsBody.contactTestBitMask = playerCategory3;
rock1.physicsBody.dynamic = NO;
rock1.physicsBody.affectedByGravity = NO;
rock1.zPosition = 2;
[rock1 setScale:0.3];
[foregroundLayerL3 addChild:rock1];
[self addChild:rock1];
}
-(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((secondBody.categoryBitMask == platformCategory3) | redPlatformCategory)
{
level3Player.physicsBody.velocity = CGVectorMake(0, 100);
level3Player.physicsBody.affectedByGravity = YES;
player3Sprite.texture = [SKTexture textureWithImageNamed:#"goo5.png"];
SKAction *sound1 = [SKAction playSoundFileNamed:#"squish.wav" waitForCompletion:NO];
[self runAction:sound1];
gestureRec3.enabled = YES;
}
if(secondBody.categoryBitMask == rockCategory)
{
gestureRec3.enabled = YES;
playerL3.physicsBody.velocity = CGVectorMake(0, 200);
SKAction *playSound = [SKAction playSoundFileNamed:#"Hurt.wav" waitForCompletion:NO];
[self runAction:playSound];
hitCountL3++;
}
switch (hitCountL3)
{
case 1:
[health1Level3 removeFromParent];
[self healthNodelevel31];
break;
case 2:
[hit1L3 removeFromParent];
[self healthNodeLevel32];
break;
case 3:
[hit2L3 removeFromParent];
player3Sprite.texture = [SKTexture textureWithImageNamed:#"splat.png"];
[self gameOverSplatLevel3];
didDie3 = true;
SKAction *playSplat = [SKAction playSoundFileNamed:#"splat.wav" waitForCompletion:NO];
[self runAction:playSplat];
break;
}
when i use this code my character will sometimes take 1 hit and sometimes take all 3 hits when i collide with the rock. I could use circleOfRadius which works fine, but it's not what I am really looking for. Is there anyway i could use bodyWithTexture so my character only takes 1 hit each time?
If you are experiencing multiple collisions eg. didBeginContact is called multiple times, you have few options...
Without looking at your code, lets say you have a player and a rock. Each time when player collides with rock you want to remove the rock. So, you remove the rock from its parent. But before that, you make this change in your code (pseudo code):
if([rockNode parent]){
[rockNode removeFromParent];
}
The other way would be to subclass SKSpriteNode and make a Rock class and to make a custom boolean property which will change its value once when first collision happens. But this is just unnecessary complication:
if(rockNode.canCollide){
[rockNode removeFromParent];
rockNode.canCollide = NO;
}
I had many problems with correct amount of collisions. Sometimes it would get one, sometimes none. So I tried this and it works. The only thing to change is in didBeginContact method.
I will presume that you declared categories like this:
//define collision categories
static const uint32_t category1 = 0x1 << 0;
static const uint32_t category2 = 0x1 << 1;
static const uint32_t category3 = 0x1 << 2;
Try to replace your code in didBeginContact with this one. I remember that correct collisions finally got to work after I did this.
-(void)didBeginContact:(SKPhysicsContact *)contact
{
SKNode *newFirstBody = contact.bodyA.node;
SKNode *newSecondBody = contact.bodyB.node;
uint32_t collision = newFirstBody.physicsBody.categoryBitMask | newSecondBody.physicsBody.categoryBitMask;
if (collision == (category1 | category2))
{
NSLog(#"hit");
}
}
Hope it helps

Main while loop not getting current value of global variable

I have a Xcode 5/Cocoa program that clicks the left mouse button after specified interval a specified number of times. That part works fine. The problem occurs when I want to stop the while loop prematurely.
I'm using a listener to detect any key press during the running of the program, set a stopnow variable and check for that variable in the while loop. But, the while loop doesn't detect the change in the variable until the loop finishes.
Also, I change a counter in the title bar of the window to display the count of clicks done, and that doesn't get updated either until the loop finishes.
I do get the NSLog message when I press a key.
I'm very confused.
My code is here :
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
// Insert code here to initialize your application
[[self myWindow] setLevel:NSFloatingWindowLevel];
[NSEvent addGlobalMonitorForEventsMatchingMask:NSKeyDownMask handler:^(NSEvent *event) {
keychar = (unichar) event.characters;
[NSApp activateIgnoringOtherApps:YES];
stopnow = 1;
NSLog(#"Key Pressed = x%x (%x) (%x)",keychar,(keychar&0x7f00),((keychar&0xff00)>>8));
}];
}
- (IBAction)setClickPoint:(NSButton *)sender {
sleep(5);
CGEventRef ourEvent = CGEventCreate(NULL);
cgPoint = CGEventGetLocation(ourEvent);
myPoint = [NSString stringWithFormat:#" (%5.0f,%5.0f)", cgPoint.x, cgPoint.y];
myNewTitle = [mytitle stringByAppendingString:myPoint];
[[self myWindow] setTitle:myNewTitle];
}
(IBAction)strtButton:(NSButton *)sender {
NSLog(#"Entered strButtn");
numClicks = [_nClicks intValue];
numWait = [_nWait floatValue];
i = 0;
while (i < numClicks || numClicks == 0) {
i++;
myTotal = [NSString stringWithFormat:#" %i of %i", i, numClicks];
myNewTitle = [mytitle stringByAppendingString:myPoint];
myNewTitle = [myNewTitle stringByAppendingString:myTotal];
[[self myWindow] setTitle:myNewTitle];
CGWarpMouseCursorPosition(cgPoint);
CGEventRef down = CGEventCreateMouseEvent(0, kCGEventLeftMouseDown,cgPoint, 0);
CGEventPost(kCGSessionEventTap, down);
CFRelease(down);
CGEventRef up = CGEventCreateMouseEvent(0, kCGEventLeftMouseUp,cgPoint, 0);
CGEventPost(kCGSessionEventTap, up);
CGRealease(up);
NSLog(#"stopnow = %i", stopnow);
if (stopnow == 1) {
stopnow = 0;
break;
}
usleep((unsigned int)(numWait * 1000000.0));
}
}
A Cocoa/Cocoa Touch app is an event-based environment, so you cannot have long running "loops" in the main thread, as you stop the handling and delivery of the events.
When your loop finishes, the UI is able to update the bits you are seeing, as it can now deliver the events.
You will need to do this work in the background thread, or some such.
Ok, here is what works - use dispatch_async(global type) for the main loop, use dispatch_async(main queue) for the code that updates the title.

Getting userData from SKNode array in for loop

I am having some trouble getting the userData values from my object. I know the userData is being set for the objects because else where in my code I am able to access it without any problems.
I created a SKNode *column3 to hold all the buttons that are in that column. which was added to scene like so:
column3 = [SKNode node];
[self addChild:column3];
column3.name = #"column3";
when I create the buttons I assign them to the appropriate column like so:
[column3 addChild:newButton];
Later in my code I need to loop through the column3 node group and get the #"buttonRow" userData from each object in that group. For some reason it only gives me "NULL" for the values. The NSLog's are just what I was using to test, they have no real importance to me.
I need to get this userData in order to shift all my buttons down to take up any empty spaces on screen when any button is deleted/removed from that column. Game will shift buttons down and add new ones to top to fill column back up.
I tried changing column3.children to self.children and it game me all the column nodes IE/column1, column2, column3 etc.. so I am not really sure why it does not work. Been reading and trying to figure out for a while now.
for(SKNode * child in column3.children) { //loop through all children in Column3.
SKSpriteNode* sprite = (SKSpriteNode*)child;
NSString* name = sprite.name;
NSLog(#"child name %#", name);
NSString *childRowTmp = [child.userData objectForKey:#"buttonRow"];
NSLog(#"child row %#", childRowTmp);
int childRowNumber = [childRowTmp intValue];
NSLog(#"child row # %i", childRowNumber);
}
Any help or tips would be great, thank you.
UPDATE:
here is how I create the button using my button class file I created.
//Create Blue Button.
NSString *rand = [self genRandStringLength:10];
newButton = [[ButtonNode alloc] initWithButtonType:1 column:3 row:i uniqueID:rand];
newButton.name = rand;
[column3 addChild:newButton]; //add the button to correct column
[column3Array addObject:newButton];
blueTotal++;
totalButtons++;
column3Total++;
here is the custom class file where the Object is created.
-(id)initWithButtonType:(int)buttonType column:(int)buttonColumn row:(int)buttonRow uniqueID:(NSString *)uniqueID {
self = [super init];
uniqueStr = uniqueID;
rowNumber = buttonRow;
columnNumber = 3; //this is just hard coded for testing
buttonNumber = 1;
[self addButtonBlue];
}
here is the part of the class that creates and adds the button
- (void) addButtonBlue {
SKSpriteNode *button;
//button type 1
button = [SKSpriteNode spriteNodeWithColor:[UIColor blueColor] size:kBoxSize];
button.name = uniqueStr;
button.physicsBody.categoryBitMask = blueCategory;
button.physicsBody.contactTestBitMask = blueCategory;
button.physicsBody.collisionBitMask = blueCategory | redCategory | yellowCategory | greenCategory | orangeCategory;
NSString *tmpColumn = [NSString stringWithFormat:#"%i",columnNumber];
NSString *tmpType = [NSString stringWithFormat:#"%i",buttonNumber];
NSString *tmpRow = [NSString stringWithFormat:#"%i",rowNumber];
button.userData = [NSMutableDictionary dictionary];
[button.userData setValue:uniqueStr forKey:#"buttonID"];
[button.userData setValue:tmpType forKeyPath:#"buttonType"];
[button.userData setValue:tmpColumn forKeyPath:#"buttonColumn"];
[button.userData setValue:tmpRow forKey:#"buttonRow"];
button.position = CGPointMake(xPos , yPos );
[self addChild:button];
}
I am having the same issue, see below the code which generates a green or red sprite. Attempting to give it the colour value within the SKSpriteNode.userData. In my log output the userData is (null)!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}
For a workaround I guess we could create a reference dictionary where the SKSpriteNode instances are the keys and a NSDictionary of data are the values.
//----- * UPDATE ** -------
And theres no wonder, after the userData was initialised, another sprite was being initialised in its place, leaving the userData nil!
+(SKSpriteNode *)randomSprite {
SKSpriteNode *sprite;
if ((arc4random() %(2)-1) == 0){
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"green.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"GREEN" forKey:#"COLOR"];
}else {
sprite = [SKSpriteNode spriteNodeWithImageNamed:#"red.png"];
[sprite setUserData:[NSMutableDictionary dictionary]];
[sprite.userData setValue:#"RED" forKey:#"COLOR"];
}
NSLog(#"user data = %#",sprite.userData);
sprite.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:sprite.size.width/2];
sprite.physicsBody.dynamic = YES;
return sprite;
}

How do I run a method when an action is finished

I was wondering how to call a method after a CCAction is finished (I would also like to call it every 2.5 seconds after it is first called). I have a sprite that goes to a random position, and I want it to run this method (a shooting bullet one) after it is done moving to its random position. So far the method is called when it is still moving. Can anyone help?
Here is the enemy create method:
(void)enemy1{
gjk= arc4random()%6;
enemy1 = [CCSprite spriteWithFile:#"enemy1.png"];
int d = arc4random()%480+480;
int o = arc4random()%320+320;
x = arc4random()%480;
if( x <= 480 && x>= 460){
x=x-100;
}
if(x <= 100){
x = x+50;
}
y = arc4random()%320;
if(y <=320 && y >= 290){
y = y-100;
}
if(y < 100){
y = y + 100;
}
enemy1.position = ccp(o,d);
[enemy1 runAction:[CCMoveTo actionWithDuration:3 position: ccp(x,y)]];
CCRotateBy *rotation = [CCRotateBy actionWithDuration:20 angle:1080];
CCRepeatForever * repeatforever = [CCRepeatForever actionWithAction:rotation];
[enemy1 runAction:repeatforever];
[self addChild:enemy1];
[enemy addObject :enemy1];
}
And the method for shooting a projectile:
(void)projectileShooting:(ccTime)dt {
projcount++;
if(enemy1.position.y < 320){
v = ccp(player.position.x,player.position.y);
for(CCSprite *enemies in enemy){
CCSprite * projectilebullet = [CCSprite spriteWithFile:#"Projectile.png"];
[proj addObject:projectilebullet];
[self addChild:projectilebullet];
CGPoint MyVector = ccpSub(enemies.position,player.position );
MyVector = ccpNormalize(MyVector);
MyVector = ccpMult(MyVector, enemies.contentSize.width/2);
MyVector = ccpMult(MyVector,-1);
projectilebullet.position = ccpAdd(enemies.position, MyVector);
}
}
The shooting method is called in the init method by the code, so it is called every 2.5 seconds.
[self schedule:#selector(projectileShooting:) interval:2.5];
I know I tried to make the shooting happen by making it so that is shoots when the y position is < 320, but it is still moving when it passes the position of 320.
You can make a sequence of actions and at the end of sequence you can give a callback function which will be executed at the end. Something like this
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(methodToRunAfterAction)],
nil]];
Adding on to Tayyab's answer, you can simply start the actual scheduling of your projectile firing within the startShooting method (or whatever you want to call it) which is fired at the end of your move action:
[CCSequence actions:
[CCMoveTo actionWithDuration:3 position: ccp(x,y)],
[CCCallFunc actionWithTarget:self selector:#selector(startShooting)],
nil]];
where startShooting is defined as follows:
- (void) startShooting
{
// start scheduling your projectile to fire every 2.5 seconds
[self schedule:#selector(projectileShooting:) interval:2.5];
}

Do [sprite stopActionByTag: kTag]; work differently for CCAction and CCSequence?

//prog 1
-(void)gameLogic:(ccTime)dt
{
id actionMove = [CCMoveTo actionWithDuration:1.0 position:ccp(windowSize.width/2-400, actualY)];
[actionMove setTag:6];
[self schedule:#selector(update:)];
[hitBullet runAction:actionMove];
}
-(void)update:(ccTime)dt
{
if ( (CGRectIntersectsRect(hitRect, playerRect)) )
{
[[[self getActionByTag:6] retain] autorelease];
[hitBullet stopActionByTag: 6];
}
}
//prog 2
-(void)gameLogic:(ccTime)dt
{
id actionMove = [CCMoveTo actionWithDuration:1.0 position:ccp(windowSize.width/2-400, actualY)];
id hitBulletAction = [CCSequence actionWithDuration:(intervalforEnemyshoot)];
id hitBulletSeq = [CCSequence actions: hitBulletAction, actionMove, nil];
[hitBulletSeq setTag:5];
[self schedule:#selector(update:)];
[hitBullet runAction:hitBulletSeq];
}
-(void)update:(ccTime)dt
{
if ( (CGRectIntersectsRect(hitRect, playerRect)) )
{
[[[self getActionByTag:5] retain] autorelease];
[hitBullet stopActionByTag: 5];
}
}
While prog1 is working prog2 is not working ? I think the both are same. But why the two stopActions are working differently in two prog1 and prog2 ? I mean the actions are stopped in prog1 but the actions are not stopping in prog2 ?
thank You.
Srikanth, what version of cocos2d are you using?
The answer to your question is no. All actions are treated the same.
The code responsible for removing the action is the following:
// -[CCActionManager removeActionByTag:target:]
-(void) removeActionByTag:(int) aTag target:(id)target
{
NSAssert( aTag != kActionTagInvalid, #"Invalid tag");
NSAssert( target != nil, #"Target should be ! nil");
tHashElement elementTmp;
elementTmp.target = target;
tHashElement *element = ccHashSetFind(targets, CC_HASH_INT(target), &elementTmp);
if( element ) {
NSUInteger limit = element->actions->num;
for( NSUInteger i = 0; i < limit; i++) {
CCAction *a = element->actions->arr[i];
if( a.tag == aTag && [a originalTarget]==target)
return [self removeActionAtIndex:i hashElement:element];
}
// CCLOG(#"cocos2d: removeActionByTag: Action not found!");
} else {
// CCLOG(#"cocos2d: removeActionByTag: Target not found!");
}
}
What I would recommend you do is turn on Cocos2d debugging, and un-comment those two CCLog lines. That should tell you if there's a problem in there.
If that doesn't tell you anything, it may be of interest to look into the CCSequence class. Perhaps, if the sequence is being removed, it doesn't remove the actions IN the sequence itself?
All actions are treated equal, but perhaps this Sequence action should be an exception to that.