Accessing Information on other Layers - cocos2d-iphone

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;
}
}

Related

how to create buttons with a for loop in cocos2d-iphone

Ok so I've been trying to get a level select scene working for Custom levels (levels that the user created), so i need to have a button for each created level, and i have been trying to do it with a for loop, but only one button shows up. here is my code:
-(void)AddLevelButtons{
NSArray *levels = [[NSArray alloc]init];
levels = [[NSFileManager defaultManager] contentsOfDirectoryAtPath:CustomPath error:nil];
LevelCount = (int)[levels count];
for (int counter= 0;counter<LevelCount;counter++) {
CCButton *tempButton;
tempButton=[CCButton buttonWithTitle:#"" spriteFrame:[CCSpriteFrame frameWithImageNamed:#"othercategory.png"]];
tempButton.block= ^(id sender) {
GameScene *GameNode = [[GameScene alloc]initWithPlistName:[NSString stringWithFormat:#"CustomLevel_%d",counter]withObjIndex:counter];
[[CCDirector sharedDirector] replaceScene:LevelEditorNode.scene withTransition:[CCTransition transitionFadeWithDuration:.5f]];
};
tempButton.positionType=CCPositionTypeNormalized;
tempButton.position = ccp(counter+2/5,.5f);
[ScrollingNode addChild:tempButton];
}
}
I thought that maybe both buttons were created, and they just had the same position, but i set a breakpoint, and only one button is created.

Removing random object from parent CCSprite

probably this is a simple problem to solve, since I'm quite new.
I have a scene with a waiter holding a tray of food
(the food is a random CCSprite choosen from an array)
each time he comes onscreen he holds a new piece of food
(the user touches the food and the waiter walks off to return
again with a new piece of food
however I cant seem to delete the old peice of food from the screen
as it says the child is already added...
any help would be great
-(id) init
{
///other code then...
waiterOnSCreen
= [CCSprite spriteWithSpriteFrameName:#"CatUP.png"];
waiterOnSCreen.position = ccp(CatOffSCreenPosX, catXpos);
[self addChild:waiterOnSCreen z:0];
//moving the waiter
// the random food sprite is added later to the waiter
// [waiterOnSCreen addChild:myRandomSprite];
}
-(void)LoadRandomFood
{
///I make my array here then and add CCSprites
RandomFood = [[NSMutableArray alloc]initWithObjects:
//cake,
RandomMuffin,
RandomMeat,
RandomCake,//
nil];//
int i = 0;
int count= [RandomFood count];
if (i < count)
{
int i = arc4random() % [RandomFood count];
myRandomSprite = (CCSprite *)[RandomFood objectAtIndex:i];
//waiterOnSCreen is a CCSprite added on the init
[waiterOnSCreen addChild:myRandomSprite];
myRandomSprite.position=ccp(290,220);
myRandomSprite.tag = RandomFoodTag;
}
}
later
in if(CGRectContainsPoint(waiterOnSCreen.boundingBox, location))
{
//trying to remove the food here
//Ive already tried to remove the sprite using
[self removeChildByTag:RandomeObjectTag];
//and also
CCSprite *wantedSprite = (CCSprite *)[self getChildByTag:RandomFoodTag];
[wantedSprite removeFromParentAndCleanup:YES];
}
}
Im guessing its also crashing 'child already added. It can't be added again'
as its trying to add RandomMuffin,RandomMeat,RandomCake, again
im not too sure how to fix that.
You shouldn't need to create another array just to delete a sprite. If you can provide a more complete picture of your project I would be happy to review it. Can you post the entire code file where you are doing your work? Or multiple files? On inspecting some of your samples further a few concerns in LoadRandomFood you are allocating an NSMutableArray without checking if RandomFood already has a value.
Something like this would be safer though note I am just doing a sanity check here. It would be more efficient to just fill in RandomFood once on init or elsewhere that is not going to be executed constantly.
-(void)LoadRandomFood
{
if (RandomFood)
{
[RandomFood release];
}
///I make my array here then and add CCSprites
RandomFood = [[NSMutableArray alloc]initWithObjects:
//cake,
RandomMuffin,
RandomMeat,
RandomCake,//
nil];//
int i = 0;
int count= [RandomFood count];
if (i < count)
{
int i = arc4random() % [RandomFood count];
myRandomSprite = (CCSprite *)[RandomFood objectAtIndex:i];
//waiterOnSCreen is a CCSprite added on the init
[waiterOnSCreen addChild:myRandomSprite];
myRandomSprite.position=ccp(290,220);
myRandomSprite.tag = RandomFoodTag;
}
}
In the case of the crash when adding a sprite are you sure the removeChildByTag call is being executed? Is it possible you are re-assigning the tag to a new sprite before deleting the old one? Have you stepped through the logic in the debugger? Again it would help to see a more complete code file here.
I had to make another array to access the sprite and delete it
NSMutableArray *_buffer = [NSMutableArray new];
[_buffer insertObject:myRandomSprite atIndex:0];
CCSprite *sprite = (CCSprite *)[_buffer objectAtIndex:0];
[sprite removeFromParentAndCleanup:YES];

Advanced Spritesheeting - using a spritesheet in multiple classes

So after taking a little break after being very frustrated for months on end with this issue, I am now back and attempting to solve my issue once and for all.
I’m not going to post code, because the code I have is messy and patchy and is from countless points of view.
My issue is this:
I have a sprite, and his information is stored in a class called player.m. I have a game level, and it is stored in GameLevelLayer.m
I have player.m store velocity, direction, and all the information about the character, and then I have the GameLevelLayer.m implement, move, use bounds to check collisions, etc.
I now want to make the character move using a spritesheet (I know how to do spritesheeting, I just don’t know how to do it while working with two different classes)
My question is, do I create the batchnodes and spritesheet information (CCBatchnodes, caches, etc) and all of the actions and everything in player.m, and then run them in GameLevelLayer.m? Or do I create all that in GameLevelLayer.m
I really need some help here because I’ve been stuck on this for months
EDIT:
Here is where I am, thanks to a suggestion from a community member.
This is my entire player.m (Long story short, I define everything about the player 'you might just want to pay attention to the init, because that seems to be the problem. The other stuff is my physics engine')
#import "Player.h"
#import "SimpleAudioEngine.h"
#import "GameLevelLayer.h"
#implementation Player
#synthesize velocity = _velocity;
#synthesize desiredPosition = _desiredPosition;
#synthesize onGround = _onGround;
#synthesize forwardMarch = _forwardMarch, mightAsWellJump = _mightAsWellJump, isGoingLeft = _isGoingLeft;
#synthesize WalkAction = _WalkAction;
#synthesize isMoving = _isMoving;
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super init]) {
self.velocity = ccp(0.0, 0.0);
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile: #"BankerSpriteSheet_default.plist"];
CCSpriteBatchNode *spriteSheet = [CCSpriteBatchNode batchNodeWithFile:#"BankerSpriteSheet_default.png"];
[self addChild:spriteSheet];
NSMutableArray *walkAnimFrames = [NSMutableArray array];
for(int i = 1; i <=6; ++i) {
[walkAnimFrames addObject: [[CCSpriteFrameCache sharedSpriteFrameCache] spriteFrameByName: [NSString stringWithFormat:#"banker%d.png", i]]];
CCAnimation *walkAnim = [CCAnimation animationWithFrames:walkAnimFrames delay:0.1f];
self = [super initWithSpriteFrameName:#"banker1.png"];
self.WalkAction = [CCRepeatForever actionWithAction:[CCAnimate actionWithAnimation:walkAnim restoreOriginalFrame:NO]];
//[_Banker runAction:_WalkAction];
[spriteSheet addChild:self];
[[CCAnimationCache sharedAnimationCache] addAnimation:walkAnim name:#"walkAnim"];
}
}
return self;
}
-(void)update:(ccTime)dt {
NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
[defaults synchronize];
CGPoint jumpForce = ccp(0.0, 310.0);
float jumpCutoff = 150.0;
if (self.mightAsWellJump && self.onGround) {
self.velocity = ccpAdd(self.velocity, jumpForce);
if (![defaults boolForKey:#"All Muted"]) {
if (![defaults boolForKey:#"SFX Muted"]) {
[[SimpleAudioEngine sharedEngine] playEffect:#"jump.wav"];
}
}
} else if (!self.mightAsWellJump && self.velocity.y > jumpCutoff) {
self.velocity = ccp(self.velocity.x, jumpCutoff);
}
CGPoint gravity = ccp(0.0, -450.0);
CGPoint gravityStep = ccpMult(gravity, dt);
CGPoint forwardMove = ccp(800.0, 0.0);
CGPoint forwardStep = ccpMult(forwardMove, dt);
self.velocity = ccp(self.velocity.x * 0.90, self.velocity.y); //2
if (self.forwardMarch) {
self.velocity = ccpAdd(self.velocity, forwardStep);
if (!self.isMoving) {
//[self runAction: _WalkAction];
//self.isMoving = YES;
}
} //3
CGPoint minMovement = ccp(0.0, -450.0);
CGPoint maxMovement = ccp(120.0, 250.0);
self.velocity = ccpClamp(self.velocity, minMovement, maxMovement);
self.velocity = ccpAdd(self.velocity, gravityStep);
CGPoint stepVelocity = ccpMult(self.velocity, dt);
if (!self.isGoingLeft) {
self.desiredPosition = ccpAdd(self.position, stepVelocity);
}else{
self.desiredPosition = ccp(self.position.x-stepVelocity.x, self.position.y+stepVelocity.y);
}
}
-(CGRect)collisionBoundingBox {
CGRect collisionBox = CGRectInset(self.boundingBox, 3, 0);
//CGRect collisionBox = self.boundingBox;
//collisionBox = CGRectOffset(collisionBox, 0, -2);
CGPoint diff = ccpSub(self.desiredPosition, self.position);
CGRect returnBoundingBox = CGRectOffset(collisionBox, diff.x, diff.y);
return returnBoundingBox;
}
#end
So now the issue is, how do i get the player to appear in the game like a normal sprite, and then how do I run the animations when the player begins to move forward
Your game architecture is very good! Keep player properties on player and use GameLayer only to send player actions is the best approach.
If your Player Class extends from CCSprite, put everithing related to player in your Player Class: Run animations on it, load the cache on player constructor (use the CCSprite initWithTexture:rect:rotated: or a custom autorelease constructor, like a +(id)player ).
Then, in your GameLevelLayer you will need a CCSpriteBatchNode to add your player to it. You can use this batch node to add other objects that use same spritesheet.
EDIT:
Reviewing your code, your initWithTexture is wrong. The correct is to call [super initWithTexture:]:
-(id)initWithTexture:(CCTexture2D *)texture{
if (self = [super initWithTexture:texture]) {
Then, in your GameLevelLayer, create your player and add it to a CCSpriteBatchNode:
// *** In GameLevelLayer.m ***
// Create player
Player *player = [Player spriteWithFile:#"player.png"];
// Create batch node
CCSpriteBatchNode *batchNode = [CCSpriteBatchNode batchNodeWithTexture:player.texture];
// Add batch node as child
[self addChild:batchNode];
// Add player as batch node's child
[batchNode addChild:player];
// Set player position
player.position = ccp(100,100);
Your GameLevelLayer needs to do only this to create your Player. And when it detects user input, call methods like "jump", "moveRight", "moveLeft" in your player. Also detect collisions and send actions to player like "die", "getSpecialItem".
// *** In GameLevelLayer.m ***
[player jump];
[player die];
So, your player needs to handle these methods and performs its own logic to do the actions:
// *** In Player.m ***
- (void)moveRight
{
self.position = ccpAdd(self.position, ccp(10,0));
}

EXC BAD ACCESS With Cocos2d pushscene that works for blank scene

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

Weird bug in my Cocos2d Game

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];