Situation:
I'm getting some mysterious crashing shortly after a CCCallFunc. In short, we have a button. The button has a tag to identify it later. When the button is pressed, we run some actions to animate it, and when the animation is done, we CCCallFunc another method to transition to another scene. We crash shortly after the CCCallFunc. Source and errors below.
Point Of Crash (in cocos2d source):
// From CCActionInstant.m of cocos2d
-(void) execute
{
/*** EXC_BAD_ACCESS on line 287 of CCActionInstant.m ***/
[targetCallback_ performSelector:selector_];
}
#end
Snapshot of Thread 1:
My Code:
Below is some source taken from MenuLayer.m (a simple menu to display a button).
// from MenuLayer.m
// …
#implementation MenuLayer
-(id) init{
if((self=[super init])) {
/****** Create The Play Button (As a CCMenu) ********/
CCSprite *playSprite = [CCSprite spriteWithFile:#"playbutton.png"];
CCMenuItemSprite *playItem = [CCMenuItemSprite itemFromNormalSprite:playSprite selectedSprite:nil target:self selector:#selector(animateButton:)];
playItem.tag = 3001;
playItem.position = ccp(160.0f, 240.0f);
CCMenu *menu = [CCMenu menuWithItems:playItem, nil];
menu.position = ccp(0.0f, 0.0f);
[self addChild:menu z:0];
}
}
// ...
- (void)animateButton:(id)sender{
/*** Run an animation on the button and then call a function ***/
id a1 = [CCScaleTo actionWithDuration:0.05 scale:1.25];
id a2 = [CCScaleTo actionWithDuration:0.05 scale:1.0];
id aDone = [CCCallFunc actionWithTarget:self selector:#selector(animationDone:)];
[sender runAction:[CCSequence actions:a1,a2,aDone, nil]];
}
- (void)animationDone:(id)sender{
/*** Identify button by tag ***/
/*** Call appropriate method based on tag ***/
if([(CCNode*)sender tag] == 3001){
/*** crashes around here (see CCActionInstant.m) ***/
[self goGame:sender];
}
}
-(void)goGame:(id)sender{
/*** Switch to another scene ***/
CCScene *newScene = [CCScene node];
[newScene addChild:[StageSelectLayer node]];
if ([[CCDirector sharedDirector] runningScene]) {
[[CCDirector sharedDirector] replaceScene:newScene]];
}else {
[[CCDirector sharedDirector] runWithScene:newScene];
}
}
Use CCCallFuncN instead of CCCallFun.
CCCallFuncN passes the Node as parameter, the problem with CCCallFun is that you are loosing reference of the node.
I test your code with CCCallFuncN and works ok.
Just a hunch. Besides checking for memory leaks, try to schedule a selector with a 0 second interval instead of directly sending the goGame message. I have a suspicion that director's replaceScene causes a cleanup of the scene and all objects associated with it. That in turn could leave the CCCallFunc action in an undefined state. Although normally it works fine - which is to say that this is just another indication about something sketchy, memory- respectively object-lifetime-management-wise.
Btw, if you support iOS 4 as a minimum, use CCCallBlock instead of CCCallFunc. That's safer and cleaner.
Related
I'm trying to pass an object from an SKScene to the current UIViewController where the scene is being shown, it's like I created a label that will only be triggered once the object from the scene reached a specific location, I understand that I can just easily create a SKLabel and have it added to the scene once the object reaches the location I want it to, but I'd rather do it the ViewController style way since I'll be adding a lot of objects that will do the same thing as my app Progress, that reason step aside, I did actually tried adding an sk label to see if it will work that way, Yes I was able to see the SKLabel appear upon the object reaching let's say location.x = 50 and I set the node to be removed when the object reaches location.x = 270, But the problem is it's only doing it once, after the object being added again, it seems that the scene is not removing the node even though I'm pointing my object to hit 270..
By the way, since I mentioned 2 problems, here's the code that executes the said operation for the SKlabel node which is only happening once, I want it to execute the statement one time, everytime I hit that location
if (newLocation.x==270.00 )) {
[self addingTheLabel];
}
if (newLocation.x == 50.00) {
SKAction *removingTheNode = [SKAction removeFromParent];
[self.label runAction:removingTheNode];
}
Nevermind, was able to resolve the issue now..
For those people who might encounter this,Creating a protocol on your scene will fix the issue:
#protocol gameSceneDelegate <NSObject>
-(void)testProtocol;
-(void)testProtocol2;
#end;
#interface MyScene : SKScene
#property (weak,nonatomic) id<gameSceneDelegate> delegate;
Implement it on your scene's view controller:
#interface ViewController : UIViewController<gameSceneDelegate>
in ViewController.m you need to set first your scene as your delegate:
MyScene *scenePointer = (MyScene*) scene;
[scenePointer setDelegate:self];
and finally, implement the methods on your ViewController:
-(void)testProtocol{
NSString *sampleString2 = [[NSString alloc]initWithFormat:#"This will show when testProtocol is selected"];
self.sampleLabel.text = sampleString2;
}
-(void)testProtocol2{
NSString *sampleString3 = [[NSString alloc]initWithFormat:#"This will show when test 2 protocol is selected"];
self.sampleLabel.text = sampleString2;
}
Make an if statement inside your ViewDidLoad that if your scenePoint is the delegate do the following:
if([scenePointer delegate]){
[self testProtocol];
[self testProtocol2];
}
Now, Going to your Scene, since what I want is for the label to change whenever the SpriteNode hits a specific location, what I did is:
-(void)touchesMoved:(NSSet *)touches withEvent:(UIEvent *)event{
for(UITouch *touch in touches){
CGPoint location = [touch locationInNode:self];
CGPoint newLocation = CGPointMake(location.x, 450);
if (newLocation.x == 270) {
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol2)]){
[_delegate performSelector:#selector(testProtocol2)];
}
}
if(newLocation.x <= 220){
newLocation.x = 220;
//This will trigger the method everytime the spritenode hit's this location
if([_delegate respondsToSelector:#selector(testProtocol)]){
[_delegate performSelector:#selector(testProtocol)];
}
}
self.spriteNode.position = newLocation;
}
}
It will now change the label every time your SpriteNode hits the location you want it to.
I hope this will help other's who are in the same concept as this.
import your appdelegate and then use
ViewController *vc = (ViewController*)[AppDelegate*)[[UIApplication sharedApplication] delegate] viewController];
to parse the object to your presented UIViewController
For some reason my remove function on my CCNode is not working after the animation is run.
I am adding a CCNode on click, running a animation then running the remove function.
// loads the Coin
CCNode* coin = [CCBReader load:#"heros/coin"];
coin.name =#"coin";
// position
coin.position = ccpAdd(_touchNode.position, ccp(0, 0));
//Add to Parent
[_touchNode addChild:coin z:-1];
id action1 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 200))];
id action2 = [CCActionMoveTo actionWithDuration:0.7f position:ccpAdd(_touchNode.position, ccp(0, 100))];
CCActionCallFunc *callAfterMoving = [CCActionCallFunc actionWithTarget:self selector:#selector(cleanupSprite:)];
[coin runAction: [CCActionSequence actions:action1, action2, callAfterMoving, nil]];
using the following delete function produces a crash error with This node does not contain the specified child
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChild:inSprite cleanup:YES];
}
Using the following delete also doesn't work
- (void) cleanupSprite:(CCNode*)inSprite
{
// call your destroy particles here
// remove the sprite
[self removeChildByName:#"coin" cleanup:YES];
}
self does not contain coin, _touchNode does. Do this in your callback :
[_touchNode removeChildByName:#"coin" cleanup:YES];
I have a sprite-kit game where I need to be checking often to see if the player has lost
- (void)update:(NSTimeInterval)currentTime
{
for (SKSpriteNode *sprite in self.alienArray ) {
if (sprite.position.y < 10) {
LostScene *lostScene = [[LostScene alloc] initWithSize: CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds))];
NSLog(#"about to present");
[self.view presentScene:lostScene transition:[SKTransition fadeWithDuration:0.5]];
}
}
}
but when this method gets called (which I know is happening), no scene presents. What am I doing wrong? I believe it has something to do with the transition, because when I take it out, it works fine
You should add a property of the existing scene like: BOOL playerHasLost and edit your update method:
- (void)update:(NSTimeInterval)currentTime
{
if(!playerHasLost)
{
for (SKSpriteNode *sprite in self.alienArray )
{
if (sprite.position.y < 10)
{
playerHasLost = YES;
LostScene *lostScene = [[LostScene alloc] initWithSize: CGSizeMake(CGRectGetWidth(self.view.bounds), CGRectGetHeight(self.view.bounds))];
NSLog(#"about to present");
[self.view presentScene:lostScene transition:[SKTransition fadeWithDuration:0.5]];
break;//to get out of for loop
}
}
}
}
So this way as soon as the sprite get to position that you treat as lost position it will set the variable to YES, present the scene and it will not do it again.
The reason is simply that the update method gets called every frame, with a running transition the scene will be presented anew every frame and thus it appears as if nithing is happening. You should see the NSLog spamming the log console.
I have found the following code and I need help with editing it. I am not really familiar with texture rendering.
First of all, init method takes a rect and magnifies only that area? How can I make it more dynamic and magnify only whatever is underneath the magnifying glass?
Secondly, Is it possible to change the shape to circle rather than rectangle?
Or Can I use an image as the frame of the magnifying glass?
Here is the code..
Cheers..
.h file
#import <Foundation/Foundation.h>
#import "cocos2d.h"
#interface Magnify : CCNode {
BOOL active;
CGRect rect;
CGFloat magnifyScale;
CCNode *renderNode;
CCRenderTexture *renderTexture;
}
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale;
- (void)enable;
- (void)disable;
.m file
#import "Magnify.h"
#implementation Magnify
- (id)initWithNodeToMagnify:(CCNode *)n rect:(CGRect)rectToMagnify scale:(CGFloat)scale
{
if (self = [super init]) {
self.visible = active = NO;
renderNode = n;
rect = rectToMagnify;
magnifyScale = scale;
renderTexture = [[CCRenderTexture renderTextureWithWidth:rect.size.width height:rect.size.height] retain];
[self addChild:renderTexture];
}
return self;
}
- (void)enable
{
self.visible = active = YES;
[self scheduleUpdate];
}
- (void)disable
{
self.visible = active = NO;
[self unscheduleUpdate];
}
- (void)drawAreaToTexture
{
[renderTexture beginWithClear:0.0 g:0.0 b:0.0 a:1.0];
// shift the renderNode's position to capture exactly the rect we need
CGPoint originalPosition = renderNode.position;
renderNode.position = ccpSub(originalPosition, rect.origin);
// scale the node as we want
CGFloat originalScale = renderNode.scale;
renderNode.scale = magnifyScale;
[renderNode visit];
// shift renderNode's position back
renderNode.position = originalPosition;
// scale back
renderNode.scale = originalScale;
[renderTexture end];
}
- (void)update:(ccTime)dt
{
[self drawAreaToTexture];
}
- (void)dealloc
{
[renderTexture release];
[super dealloc];
}
#end
OK, so, as I mentioned above for something like this, one possible answer is to use the CCLens3D class to get the "effect" of magnifying something in a circular manner.
I found using this to be a little tricky because it doesn't seem to work unless it's a child of the top level node of your 'scene'.
Here is some code I use to create a lens that moves around the screen, and then disappears:
// Create the lens object first.
//
CCLens3D *lens =
[CCLens3D actionWithPosition:fromPos
radius:50
grid:ccg(50, 50)
duration:2.0];
// Set the "size" of the lens effect to suit your needs.
//
[lens setLensEffect:1.0];
// In my case, I then move the lens to a new position. To apply an action on
// a lens, you need to give the actions to the actionManager in the
// CCDirector instance.
//
CCMoveTo *move = [CCMoveTo actionWithDuration:2.0 position:toPos];
// I had another action in this array, but this will do.
//
CCSequence *seq = [CCSequence actions:move, nil];
// Now tell the actionManager to move the lens. This is odd, but it works.
//
[[[CCDirector sharedDirector] actionManager] addAction:seq target:lens paused:NO];
// Now just for some more weirdness, to actually make the lens appear and operate
// you run it as an action on the node it would normally be a child of. In my case
// 'self' is the CCLayer object that is the root of the current scene.
//
// Note that the first action is the lens itself, and the second is a special
// one that stops the lens (which is a "grid" object).
//
[self runAction:[CCSequence actions:lens, [CCStopGrid action], nil]];
I imagine that you should be able to stop the grid by running the CCStopGrid action when you want to. In my case it is a programmed thing. In yours it might be when the user lets go of a button.
I'm having an odd, irritating issue.
For example, in my main menu screen, I have a button that says "Instructions."
Once I click that, in the instructions layer, there is a button that takes you back to the main menu.
However, for some reason, the button action is not exclusive to the sprite image. If i click 3 inches away from the 'backtomenu' button, it still takes me back to the main menu.
So, my question is, how can I make a button be clicked only if you click the actual image?
(this is how I create a button)
- (id) init
{
if((self = [super init]))
{
[self instructions];
}
return self;
}
- (void) instructions
{
bgI = [CCSprite spriteWithFile:#"testbackground11.png"];
[bgI setPosition:ccp(160,240)];
ccTexParams params = {GL_LINEAR, GL_LINEAR, GL_REPEAT, GL_REPEAT};
[bgI.texture setTexParameters:¶ms];
[self addChild:bgI z:0];
returnToMenu = [CCMenuItemImage itemFromNormalImage:#"berry2.png"
selectedImage:#"berry2_selected.png"
target : self
selector: #selector (ifReturnToMenu:)];
CCMenu *myReturnMenu = [CCMenu menuWithItems:returnToMenu, nil];
[myReturnMenu alignItemsVertically];
[self addChild: myReturnMenu];
}
- (void) ifReturnToMenu: (CCMenuItem *) menuItem
{
if(menuItem == returnToMenu)
[[CCDirector sharedDirector] replaceScene:
[CCTransitionFade transitionWithDuration:0.5f scene: [MainMenu scene]]];
}
I am not sure how 'isReturnToMenu' is fired, but you can try this
- (void) ifReturnToMenu: (CCMenuItem *) menuItem{
if(menuitem == returnToMenu){
[[CCDirector sharedDirector] replaceScene:
[CCTransitionFade transitionWithDuration:0.5f scene: [MainMenu scene]]];
}
}
If it doesnt work, you'll need to post the code that fires it so we can help you more