so im trying to make a tower defense game, (Towers shoot bullets at advancing enemies), so the problem is when i try to remove the bullet from the scene after it hit the enemy, it throws an exception, with no error in the debugger.
Here is the code:
-(void)shootWeapon
{
CCSprite * bullet = [CCSprite spriteWithImageNamed:#"snowball.png"];
[theGame addChild:bullet];
[bullet setPosition:mySprite.position];//Todo offset snowball to the right of the tower
[bullet runAction:[CCActionSequence actions:[CCActionMoveTo actionWithDuration:0.3
position:chosenEnemy.mySprite.position],[CCActionCallFunc actionWithTarget:self
selector:#selector(damageEnemy)],[CCActionCallFunc actionWithTarget:self
selector:#selector(removeBullet:)], nil]];
}
-(void)removeBullet:(CCSprite *)bullet
{
[bullet.parent removeChild:bullet cleanup:YES];
}
-(void)damageEnemy
{
[chosenEnemy getDamaged:damage];
}
if anyone has an idea why this is going on, any help would be greatly appreciated.
cheers
The bullet is not being passed, hence the exception on removeBullet: method.
This line is the problem:
[CCActionCallFunc actionWithTarget:self
selector:#selector(removeBullet:)]
Add breakpoint to [bullet.parent removeChild:bullet cleanup:YES]; and po bullet on the debugger and you will probably get nil.
My solution would be to use a block action, for example:
CCAction *blockAction = [CCActionCallBlock actionWithBlock:^{
[bullet removeFromParentAndCleanup:YES];
}];
Related
I am programming a game, and using a SKLabel to view the current score. The problem is that when score changes it doesn't display the change in the screen at the moment, it does after a second more or less. What can I do to see the changes in the moment I use [sklabelscore setTextScore:++self.score]. Can I force render or something similar?
I call the setTextScore when the user touches an enemy, with touchesBegan:withEvent:
setTextScore: implementation is
SKLabelNode* scoreLabel=(SKLabelNode*)[self childNodeWithName:#"scoreLabel"];
scoreLabel.text=[NSString stringWithFormat:#"Score: %d",self.score];
It seems most likely that the ++score you increment is a local variable and not the same as self.score.
You call the method as follows:
[sklabelscore setTextScore:++score]
Which means its signature with code must be:
-(void) setTextScore:(int)theNewScore
{
SKLabelNode* scoreLabel=(SKLabelNode*)[self childNodeWithName:#"scoreLabel"];
scoreLabel.text=[NSString stringWithFormat:#"Score: %d",self.score];
}
So you're passing in theNewScore but instead of using that in the format string you use self.score which may never be updated if the incremented ++score variable is a local variable (ie never assigns its new value to self.score).
Solved... I feel like an idiot :S
The problem was I fade out the enemy when I touched it, and then, after 0.5 seconds, changes the label. I put that out of the block and all working fine.
Changed setTextScore: method because was redundant (thanks #LearnCocos2D)
...
SKAction* fade=[SKAction fadeOutWithDuration:0.5];
[node runAction:fade completion:^{
[node removeFromParent];
self.enemyNumber--;
self.score++;
SKLabelNode* scoreLabel=(SKLabelNode*)[self childNodeWithName:#"scoreLabel"];
scoreLabel.text=[NSString stringWithFormat:#"Score: %d",self.score];
}];
The new form (outside of the block):
...
self.score++;
SKLabelNode* scoreLabel=(SKLabelNode*)[self childNodeWithName:#"scoreLabel"];
scoreLabel.text=[NSString stringWithFormat:#"Score: %d",self.score];
SKAction* fade=[SKAction fadeOutWithDuration:0.5];
[node runAction:fade completion:^{
[node removeFromParent];
}];
Thanks for your help and sorry for asking this stupid question...
[self goToStage:currentStage];
[actor deathAnimation];
I have these two methods. I want to call it one by one in sequence. When one is completed after second one is started.
//this method used in nsobject subclass
-(void)deathAnimation {
//Play death animation
}
//this method in cclayer subclass
-(void)goToStage:(int)stage {
//changing scene
}
IN my tick function at particular event i want to call in seqeuence
I use following code but it not working
[CCSequence actions:[CCCallFunc actionWithTarget:actor selector:#selector(deathAnimation)], [CCDelayTime actionWithDuration:4.0], [CCCallFuncN actionWithTarget:self selector:#selector(goToStage:)], nil];
Now what i do? Please tell me.. Is there something wrong?
You can use CCDelayTime, that is used to give a delay, and CCCallBlock,to pass a method to be invoked, like this:
[self runAction:[CCSequence actions:[CCDelayTime actionWithDuration:1],
[CCCallBlock actionWithBlock:^{[self goToStage:currentStage];}],
[CCDelayTime actionWithDuration:1],
[CCCallBlock actionWithBlock:^{[actor deathAnimation}], nil]];
Define a block in you inteface as e.g.
typedef void(^MyCompletion)(void);
edit you deathAnimation to take a block parameter as
- (void)deathAnimationWithCompletion:(MyCompletion)finish {
//..death animation
//...
//when animation finishes
finish(); // This will call your completion block
}
Call this method as
[self deathAnimationWithCompletion:^{
[self goToStage:2];
}];
You can read up on blocks at Ray Wenderlichs fantastic blog.
Hope it helps!
EDIT DUE TO COMMENT
In cocos2d I think you can also make a sequence like this
id aFuncCall = [CCCallFunc actionWithTarget:self selector:#selector(deathAnimation)];
id antoherFuncCall = [CCCallFunc actionWithTarget:self selector:#selector(goToSecondStange:)];
CCSequence *sequence = [CCSequence actions:aFuncCall,anotherFuncCall, nil];
[self runAction:sequence];
But my cocos2d programming skills are bit outdated so not sure if this works...
You can call
[actor deathAnimation];
at the end of the goToStage function, that way it will execute everything in goToStage before calling deathAnimation.
You could put a delay timer in the goToStage if you require it to wait for a specific amount of time.
I am trying to remove a sprite off of my main game layer after the sprite itself is done animating... To do achieve this, I first attempted to pass a block into the sprite object's CCSequence like this:
#Game.m
// some method
[self.spriteMan zoomAwayWithBlock:{[self destroySpriteMan];}];
[self createNewSpriteMan];
}
-(void)destroySpriteMan {
[self removeChild:self.spriteMan cleanup:YES];
}
#SpriteMan.m
-(void)zoomAwayWithBlock:(void(^)())block {
[self runAction:[CCSequence actions: [CCScaleTo actionWithDuration:2.0f scale:1.0f],
[CCCallFuncN actionWithBlock:block],
nil]];
}
I wondered if somehow the binding of self.spriteMan was getting messed up do to [self createNewSpriteMan] being called prior to the animation completing.... So I stored spriteMan in a tempSpriteMan variable prior to calling it, and attempting to removeChild on tempSpriteMan..... both results in a crash immediately.
I then rewrote this to use selectors and targets:
#game.m
[self.spriteMan zoomAwayWithSelector:#selector(destroySpriteMan:) target:self];
-(void)destroySpriteMan:(SpriteMan *)spriteMan {
[self removeChild:spriteMan cleanup:YES];
}
#SpriteMan.m
-(void)zoomAwayWithSelector:(SEL)sel target:(id)target {
[self runAction:[CCSequence actions: [CCScaleTo actionWithDuration:2.0f scale:1.0f],
[CCCallFuncN actionWithTarget:target selector:sel],
nil]];
}
same result.. crash every time...... What am I doing wrong?
As Aroth pointed out, the answer lies in this topic:
http://cocos2d-iphone.org/forum/topic/6818
It shows a solution to the problem, putting this into the array of actions solved the problem for me:
[CCCallFuncO actionWithTarget:self selector:#selector(removeFromParentAndCleanup:) object:[CCNode node]].
You can try to add category to CCNode with method
- (void) removeFromParentWithCleanupYes
{
[self removeFromParentWithCleanup:YES];
}
then just make sequences like this
id sequence = [CCSequence actions: action1,
action2,
...,
[CCCallFunc actionWithTarget: target selector:#selector(removeFromParentWithCleanupYes)],
nil];
[target runAction: sequence];
it is not very elegant, but it should work.
Or you can create your own simple action, which will remove it's target from parent.
My code has two Bullet-related classes. Bullet and BulletCache. The BulletCache creates a certain number of
I have moved on to just creating a new bullet creating method meant to shoot off the bullets. I used the CCFuncN method but the game is currently throwing NSException errors:
CCAction* action = [CCSequence actions:
[CCAnimate actionWithAnimation:[profile getAnimation:#"attack" index:currentDir]],
[CCCallFuncN actionWithTarget:self selector:#selector(shootBulletFrom:)],
nil];
NSInvalidArgumentException', reason: '-[Player shootBulletFrom:]: unrecognized selector sent to instance 0x703ec70'
edit:
For further help and advice here is the shootBulletFrom method in the BulletCache.
This method is in the BulletCache
-(void) shootBulletFrom:(CGPoint)startPosition velocity:(CGPoint)velocity frameName:(NSString*)frameName
isPlayerBullet:(bool)isPlayerBullet
{
CCArray* bullets = [batch children];
CCNode* node = [bullets objectAtIndex:nextInactiveBullet];
NSAssert([node isKindOfClass:[Bullet class]], #"not a Bullet!");
Bullet* bullet = (Bullet*)node;
[bullet shootBulletAt:startPosition velocity:velocity frameName:frameName
isPlayerBullet:isPlayerBullet];
nextInactiveBullet++;
if (nextInactiveBullet >= [bullets count])
{
nextInactiveBullet = 0;
}
}
I was also recommended to change the [CCCallFuncN] call at the bottom to:
[CCCallFuncN actionWithTarget:self selector:#selector(shootBulletFrom:shotPos velocity:velocity frameName:#"bullet1big.png" isPlayerBullet: YES)],
But then I got the compile Error: Expected ':' before Velocity
You have not mentioned the code for shootBulletFrom, and the error denoted that there is some mistake in the same. May be you have not declared the function in .h file or some other. So if possible mention so.
You can go through this and this links. They are having good examples for bullet firing apps. Hope that helps you.
Bear with me, this one is hard to explain. I hope some hero out there knows what’s going on here. Some history needed;
One of my cocoa objects, “Ball” represents a small graphic. It only makes sense within a view. In some of the Ball’s methods, it asks the view to redraw. Most importantly, it asks the view to redraw whenever the Ball’s position parameter is set. This is achieved in the setter.
Here’s the mouthful, as suggested:
In View.m
- (void)mouseUp:(NSEvent *)theEvent {
if (![runnerPath isEmpty]) {
[walkPath removeAllPoints];
[walkPath appendBezierPath:runnerPath];
[runnerPath removeAllPoints];
[[self held] setStep:0];
[[self held] setPath:walkPath];
[NSTimer scheduledTimerWithTimeInterval:.01 target:[self held] selector:#selector(pace) userInfo:nil repeats:YES];
}
}
In Ball.m
- (void)pace {
CGFloat juice = 10;
BOOL loop = YES;
while (loop) {
if ([self step] == [[self path] elementCount]) {
if ([[self timer] isValid]) {
[[self timer] invalidate];
}
[[self path] removeAllPoints];
// #throw([NSException exceptionWithName:#"test" reason:#"reason" userInfo:nil]);
}
if (loop) {
CGFloat distance;
NSPoint stepPoint;
if ([[self path] elementCount] > 0) {
NSPoint returnPoints[2];
[[self path] elementAtIndex:[self step] associatedPoints:returnPoints];
stepPoint = returnPoints[0];
distance = pixelDistance([self position], stepPoint);
}
if (distance <= juice) {
[self setPosition:stepPoint];
if (distance < juice) {
juice -= distance;
loop = YES;
[self setStep:[self step]+1];
} else {
loop = NO;
}
} else {
NSPoint cutPoint = moveAlongBetween([self position], stepPoint, juice);
[self setPosition:cutPoint];
loop = NO;
}
}
}
}
could you also tell how you handle exceptions? since normally an unrecognized selector will end your program. Maybe you need an exception rather than an unrecognized selector. Try:
#throw([NSException exceptionWithName:#"test" reason:#"reason" userInfo:nil]);
If this would fix it as well, you're doing something after this code which freezes the app.
edit: thanks for the code update.
There's some weird stuff going on here! I'm not going to rewrite the whole thing, so here's some pointers:
first of all: you're looping inside some routine that is called from a timer loop. Is that intended? There is no way to pause execution within that while() loop, so it will happen in a blink anyway. You would need to keep some state information in the class. E.g. adding a loop counter every time pace is called.
second: if you start a timer, it will call your selector with the timer as an argument. So define the function as -(void)pace:(NSTimer*)timer, and use timer, not [self timer] (the latter will not be your timer anyway, if you don't assign it!)
third: you're firing 100 times a second. That is a lot, and presumably higher than the refresh rate of any device you're writing this for. I think 20/sec is enough.
fourth: to be sure, if you change it to -(void)pace:(NSTimer*)timer, don't forget to use #selector(pace:) (i.e. don't forget the :)
fix those things, and if it's still broken, update your question again and put in comment so we will know. Good luck!
Try calling
for (NSView *each in [self views]) {
...
}
I'm assuming that views is an array, so fast enumeration applies to it directly and there is no need to call allObjects.
A couple of other points.
Have you set a Global breakpoint of objc_exception_throw? This will apply to all Xcode projects and is so useful I'm surprised it isn't set by default.
You say you looked at the Console for errors. I take it, then, that you didn't set a breakpoint on the code and step into it to see exactly what is happening when your execution reaches that point? Have a look at the Xcode Debugging Guide