I am done making a very simple Cocos2d educational game for kids. The game shows the user bubbles with letter or number and a voice tells the user to pop the particular letter or alphabet. The game works fine most of the time but sometimes the game says "Pop the Letter R". and then when you try to touch and pop it does not pop. This happens 10% of the time which is preventing me to submit the app to app store. I am not really sure where I am missing.
The method that selects random alphabets or numbers:
The options in the below code can be a NSMutableArray of which consists of alphabets from A to Z.
-(void) populateRandomBubbles:(NSMutableArray *) options
{
int randomNumber;
int randomBubbleColor;
NSString *option = #"";
for(int i=0;i<self.gamePlaySettings.noOfBubblesToDisplay;i++)
{
randomNumber = arc4random() % options.count;
randomBubbleColor = arc4random() % self.bubbleColors.count;
option = [options objectAtIndex:randomNumber];
CGPoint point = [self getRandomPointOnScreen];
// add first bubble
Bubble *bubble = [[Bubble alloc] initWithColor:[self getRandomBubbleColor]];
bubble.delegate = self;
bubble.text = option;
bubble.soundFile = [[option stringByAppendingPathExtension:#"caf"] lowercaseString];
bubble.sprite.position = point;
bubble.tag = [option characterAtIndex:0];
[bubble setStringForLabel:bubble.text];
if([self getChildByTag:bubble.tag] == nil)
{
if( (int) (self.bubbles.count) < self.gamePlaySettings.noOfBubblesToDisplay)
{
[self.bubbles addObject:bubble];
}
}
else
{
i--;
}
}
// set the randomly selected alphabet
randomNumber = arc4random() % self.bubbles.count;
Bubble *bubble = [self.bubbles objectAtIndex:randomNumber];
bubble.isSelected = YES;
// play sound
[self.environment playSoundByFileName:bubble.soundFile];
}
The Bubble class is defined below:
#implementation Bubble
#synthesize soundFile,text,isSelected,color,label,delegate = _delegate;
-(id) initWithColor:(NSString *)bubbleFile
{
self = [super init];
self.sprite = [CCSprite spriteWithFile:bubbleFile];
[self addChild:self.sprite];
return self;
}
-(void) pop
{
CCParticleExplosion *explosion = [[CCParticleExplosion alloc] init];
explosion.position = self.sprite.position;
[explosion setAutoRemoveOnFinish:YES];
[self.parent addChild:explosion];
[self.parent removeChild:self cleanup:YES];
Environment *environment = [[Environment alloc] init];
[environment playPopSound];
}
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
if([self containsTouchLocation:touch])
{
// if this is the correct bubble then pop the bubble
if(self.isSelected)
{
[self pop];
[_delegate onBubblePop:self];
}
return YES;
}
return NO;
}
The BaseNode is defined below:
#implementation BaseNode
#synthesize sprite;
-(void) onEnter
{
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
[super onEnter];
}
-(void) onExit
{
[[CCTouchDispatcher sharedDispatcher] removeDelegate:self];
[super onExit];
}
- (CGRect)rect
{
CGSize s = [self.sprite.texture contentSize];
return CGRectMake(-s.width / 2, -s.height / 2, s.width, s.height);
}
- (BOOL)containsTouchLocation:(UITouch *)touch
{
BOOL isCollided = CGRectContainsPoint([self.sprite boundingBox], [self convertTouchToNodeSpace:touch]);
return isCollided;
}
Any ideas where the bug is?
UPDATE 1:
The following code which is also pasted in the original makes sure that there are no duplicates. It seems to be working fine since I have never encountered any duplicate.
if([self getChildByTag:bubble.tag] == nil)
{
if( (int) (self.bubbles.count) < self.gamePlaySettings.noOfBubblesToDisplay)
{
[self.bubbles addObject:bubble];
}
}
Try using this to ensure that you do not have any duplicates
int length = [YOURBUBBLEARRAY count];
NSMutableArray *indexes = [[NSMutableArray alloc] initWithCapacity:length];
for (int i=0; i<length; i++) [indexes addObject:[NSNumber numberWithInt:i]];
NSMutableArray *shuffle = [[NSMutableArray alloc] initWithCapacity:length];
while ([indexes count])
{
int index = rand()%[indexes count];
[shuffle addObject:[YOURBUBBLEARRAY objectAtIndex:index]];
[indexes removeObjectAtIndex:index];
}
[indexes release];
Bubble *bubble = [shuffle objectAtIndex:randomNumber];
Hope that helps
I might have solved the problem. I think the problem was that I was never making a check to ensure that the objects in the bubbles collection are unique. The updated code is below:
NSArray *bubbleAlreadyInArray = [self.bubbles filteredArrayUsingPredicate:[NSPredicate predicateWithFormat:#"tag == %d",bubble.tag]];
if([self getChildByTag:bubble.tag] == nil)
{
if( (int) (self.bubbles.count) < self.gamePlaySettings.noOfBubblesToDisplay)
{
// only add to the bubbles array if not already added!
if(bubbleAlreadyInArray.count == 0)
{
[self.bubbles addObject:bubble];
}
}
}
else
{
i--;
}
I wonder if there is a better way than using NSPredicate but for now it works fine and does not get duplicates.
The game works fine most of the time but sometimes the game says "Pop the Letter R".
The question is... are you popping the correct letter r? There's nothing in your code that avoids using duplicates. It's possible that there are two r's on the screen at the same time, and pressing one of them isn't triggering the action on the other.
Bubble *bubble = [self.bubbles objectAtIndex:randomNumber];
isn't setting both bubbles if there are duplicates.
it's actually here...
[self.parent removeChild:self cleanup:YES];
needs to be
[self.parent removeChild:self.sprite cleanup:YES];
Related
Please help me as am fed with the searches and new to cocos 2d. Only am getting is the last sprite got moving if i schedule addRed and i want all the sprites moving randomly in the screen. Any help will be Appreciated and thanks in advance.
-(void)addRed
{
redSprite = [CCSprite spriteWithImageNamed:#"Red.png"];;
CGSize screenSize = [[CCDirector sharedDirector] viewSize];
[redSprite setPosition:CGPointMake(screenSize.width/2, screenSize.height/2)];
[self resizeSprite:redSprite toWidth:80 toHeight:80];
[self addChild:redSprite z:10];
[self gameStart];
}
- (void)gameStart {
// Create the actions
CGSize result = [[UIScreen mainScreen] bounds].size;
CGPoint nextPoint;
if (redSprite.position.x == result.width-40.0) {
nextPoint.x = redSprite.position.x - kAccelXAxis;
backx = YES;
}
else {
if (redSprite.position.x == 40.0) {
nextPoint.x = redSprite.position.x + kAccelXAxis;
backx = NO;
}
else {
if (backx) {
nextPoint.x = redSprite.position.x - kAccelXAxis;
}
else
{
nextPoint.x = redSprite.position.x + kAccelXAxis;
}
}
}
if (redSprite.position.y == 40.0) {
nextPoint.y = redSprite.position.y + kAccelYAxis;
backy = YES;
}
else {
if (redSprite.position.y == result.height-40.0) {
nextPoint.y = redSprite.position.y - kAccelYAxis;
backy = NO;
}
else {
if (backy) {
nextPoint.y = redSprite.position.y + kAccelYAxis;
}
else
{
nextPoint.y = redSprite.position.y - kAccelYAxis;
}
}
}
CCAction *myAction = [CCActionSequence actions:[CCActionMoveTo actionWithDuration:0.01 position:nextPoint], [CCActionCallFunc actionWithTarget:self selector:#selector(gameStart)], [CCActionCallFunc actionWithTarget:self selector:#selector(updateCol:)],nil];
[myAction setTag:10];
[redSprite runAction:myAction];
}
Try creating a updater with an interval of 3 sec for creating your sprites. For your created sprites, use a CCRepeatForever which will generate your next positions for your sprite.
For your case, I also thought about using a custom CCSprite to store your next position to move.
You will need something like...
[self schedule:#selector(createSprite) interval:(3)]; //to schedule your sprite generator
// Your sprite generator with action
-(void)createSprite{
CCCustomRed *red = [CCCustomRed spriteWithFile:#"red.png"];
[self addChild:red];
CCCallFuncN *changePos = [CCCallFuncN actionWithTarget:self selector:#selector(setRandomPos:)];
CCMoveTo *move = [CCMoveTo actionWithDuration:1 position:red.nextPosition];
CCDelayTime *delay = [CCDelayTime actionWithDuration:1.0];
[red runAction:[CCRepeatForever actionWithAction:[CCSequence actions:move,changePos,delay,nil]]];
}
//Generate a random pos as your sprite next move
-(void)setRandomPos:(id)sender{
CCCustomRed *red = (CCCustomRed *) sender;
CGSize screenSize = [[CCDirector sharedDirector] winSize];
red.nextPosition = ccp(arc4random()%screenSize.width,arc4random()%screenSize.height);
}
Hope it helps :)
I have Two layers, the game and the hudlayer. Hud is on top of HelloWorldLayer.
I've got a button which I press to reload the hudlayer which is just numbers showing up on the screen to capture what's on gameplay.
buttonTapped: is declared on the HudLayer(which goes before HelloWorldLayer) implementation.
HudLayer and HelloWorldLayer are on the same file which is helloworldlayer.m
I press the button and:
- (void)buttonTapped:(id)sender
{
int number = 6;
//Heres the problem
//I dont know how to change this part...
[[HelloWorldLayer]->changedNumber = Number; ///How do I give changedNumber Number's value?????
_label.string = [NSString stringWithFormat:#"Number: %d",number];
}
HelloWorldLayer has a property:
#property (assign) int changedNumber;
Sounds like your sender is HelloWorldLayer.
You can try:
sender.changedNumber = Number;
Try this:
CCArray* layers = [[CCDirector sharedDirector] runningScene].children;
for(int i=0; i<[layers count]; i++)
{
CCNode *node = [layers objectAtIndex:i];
if ([node isKindOfClass:[HelloWorldLayer class])
{
HelloWorldLayer *obj = (HelloWorldLayer*)node;
obj.changedNumber = Number;
break;
}
}
So I am trying to do a levelup screen with pushscene/popscene. The pushscene works when it's a blank scene, but not when it's my scene that I want. My scene/layer loads completely with all images and text displaying their exact correct content. After all of the images load there is a EXC BAD ACCESS that doesn't seem to be linked to any particular message being sent. Any help or further diagnostic tests would be appreciated.
I have a version where I commented out the sprites and labels and it still crashes. Is there something big that I'm missing?
EDIT: I've added the [self = [super init]] and [super onEnter] methods and still same problem. It's something else. Any ideas?
EDITEDIT: I think this has something to do with the optionsArray I'm using, not sure what objects need to be retained. The array is a CCArray and contains NSDictionaries of differing capacities
#import "LevelupLayer.h"
#import "GameManager.h"
#implementation LevelupLayer
#synthesize optionsArray,spritesArray;
#synthesize confirmLabel;
#synthesize counter;
+(id) scene {
CCScene *scene = [CCScene node];
CCLayer* layer = [LevelupLayer node];
[scene addChild:layer];
return scene;
}
-(void)onEnter
{
counter = 1; // for debugging
//Detemine what levelups are possible
GameManager* gm = [GameManager sharedManager]; //GameManager is a helper that oversees communication between layers and plists
optionsArray = [gm possibleLevelups]; //Access plist and formats data into expected format
[optionsArray retain];
int numPossibilities = [optionsArray count];
//Build Levelup layer based on possible options
CGSize size = [[CCDirector sharedDirector] winSize];
//float positionIncrement = (size.width / numPossibilities) - ((size.width/numPossibilities) * 0.5);
float positionIncrement = (size.width / numPossibilities);
float stripWidth = size.width / numPossibilities;
for (int i = 0; i < numPossibilities; i++) {
int slot = i+1;
NSDictionary* optionDict = [optionsArray objectAtIndex:i];
NSString* name = [optionDict objectForKey:#"name"];
NSString* title = [optionDict objectForKey:#"title"];
NSString* description = [optionDict objectForKey:#"description"];
// Add the sprite
CCSprite* optionSpite = [CCSprite spriteWithSpriteFrameName:[NSString stringWithFormat:#"%#.png",name]];
[self addChild:optionSpite];
[spritesArray addObject: optionSpite];
optionSpite.position = CGPointMake(slot * positionIncrement, size.height*0.60);
[optionSpite setAnchorPoint:CGPointMake(0.5f, 0.5f)];
// Add the description
CCLabelBMFont *optionDescription = [CCLabelBMFont labelWithString:description fntFile:#"bodyFont.fnt" width:stripWidth alignment:kCCTextAlignmentCenter];
[self addChild:optionDescription];
optionDescription.position = CGPointMake(slot * positionIncrement, size.height*0.30);
[optionDescription setAnchorPoint:CGPointMake(0.5f, 0.5f)];
// Add the title
CCLabelBMFont *optionTitle = [CCLabelBMFont labelWithString:title fntFile:#"titleFont.fnt" width:stripWidth alignment:kCCTextAlignmentCenter];
[self addChild:optionTitle];
optionTitle.position = CGPointMake(slot * positionIncrement, size.height*0.90);
[optionTitle setAnchorPoint:CGPointMake(0.5f, 0.5f)];
}
[self scheduleUpdate]; //Update only prints counter to see how many frames it lasts
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
return YES;
}
-(void) update:(ccTime)delta
{
CCLOG(#"counter: %d",counter);
counter++;
}
-(void) onExit
{
[optionsArray release];
}
#end
`
i don't see any [super onEnter]; or [super init] or anything like that ...that's your problem
First add [super onEnter] on the first line in onEnter.
Second add an init method like this:
-(id)init{
if (self=[super init]){}
}
Third add [super onExit] at the end of your onExit method
I figured it out, it wasn't anything to do with the code I posted, so sorry about that. It was just a stupid release call to a non-retained array. Retained the array previously and it worked fine. Sorry about crying wolf
My problem is that animation are not working properly during movements of sprite.
Below is the code which i'm using
- (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event {
[selSprite resumeSchedulerAndActions];
CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
[self selectSpriteForTouch:touchLocation];
return TRUE;
}
- (void)selectSpriteForTouch:(CGPoint)touchLocation
{
CCSprite * newSprite = nil;
for (CCSprite *sprite in movableSprite) {
if (CGRectContainsPoint(sprite.boundingBox, touchLocation)) {
newSprite = sprite;
break;
}
}
if (newSprite != selSprite) {
[selSprite stopAllActions];
selSprite = newSprite;
_MoveableSpritetouch = TRUE;
}
if(_MoveableSpritetouch==TRUE)
{
movement=0;
CGRect selRect=CGRectMake((SpriteX)-20.0,(SpriteY)-20.0,40.0,40.0);
if(CGRectContainsPoint(selRect, touchLocation))
{
[selSprite stopAllActions];
}
if((selSprite==MarshallCar)&& (!(CGRectContainsPoint(selRect, touchLocation))))
{
movement=1;
[self reorderChild:selSprite z:5];
NSMutableArray *MarshallCarWalkAnimFrames = [NSMutableArray array];
for(int i = MarshallCarTouchStartFrameIndex; i <= MarshallCarTouchEndFrameIndex; ++i) {
[MarshallCarWalkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"mcar_move_%d.png", i]]];
}
MarshallCarWalkAnim = [CCAnimation animationWithFrames:MarshallCarWalkAnimFrames delay:MarshallCarTouchFrameDelay];
walkMarshallCar = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:MarshallCarWalkAnim restoreOriginalFrame:NO]];
[selSprite runAction:walkMarshallCar];
}
}
}
- (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event {
if(gameState == TRUE){
CGPoint point = [touch locationInView:[touch view]];
point = [[CCDirector sharedDirector] convertToGL:point];
if (moveDifference.x>0)
{
selSprite.flipX = YES;
}
else {
selSprite.flipX = NO;
}
[selSprite setPosition:point];
}
}
-(void)ccTouchEnded:(UITouch *)touch withEvent:(UIEvent *)event
{
movement=0;
if(selSprite==MarshallCar)
{
[selSprite setDisplayFrame:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:#"mcar_idle.png"]];
}
[selSprite pauseSchedulerAndActions];
}
The animation frames for movement are not playing every time during movements sometimes it plays or sometimes not. It plays properly when you touch and move your sprite for the first time but if touch another sprite and then again move the previous sprite the animations for movement won't play.
Is anyone having any idea why this is happening?
Please tell me the proper code for removing this bug.
Thanks!!!
I believe your problem is the if/else if construct:
if (_MoveableSpritetouch==TRUE)
{
CGRect selRect = CGRectMake(SpriteX - 20, SpriteY - 20, 40, 40);
if(CGRectContainsPoint(selRect, touchLocation))
{
[selSprite stopAllActions];
}
else if(...)
{
...
[selSprite runAction:walkMarshallCar];
}
}
If you don't see it right away: if the touch location is inside the selRect, you call stopAllActions on the selected (new) sprite and do nothing else. Only if the touch location is not within that rectangle you'll run the animation action.
I think the "in rectangle" check is superfluous since you've already called stopAllActions anyway a few lines above.
Allow me a few general remarks about your code:
The method "selectSpriteForTouch" tells me that you're selecting a new sprite. The function does that. But it does not advertise playing an animation. You might want to refactor this out to a seperate "playAnimationOnSelectedSprite" method.
You wrote 20.0 and 40.0 several times. This means you're actually passing a double (8 bytes floating point data type) to a CGPoint which takes floats (4 bytes floating point). Strictly speaking use either 20.0f with the suffixed "f" to denote it as a floating point data type, or use integers since you don't use the floating point part.
Why you put (SpriteX) in brackets is not clear to me, if you want to enhance readability you'll achieve more by adding spaces after commas and operands.
In Objective-C, use YES and NO macros instead of TRUE and FALSE.
The bool variable _MoveableSpritetouch seems superfluous, unless needed someplace else. In any case you should move the following if(_MoveableSpritetouch==TRUE) block to where you set the _MoveableSpritetouch variable to TRUE because it just makes your code harder to read by setting a variable, leaving the code block you were in ( if(selSprite != newSprite) ) just to jump into another block of code ( if(_MoveableSpritetouch==TRUE) ) that you already know you're going to run anyway.
if((selSprite==MarshallCar)&& (!(CGRectContainsPoint(selRect, touchLocation))))
{
movement=1;
[self reorderChild:selSprite z:5];
NSMutableArray *MarshallCarWalkAnimFrames = [NSMutableArray array];
for(int i = MarshallCarTouchStartFrameIndex; i <= MarshallCarTouchEndFrameIndex; ++i) {
[MarshallCarWalkAnimFrames addObject:[[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName:[NSString stringWithFormat:#"mcar_move_%d.png", i]]];
}
MarshallCarWalkAnim = [CCAnimation animationWithFrames:MarshallCarWalkAnimFrames delay:MarshallCarTouchFrameDelay];
walkMarshallCar = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:MarshallCarWalkAnim restoreOriginalFrame:NO]];
[selSprite runAction:walkMarshallCar];
}
I have added [selSprite stopAllActions];
and it started working correctly because in the touch ended method i was pausing the actions
but not resuming them so when i touch the sprite for the second time it was not playing animation because the action was paused.
I'm trying to make a effect using the CCBitmapFontAtlas, here is what I want:
The string say "ABCDEFG" being dispayed one by one, each one won't be displayed
until the one before is completely displayed.
And here is what I tried:
-(id) init
{
if( (self=[super init] )) {
label = [CCBitmapFontAtlas bitmapFontAtlasWithString:#"ABC" fntFile:#"bitmapFontTest.fnt"];
[self addChild:label];
CGSize s = [[CCDirector sharedDirector] winSize];
label.position = ccp(s.width/2, s.height/2);
label.anchorPoint = ccp(0.5f, 0.5f);
label.visible = NO; //hide it first
[[CCTouchDispatcher sharedDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
}
return self;
}
-(BOOL) ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
{
CCSprite AChar = (CCSprite) [label getChildByTag:0];
CCSprite BChar = (CCSprite) [label getChildByTag:1];
CCSprite CChar = (CCSprite) [label getChildByTag:2];
id fade_in = [CCFadeIn actionWithDuration:3];
label.visible = YES;
[AChar runAction:fade_in];
[BChar runAction:fade_in];
[CChar runAction:fade_in];
return YES;
}
The effect is the "ABC" will fade in once I touched the screen, then I tried to use the
CallFuncND to call the next string to fade in while the current string is displayed.
But this seems to make things very complex.
Is there a easier way to get this effect done?
Any suggestion will be appreciate.
I feel like you are going in the right direction with this one. You could have each letter be a separate sprite and store them in an array and then run them each one by one.
The call function can be started by:
[self displayNextSprite:spriteArray nextIndex:0];
And the function is:
// Warning, This assumes you are not passing it an empty array, you may want to put in a check for that
-(void)displayNextSprite:(NSMutableArray*)spriteArray nextIndex:(NSUInteger)nextIndex
{
CCSprite *nextSprite = [spriteArray objectAtIndex:nextIndex];
id action1 = [CCFadeIn actionWithDuration:3];
// or = [CCPropertyAction actionWithDuration:3 key:#"opacity" from:0 to:255];
// The last letter
if(nextIndex == ([spriteArray count] - 1))
{
[nextSprite runAction:action1];
}
else // continue to the next letter
{
id callFunc = [CCCallFunc actionWithTarget:self selector:#selector(displayNextSprite:spriteArray nextIndex:nextIndex+1)];
id sequence = [CCSequence actionOne:action1 two:callFunc];
[nextSprite runAction:sequence];
}
}