Cocos2d: ARC releasing CCSprite too soon? - cocos2d-iphone

I enabled ARC for my cocos2d project.
Now i try doing the following:
BuildTowerMenu *menu = [BuildTowerMenu menuAtLocation:tileScreenPos];
[self addChild:menu];
And in the BuildTowerMenu class:
+(id)menuAtLocation:(CGPoint)location {
return [[self alloc] initMenuAt:location];
}
-(id) initMenuAt:(CGPoint)location {
if (self = [super init]) {
self.position = location;
CCSprite *item1 = [CCSprite spriteWithFile:#"Icon.png"];
item1.position = location;
[self addChild:item1];
}
return self;
}
But for some reason, the Sprite never shows up. After a bit of debugging i see that when i return from menuAtLocation, the CCSprite is still in the Array of children of BuildTowerMenu, but empty (only got an id).
If i actually add the sprite from outside it works and the Sprite is displayed:
BuildTowerMenu *menu = [BuildTowerMenu menuAtLocation:tileScreenPos];
CCSprite *item1 = [CCSprite spriteWithFile:#"Icon.png"];
item1.position = location;
[menu addChild:item1];
[self addChild:menu];
Any hints on what i did wrong here?
P.S.: i added a breakpoint in the dealloc of CCSprite, which never gets called (i guess it should be called if ARC is releasing it)...

I think that the problem is that you set equal positions to your menu and sprite.
I mean that this part of code
CGPoint location = ccp(200.f, 200.f);
[menu setPosition: location];
[sprite setPosition: location];
[menu addChild: sprite];
will add your sprite with position position (400.f, 400.f), relatieve to the menu's parent. You are doing almost the same thing in your BuildTowerMenu's initMenuAt: method/

As already commented on the initial question_:
Ok, it had nothing to do with ARC; the Sprite which seemed to be released was there, it was just a debugger - bug which didnt display it correctly. The problem was actually the line self.position = location; After moving the setposition after the addChild, everything worked.

Related

Why is Cocos2D update only firing when adding sprite to parent's CCSpriteBatchNode from child

I don't understand why the following is happening and I hope someones here can explain it.
I have a GameLayer (CCLayer) Class and a Food (CCNode) Class.
In the Gamelayer Class I create a bunch of food Objects that have a sprite as a property.
And I want to add these sprites to a CCSpriteBatchNode.
spriteBatch = [CCSpriteBatchNode batchNodeWithFile:#"bol.png"];
[self addChild:spriteBatch];
for (int i = 0; i < 1000; i++) {
Food * tempfood = [Food foodWithParentNode:self];
[spriteBatch addChild:[tempfood mySprite]];
[self addChild:tempfood];
}
When I use the code above, the sprites all show up on screen but don't move. (They should because I scheduled an Update in the Food Class (see below) and in this update the position of the food objects is changed)
-(id)initWithParentNode:(CCNode *)parentNode{
if((self = [super init]))
{
mySprite = [CCSprite spriteWithFile:#"bol.png"];
CGSize screenSize = [[CCDirector sharedDirector] winSize];
[[self mySprite] setPosition:CGPointMake(screenSize.width/2, screenSize.height/2)];
[self scheduleUpdate];
}
return self;
}
-(void) update:(ccTime)delta
{
... DO SOME position calculations ...
[[self mySprite] setPosition:CGPointMake(newX, newY)];
}
But if I move the code that adds the sprite to the batch from the Game class to the food class the update that changes the position does work and the foods are moving on screen.
BUT WHY?
So this gives:
-(id)initWithParentNode:(CCNode *)parentNode{
if((self = [super init]))
{
mySprite = [CCSprite spriteWithFile:#"bol.png"];
CGSize screenSize = [[CCDirector sharedDirector] winSize];
[[self mySprite] setPosition:CGPointMake(screenSize.width/2, screenSize.height/2)];
[[ (GameLayer*) parentNode spriteBatch] addChild:mySprite];
[self scheduleUpdate];
}
return self;
}
I really can't see the difference between calling
[[ (GameLayer*) parentNode spriteBatch] addChild:mySprite];
from the food class or :
[spriteBatch addChild:[tempfood mySprite]];
from the 'parent' GameLayer
Ruben, the mySprite is a property with retain attribute?
The Food class can be loosing the memory reference of this property...
on init, try to set mySprite using self.mySprite, to retain this.
on .m or .h, put:
#property (nonatomic, retain) CCSprite *mySprite
and on init, use:
self.mySprite = [CCSprite spriteWithFile:#"bol.png"];

Why is my main menu image wrong sized on launch but not after a game?

I have an image that loads on a MainMenu scene and appears as wrong sized when the game launches but then after finishing a level returns you to the MainMenu again the image appears right sized.
The first time the image appears to be three quarters or 4/5 of the size of the screen from left to right. The image has a white background around it so I can see a black tall rectangle in the right side of the screen on the main menu's first launch. But after a game it is sized properly and the white background image is sized properly.
Anybody ever have this happen?
Here is the init code for the MainMenuLayer:
-(id)init {
if( (self=[super initWithColor:ccc4(255,255,255,255)]) ) {
[[GameManager sharedGameManager] playBackgroundTrack:BACKGROUND_TRACK_MAIN_MENU];
CGSize screenSize = [CCDirector sharedDirector].winSize;
if (UI_USER_INTERFACE_IDIOM() == UIUserInterfaceIdiomPad) {
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu-iPad.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
} else {
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
}
-
It works fine on the simulator though...
Print screen position, if position is wrong then put you code in onEnter instead of init.
See this thread in Stack overflow.
-(id)init {
if( (self=[super initWithColor:ccc4(255,255,255,255)]) ) {
}
return self;
}
-(void)onEnter {
[super onEnter];
[[GameManager sharedGameManager] playBackgroundTrack:BACKGROUND_TRACK_MAIN_MENU];
CGSize screenSize = [CCDirector sharedDirector].winSize;
CCSprite *background = [CCSprite spriteWithFile:#"MainMenu.png"];
[background setPosition:ccp(screenSize.width/2,screenSize.height/2)];
[self addChild:background];
[self displayMainMenu];
}
The image was apparently being cached even after clean and build! I ended up replacing the image by removing the original and adding the same one but with some markers just to make sure it got replaced.

removing texture

I am having some problem with removing my spritesheets after my scene exit..
I basically follow Ray's instructions by removing unused textures in init
[[CCSpriteFrameCache sharedSpriteFrameCache] removeUnusedSpriteFrames];
[[CCTextureCache sharedTextureCache] removeUnusedTextures];
[[CCDirector sharedDirector] purgeCachedData];
and in dealloc I have
CCTexture2D * texture = spriteNode.textureAtlas.texture;
[[CCSpriteFrameCache sharedSpriteFrameCache] removeSpriteFramesFromTexture:texture];
[[CCTextureCache sharedTextureCache] removeTexture:texture];
Reference
This works fine if the transition to scene is not the current scene.
But when I tried to "restart" the current scene it crashes.
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:2.0 scene:[currentScene scene] withColor:ccBLACK]];
Seems like the problem is that when replacing the current scene by the "new" current scene.. the "new" current scene init is called before the current scene is deallocated. Hence my "new" current scene spritesheet got deallocated right after it's being created.
How can I properly remove the spritesheets in this case?
Or is my approach to restart the current scene incorrect?
Update:
I was trying to add a loading scene as advised but couldn't make it work.. here's my loading scene
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
LoadingLayer * layer = [[[LoadingLayer alloc]init]autorelease];
// add layer as a child to scene
[scene addChild: layer];
// return the scene
return scene;
}
-(id) init{
if(self = [super init]){
winSize = [CCDirector sharedDirector].winSize;
CCLabelTTF * loadingLabel = [CCLabelTTF labelWithString:#"Loading..." fontName:#"Marker Felt" fontSize:30];
}
loadingLabel.position = ccp(winSize.width/2, winSize.height/2);
[loadingLayer addChild:loadingLabel];
[self scheduleUpdate];
}
return self;
}
-(void) update:(ccTime)dt{
[self unscheduleUpdate];
NSString * bgPlist = #"backgroundsIPAD.plist";;
NSString * hudPlist = #"hudSpritesIPAD.plist"
NSString * gameOnePlist = #"gameOneSpritesIPAD.plist";
// load background
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:bgPlist];
// load hud
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:hudPlist];
// load sprites
[[CCSpriteFrameCache sharedSpriteFrameCache] addSpriteFramesWithFile:gameOnePlist];
[[CCDirector sharedDirector] replaceScene:[CCTransitionFade transitionWithDuration:2.0 scene:[GameTwoLayer scene] withColor:ccBLACK]];
}
This will give me a splash of the GameTwoLayer in this case, then a black screen..
What am I doing wrong?
Indeed, the new scene's init runs before your current scene is deallocated. Makes sense, since you're creating a new scene object within your current scene object.
You should not remove textures etc during init for that reason. It's just not the right place to do so. The scene that has been using the resources should be responsible for removing those resources. Removing textures etc should be done in the dealloc method of the scene.
If you need to avoid the memory spike with both scenes in memory at the same time, you'll have to add an in-between "loading" scene so that the previous scene will be deallocated before the next scene starts.

CCRepeatForever action stops automatically while switching scenes

I am developing a simple game app.
I am required to have two screens one on left & other on right, just like on scroll view with pagination having 2 screens to toggle between. I am detecting swipes with the help of RootViewController class and successfully swiping left and right screens.
But the problem is I have to have infinitely running rotateBy action on both screens each running this action on single sprite placed on center of screen.
I am using a main scene called SelectWorld & two are it's sub scenes.
First screen is called factory & other is called stack.
Following is my code:
Select World Screen:
#implementation SelectWorld
+(CCScene*)scene
{
CCScene* scene = [CCScene node];
SelectWorld* layer = [SelectWorld node];
[scene addChild:layer];
return scene;
}
-(id)init
{
if((self = [super init]))
{
[RootViewController singleton].swipeCallBackHandler = self;
[[RootViewController singleton] enableSwipeDetection];
factory = [[ColorfulFactory scene] retain];
stack = [[CoinsStack scene] retain];
[self addChild:factory];
isOnFactory = YES;
}
return self;
}
-(void)swipedLeft
{
if(isOnFactory)
{
[self removeAllChildrenWithCleanup:YES];
isOnFactory = NO;
CCTransitionScene* transitionalScene = [CCTransitionSlideInR transitionWithDuration:0.3 scene:stack];
[[CCDirector sharedDirector] replaceScene:transitionalScene];
}
}
-(void)swipedRight
{
if(!isOnFactory)
{
[self removeAllChildrenWithCleanup:YES];
isOnFactory = YES;
CCTransitionScene* transitionalScene = [CCTransitionSlideInL transitionWithDuration:0.3 scene:factory];
[[CCDirector sharedDirector] replaceScene:transitionalScene];
}
}
Here's the code for factory only, same goes for stack-
#implementation ColorfulFactory
+(CCScene*)scene
{
CCScene* scene = [CCScene node];
ColorfulFactory* layer = [ColorfulFactory node];
[scene addChild:layer];
return scene;
}
-(id)init
{
if((self = [super init]))
{
CGSize size = [CCDirector sharedDirector].winSize;
CCLabelTTF* info = [CCLabelTTF labelWithString:#"Colorful Factory" fontName:#"Helvetica" fontSize:35.0];
info.position = ccp(size.width/2, size.height-50);
[self addChild:info];
CCSprite* sprite = [CCSprite spriteWithFile:#"Icon.png"];
sprite.position = ccp(size.width/2, size.height/2);
[self addChild:sprite];
sprite.tag = 123;
CCRotateBy* rotateBy = [CCRotateBy actionWithDuration:60 angle:360.0];
CCRepeatForever* repeatForever = [CCRepeatForever actionWithAction:rotateBy];
[sprite runAction:repeatForever];
repeatForever.tag = 456;
}
return self;
}
Now the problem is, in first two swipes actions are running fine, but as soon as I try to swipe more than two or three times, actions got stopped. I haven't written a single line to stop this in both the classes. I require it there running forever.
Your mistake is that you're re-using the same scene object for every scene change. You can't do that in cocos2d, you have to create a new instance of the scene object.
You should never retain a scene object, since there ought to be only one active scene at any one time (with the exception of the duration of a scene transitioning to another scene - but that's handled by cocos2d internally).
For example:
CCScene* factory = [ColorfulFactory scene];
CCTransitionScene* transitionalScene = [CCTransitionSlideInR transitionWithDuration:0.3 scene:factory];
[[CCDirector sharedDirector] replaceScene:transitionalScene];
If you need both objects in memory, make them layers (or nodes), not scenes. Don't use replaceScene but instead animate the layers with regular moveTo actions in and out of the screen.

Using glScissor on a modal layer in cocos2d

I am coding a modal layer in cocos2d and I would like to use the OpenGL glScissor API call to crop the inside of a CCScrollLayer that I am using,
Basically,
Present some kind of Modal sprite
Put CCScrollLayer with assets on the modal sprite
I want to crop the CCScrollLayer so that it does not overflow the sprite.
Seems pretty simple.
The problem I'm having is that the visit function never seems to be get hit and thus glScissor never gets implemented.
I'm not sure what I'm doing wrong.
The way I do modals is I use blocks to handle a Yes and No condition/state.
// This appears in my "Pick City scene"
-(id) init
{
if ((self = [super init]))
{
NSLog(#"City list");
for (City *cityObj in listOfCities)
{
citySpriteOff = [CCSprite spriteWithSpriteFrameName:#"city.png"];
citySpriteOn = [CCSprite spriteWithSpriteFrameName:#"city_on.png"];
int x = [cityObj.x intValue];
int y = [cityObj.y intValue];
CCMenuItemSprite *mapMenuItem = [CCMenuItemSprite itemFromNormalSprite:citySpriteOff selectedSprite:citySpriteOn target:self selector:#selector(btnCity:)];
[mapMenuItem setTag:i];
[mapMenuItem setIsRelativeAnchorPoint:YES];
[mapMenuItem setAnchorPoint:CGPointMake(0, 0)];
[mapMenuItem setPosition:CGPointMake(x, y)];
[mapMenuItem setIsEnabled:YES];
[mapMenu addChild:mapMenuItem];
i++;
} // next
[self addChild:mapMenu z:2];
}
}
-(void) btnCity:(id)sender
{
NSLog(#"clicked a city button");
int tag = [sender tag];
City *cityObj = [listOfCities objectAtIndex:tag];
NSLog(#"You clicked on city: %#", cityObj.name);
[self lockLayers];
CCLayer *layer = [CCLayer node];
[self addChild:layer z:100];
[MapModalLayer ConfirmCity:cityObj onLayer:layer yesBlock:^{[self btnConfirmedCity:cityObj];} noBlock:^{[self unlockLayers];}];
}
-(void) btnConfirmedCity:(City *)cityObj
{
NSLog(#"confirmed city: %#", cityObj.name);
}
#pragma mark - Lock/Unlock layers
-(void) lockLayers
{
[self MenuStatus:NO Node:self];
}
-(void) unlockLayers
{
[self MenuStatus:YES Node:self];
}
// Disabled/Enable layers
-(void) MenuStatus:(BOOL)_enable Node:(id)_node
{
for (id result in ((CCNode *)_node).children) {
if ([result isKindOfClass:[CCMenu class]]) {
for (id result1 in ((CCMenu *)result).children) {
if ([result1 isKindOfClass:[CCMenuItem class]]) {
((CCMenuItem *)result1).isEnabled = _enable;
}
}
}
else
[self MenuStatus:_enable Node:result];
} // next
}
The actual modal code appears here,
#implementation MapModalLayer
- (id)init {
self = [super init];
if (self) {
// This method never seems to be called
NSLog(#"MapModalLayer init");
}
return self;
}
// This method never seems to be called
- (void) visit {
NSLog(#"Visit");
if (!self.visible)
return;
glPushMatrix();
glEnable(GL_SCISSOR_TEST);
glScissor(50, 50, 100 , 150);
[super visit];
glDisable(GL_SCISSOR_TEST);
glPopMatrix();
}
+ (void) CloseAlert: (CCSprite*) alertDialog onCoverLayer: (CCLayer*) coverLayer executingBlock: (void(^)())block {
// shrink dialog box
[alertDialog runAction:[CCScaleTo actionWithDuration:kAnimationTime scale:0]];
// in parallel, fadeout and remove cover layer and execute block
// (note: you can't use CCFadeOut since we don't start at opacity 1!)
[coverLayer runAction:[CCSequence actions:
[CCFadeTo actionWithDuration:0.2f opacity:0],
[CCCallBlock actionWithBlock:^{
[coverLayer removeFromParentAndCleanup:YES];
if (block) block();
}],
nil]];
}
+(void) ConfirmCity:(City *)cityObj onLayer:(CCLayer *)layer yesBlock :(void (^)())yesBlock noBlock:(void (^)())noBlock
{
CCLayerColor *coverLayer = [CoverLayer new];
[layer addChild:coverLayer z:INT_MAX]; // put to the very top to block application touches
[coverLayer runAction:[CCFadeTo actionWithDuration:kAnimationTime opacity:80]]; // smooth fade-in to dim with semi-transparency
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite *dialog = [CCSprite spriteWithSpriteFrameName:#"modal.png"];
[dialog setPosition:CGPointMake(winSize.width/2,winSize.height/2)];
[dialog setTag:kDialogTag];
//
// We put our CCScrollLayer *scroller content here... it doesn't matter what it is right now
//
//
// Finally we put our accept/reject buttons
//
// Tick/Cross buttons
CCSprite *closeButtonOn = [CCSprite spriteWithSpriteFrameName:#"btn_close.png"];
CCSprite *tickButtonOn = [CCSprite spriteWithSpriteFrameName:#"btn_accept.png"];
// add one or two buttons, as needed
CCMenuItemSprite *opt1Button = [CCMenuItemSprite itemFromNormalSprite:closeButtonOn
selectedSprite:nil
block:^(id sender){
// close alert and call opt1block when first button is pressed
[self CloseAlert:dialog onCoverLayer: coverLayer executingBlock:noBlock];
} ];
[opt1Button setPosition:CGPointMake(-200, -120)];
// create second button, if requested
CCMenuItemSprite *opt2Button = [CCMenuItemSprite itemFromNormalSprite:tickButtonOn
selectedSprite:nil
block:^(id sender){
// close alert and call opt2block when second button is pressed
[self CloseAlert:dialog onCoverLayer: coverLayer executingBlock:yesBlock];
} ];
[opt2Button setPosition:CGPointMake(40, -120)];
CCMenu *menu = [CCMenu menuWithItems:opt1Button, opt2Button, nil];
[menu setContentSize:dialog.contentSize];
[dialog addChild:menu z:2];
[coverLayer addChild:dialog];
}
#end
I think its because I only ever use MapModalLayer's private methods only.
But I am not sure.
Is there a way to allow me to use the glScissor in a modal like explained above?
I've tried moving the glScissor code to the display of the modal, but it never seems to do anything.
To confirm it is working, I moved the glScissor code to the parent and it seems to work fine.
Thus, how do I make the modal layer use/work with glScissor?
I have since resolved this problem.
I used the Viewport from http://pastebin.com/tWsEbxvJ
The way I did it was:
CoverLayer *coverLayer = [CoverLayer new];
[layer addChild:coverLayer z:INT_MAX]; // put to the very top to block application touches
[coverLayer runAction:[CCFadeTo actionWithDuration:kAnimationTime opacity:80]]; // smooth fade-in to dim with semi-transparency
// ------------------------------
CGSize winSize = [[CCDirector sharedDirector] winSize];
CCSprite *dialog = [CCSprite spriteWithSpriteFrameName:#"modal.png"];
[dialog setPosition:CGPointMake(winSize.width/2,winSize.height/2)];
[dialog setTag:kDialogTag];
.. // Build your pagesArray here for ScrollLayer...
// Now create the scroller and pass-in the pages (set widthOffset to 0 for fullscreen pages)
CCScrollLayer *scroller = [[CCScrollLayer alloc] initWithLayers:pagesArray widthOffset: 350];
[scroller setShowPagesIndicator:NO];
// finally add the scroller to your scene
//[dialog addChild:scroller];
Viewport *cn = [[Viewport alloc] initWithRect:CGRectMake(3, 0, dialog.contentSize.width-12, dialog.contentSize.height-5)];
[cn addChild:scroller];
[scroller release];
[coverLayer addChild:dialog z:1];
This allows me to put a modal on a layer, put a CCScrollLayer and make sure that the contents do not spill over the internals of the modal sprite I have made (this is why the width configuration looks a bit odd).
Thanks.