In my code I have made a GameManager singleton which has a method responsible for changing scenes. The first scene I call is the MenuScene after that I replace it with the GameScene. When I do this the Console output shows:
2013-10-07 19:40:55.895 MyGame[56164:a0b] -[MenuScene distance]: unrecognized selector sent to instance 0xb460690
2013-10-07 19:40:56.011 MyGame[56164:a0b] *** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: '-[MenuScene distance]: unrecognized selector sent to instance 0xb460690'
From the log, I don't understand why I get -[MenuScene distance] ... because distance is a property of GameScene not MenuScene.
Below is GameManager method for changing scenes:
-(void)runSceneWithID:(SceneTypes)sceneID {
SceneTypes oldScene = currentScene;
currentScene = sceneID;
//NSString* str;
id sceneToRun = nil;
switch (sceneID)
{
case kBeginScene:
sceneToRun = [BeginScene node];
break;
case kGameScene:
sceneToRun = [GameScene node];
break;
case kMenuScene:
sceneToRun = [MenuScene node];
break;
default:
CCLOG(#"Unknown ID, cannot switch scenes");
return;
break;
}
if (sceneToRun == nil) {
// Revert back, since no new scene was found
currentScene = oldScene;
return;
}
if ([[CCDirector sharedDirector] runningScene] == nil) {
[[CCDirector sharedDirector] runWithScene:sceneToRun];
} else {
[[CCDirector sharedDirector] replaceScene:sceneToRun];
}
}
Also the call to replace scene is in a layer class which is part of the MenuScene. See below:
-(void)startGameScene {
[[GameManager sharedGameManager] runSceneWithID:kGameScene];
}
Please help.
You get this message because the distance message was sent to the MenuScene instance which doesn't have this selector (GameScene does apparently).
So probably somewhere in your scene managing singleton something goes wrong and you still (or already) have a MenuScene instance where you expect to have a GameScene instance.
Add an exception breakpoint in Xcode to see exactly where the message was coming from.
PS: be very careful when managing scenes in a global instance like a singleton. You could easily leak memory if you keep a strong reference to a scene (or any node for that matter) in a global instance/variable. Make sure that every scene has the dealloc method implemented with a log to see that it actually does deallocate.
Related
Using Cocos2d-x and C++, I'm trying to play and pause an animation for a sprite.
I'm using version 3.15.1 of Cocos2dx.
I have a class called PlayerSprite which is derrived from the cocos2d::Sprite class. Inside PlayerSprite initialization, I've setup my animation with the following code:
SpriteBatchNode* playerSpriteBatch = SpriteBatchNode::create("player.png");
SpriteFrameCache* spriteFrameCache = SpriteFrameCache::getInstance();
spriteFrameCache->addSpriteFramesWithFile("player.plist");
Vector<SpriteFrame*> animFrames(2);
char str[18] = { 0 };
for (int i = 1; i < 3; i++) {
sprintf(str, "player_idle_%d.png", i);
SpriteFrame* frame = spriteFrameCache->getSpriteFrameByName(str);
animFrames.pushBack(frame);
}
Animation* idleAnim = Animation::createWithSpriteFrames(animFrames, 0.8f);
self->idleAction = self->runAction(RepeatForever::create(Animate::create(idleAnim)));
self->idleAction->setTag(0);
When I run the code, it works fine and the animation loops correctly.
In my void update() method, I am trying to pause/play the action/animation based of weather the player is moving or idle.
I do this with the following code:
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
this->runAction(idleAction);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
When I run this code now, my character falls, and as soon as he hits the gound, I get an error at this->runAction(idleAction); saying:
Access violation reading location 0xDDDDDDE5
I believe this is caused due to this->stopActionByTag(0) deleting the action pointer. I've tried to clone the action to avoid this but have had no success.
I know this is a bit late and you might already have solved this but here goes...
Your problem is that you cannot use one instance of Action (idleAction) multiple times. So, once you have run it and removed it, it is released and cannot be used. So, you have 2 options now,
Either create a new idleAction Action every time before running the action.
Or, have an idleAction retained and don't run it ever. Instead, create a clone of this idleAction and run a new clone each time. i.e.
idleAction->retain();
const bool isIdleActionRunning = this->getNumberOfRunningActionsByTag(0) > 0 ? true : false;
const bool isMoving = !vel.isZero();
if (!isMoving && !isIdleActionRunning) {
// Player is idle AND animation is not running
// Run animation
Action idleActionClone = idleAction->clone();
this->runAction(idleActionClone);
} else if (isMoving && isIdleActionRunning) {
// Player is moving but animation is running
// Pause animation
this->stopActionByTag(0);
}
Solution: call retain() to keep your action.
It's a matter of memory management of cocos2d-x.
In create() function of your RepeatForever class (derived from Ref), the reference count is set to 1 and there is a line of code ret->autorelease() before returning the object, which means this object will be released automatically at the end of this frame.
You called runAction() function the same frame you created it, the action is retained by ActionManager, it's reference count set to 2, and then 1 at the end of the frame (autorelease).
After your stopping it, it's released by ActionManager, reference count set to 0 and it's deleted. After this you use your action, it will be an access violation method.
*Edit: don't forget to release the action manually when PlayerSprite is deleted, or it's a leak.
When you stop action it's being recycled from memory. In order to play action once more, you have to recreate it. So you may just make a creator function, which returns your animation. The downside is you're recreating animation each time and it'll also play from the beginning (technically you can rewind it).
But I've developed a simpler technique to pause/resume animations:
Let's say you have an action:
action = MoveBy::create(5.0f, Vec2(50, 100));
Now, you can embed this action into Speed action like this:
action = Speed::create(MoveBy::create(5.0f, Vec2(50, 100)), 1.0f);
1.0f - is speed, so it's normal action rate. Now to pause just call:
action->setSpeed(0.0f);
and to resume:
action->setSpeed(1.0f);
you can also use different speed if you need it for some reason or another ;)
Please help me, i am trying to remove b2Body from world but getting assertion error as
"Assertion failed: (m_world->IsLocked() == false)" in the following code:
-(void)beginContact:(b2Contact *)contact{
for (int i=0; i<10; i++) {
b2Body *bodyA=contact->GetFixtureA()->GetBody();
b2Body *bodyB=contact->GetFixtureB()->GetBody();
if((bodyA&&bodyA==monsterBody[i])||(bodyB&&bodyB==monsterBody[i]))
{ [self removeChild:(CCSprite*)monsterBody[i]->GetUserData() cleanup:YES];
NSLog(#"%d",_world->IsLocked());
_world->DestroyBody(monsterBody[i]);
break;}
}}
Contact listener callback methods are executed during (within) a world step time. So deleting a body at this time will cause Assertion failed error.
What you can do is set a bool like isOkToDelete in the body's user data. Then inside the beginContact() callback just update the bool to yes. And then you can do delete outside the step method like even inside the update() tick after cocos2d renders the sprite of the body, or you can insert the body into an array and delete later etc.
I have one layer called alayer, and there is a button called abutton, when click the button, another layer called blayer will show in alayer, not replaceScene, please look at the following code,
alayer.m
-(void)abuttonclicked:(id)sender
{
blayer *blayer = [blayer node];
blayer.position = ccp(1,1);
[self addChild:blayer];
}
blayer.m has a button called bbutton and string value called bstring, I want to click the b button, it will close blayer (remove blayer from alayer), and pass the string value bstring to alayer, please look at following code,
-(void)bbuttonclicked:(id)sender
{
// how can do here to close its self(remove its self from alayer), and pass the bstring to alayer?
}
thanks.
ps. I can use NSUserDefault to pass the string value, but I think it's a bad way to do this, is there another way to pass value?
maybe to pass the string you could declare and implement a property in ALayer.h/.m
#property(nonatomic,copy) NSString *stringFromLayerB;
to remove bLayer when bButtonClicked :
-(void)bbuttonclicked:(id)sender
{
ALayer *lay = (ALayer*) self.parent;
lay.stringFromLayerB = #"Whatever you want to set";
[self removeFromParentAndCleanup:YES];
}
There are other ways to do this. You could implement a callback mechanism , use notifications, some kind of delegate protocol binding BLayer and ALayer. All depends what your real (unsaid) requirements are.
Considering your scenario, I think its better to use NSNotificationCenter.
You can post notification from blayer when the button is tapped and add an observer in alayer to respond exactly how to want.
Add [[NSNotificationCenter defaultCenter] addObserver:self selector:#selector(receivedNotification:) name:#"BlayerNotification" object:nil]; in alayer's init and [[NSNotificationCenter defaultCenter] removeObserver:self]; in its dealloc.
Its selector like:
- (void)receivedNotification:(NSNotification *)notification {
NSString *string = (NSString *)notification.object;
NSLog (#"String received %#", string);
}
Now you can post notification from blayer when hte button is clicked:
-(void)bbuttonclicked:(id)sender {
[[NSNotificationCenter defaultCenter] postNotificationName:#"BlayerNotification" object:self];
[self removeFromParentAndCleanup:YES];
}
I'm threeaing some tasks like this :
RootViewController
- (void)viewDidLoad {
[NSThread detachNewThreadSelector:#selector(findSomething) toTarget:self withObject:nil];
}
- (void) findSomething {
NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init];
doMoreThings
[pool release];
}
- (void) doMoreThings {
doMoreMoreMoreThings on different objects
}
- (void) foundSomething:(NSFoundThing*)foundObj {
do your stuff
}
oneObject
- (void) doMoreMoreMoreThings {
do things
[self performSelectorOnMainThread:#selector(foundSomething:) withObject:thingFound waitUntilDone:NO];
}
gives
-[KMLParser foundSomething:]: unrecognized selector sent to instance 0x5888080
What is the problem ?
The threading is irrelevant. Some of the code you're not showing us is making it so you are sending the foundSomething: selector to an object that doesn't handle that message. Route the message to an object that does handle it, and your problem will go away.
See also "Unrecognized selector sent to instance".
I'm studying Strougo/Wenderlich tutorial (space Viking project). I got troubles with chapter 4.
In RadarDish.m:
-(void)initAnimations
{
[self setTransmittingAnim: [self loadPlistForAnimationWithName:#"transmittingAnim" andClassName:NSStringFromClass([self class])]];
}
-(void)changeState:(CharacterStates)newState {
[self stopAllActions];
id action = nil;
[self setCharacterState:newState];
switch (newState) {
.
.
case kStateIdle:
action = [CCAnimate actionWithAnimation:transmittingAnim
restoreOriginalFrame:NO];
break; }
if (action != nil) {
[self runAction:action];
}
}
-(id)init
{
self=[super init];
if (self!=nil) {
.
.
[self initAnimations];
.
.
}
return self;
}
Exact the same code as in the tutorial. Failure:
*** Assertion failure in -[CCAnimate initWithAnimation:], /Users/macowner/Documents/examples/SpaceViking/SpaceViking/libs/cocos2d/CCActionInterval.
Using debugger with breakpoints, i noticed that value of transmittingAnim = nil.
So, if i put line with
[self setTransmittingAnim:
[self loadPlistForAnimationWithName:#"transmittingAnim" andClassName:NSStringFromClass([self class])]];
into case of
-(void)changeState then animation works correctly.
Why [self initAnimations] from (id)init is not called?
Im using cocos2d v.2 templates.
Great thanks in advance.
I had problems because I have been building project using cocos 2d v.2.0, while tutorial is based on cocos 2d templates v.1.x.x If you are going to follow the book "Learning Cocos2D", I strongly recommend you loading cocos2d-iphone version 1.0.1. Here is the link download cocos2d 1.x.x branch
if you still want to use latest cocos2d templates, I can give you some advice:
Follow the instructions in this link cocos2d v2.0 migration guide
You are going to have a lot of deprecations and changes to fix, so use this link to understand how to fix those deprecations and changes.
Now few words about the solution of the problem I mentioned here. In each of the GameObjects, EnemyObjects, and PowerUps, I added a method to override initWithFrameName.
-(id) initWithSpriteFrameName:(NSString*)frameName{
if ((self=[super init])) {
if ((self = [super initWithSpriteFrameName:frameName])) {
CCLOG(#"### RadarDish initialized");
[self initAnimations]; // 1
characterHealth = 100.0f; // 2
gameObjectType = kEnemyTypeRadarDish; // 3
[self changeState:kStateSpawning]; // 4
}
}
return self;
}
This allows the GameObject and GameCharacter init methods to run before the CCSprite's initWithSpriteFrameName method to run.
The Viking GameObject had to have a slightly different solution because it is initialized with initWithSpriteFrame rather than initWithSpriteFrameName. However, the override implementation is basically the same as the example of RadarDish above.