CCRepeatForever giving me EXC_BAD ACCESS - ios

SO i'm creating a object called Enemy from the main game class
the enemy object is calling ccrepeatforever on a ccmoveto
i retained the move and repeat but still getting crashes
here is some code
for (Enemy *iceLord in self.children) {
if ([iceLord isKindOfClass:[Enemy class]] && iceLord.isIceLord==YES) { <-----this is where EXC_BAD_ACCESS is showing up
//DO Stuff
}
}
//inside of the enemyClass i have this method i think this is causing the crash but i can not see why
-(void)startWiggle{
int randomX = (arc4random()%30)+15;
int randomY = (arc4random()%40)+15;
int dieroll = arc4random()%2+1;
if (dieroll==1) {
randomY=randomY* -1;
}
if (self.position.y < winSize.height && self.position.y >0) {
id move = [CCMoveBy actionWithDuration:1 position:CGPointMake(-randomX, randomY)];
id forever = [CCRepeatForever actionWithAction:move];
[self runAction:forever];
[move retain];
[forever retain];
}
}

Related

Sprite not keeping track of count

The following code I'm using is to end the game when I my number of balls in the scene reaches zero. Now I'm not getting any error when I put the code in but it doesn't do any thing as of counting the balls and reacting to that count. I am fairly new to sprite kit so I understand that this is a problem that comes so easy to others but this is the first time of worked with reacting to the count of a certain sprite in a scene so please help.
SKSpriteNode *ball;
int numberOfBalls = 3;
#implementation EasyScene
-(void)didBeginContact:(SKPhysicsContact *)contact {
if ([self isGameWon]) {
EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
[self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
}
}
-(BOOL)isGameWon {
int numberOfBalls = 3;
for (SKNode* node in self.children) {
if ([node.name isEqual: ball]) {
numberOfBalls = 0;
}
}
return numberOfBalls =0;
}
- (void) addBalls:(CGSize)size {
for (int i = 0; i < 3; i++) {
//create brick sprite from image
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball-image"];
ball.name = #"ball";
//resize balls
ball.size = CGSizeMake(self.size.width/5.5, self.size.width/5.5);
//position and space out balls
int xPos = size.width/3 * (i+.5);
int yPos = self.size.height - (self.size.width/7);
ball.position = CGPointMake(xPos, yPos);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = bottomEdgeCategory;
[self addChild:ball];
}
}
In the following code I got rid of the Global Variable and tested all nodes for the specified name of #"ball" and return YES if none were found:
static const uint32_t ballCategory = 1; //00000000000000000000000000000001
static const uint32_t edgeCategory = 2; //00000000000000000000000000000010
static const uint32_t bottomEdgeCategory = 4; //00000000000000000000000000000100
SKSpriteNode *ball;
#implementation EasyScene
-(void)didBeginContact:(SKPhysicsContact *)contact {
//create placeholder for the "non ball" object
SKPhysicsBody *notTheBall;
SKPhysicsBody *theBall;
if (contact.bodyA.categoryBitMask < contact.bodyB.categoryBitMask) {
notTheBall = contact.bodyB;
theBall = contact.bodyA;
} else {
notTheBall = contact.bodyA;
theBall = contact.bodyB;
}
if (notTheBall.categoryBitMask == bottomEdgeCategory) {
NSLog(#"hit bottom edge");
// SKAction *playSFX = [SKAction playSoundFileNamed:#"gameover.mp3" waitForCompletion:NO];
// [self runAction:playSFX];
// EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
// [self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
// [GameState sharedInstance].score = 0;
// [gameMusic pause];
[theBall.node removeFromParent];
}
if ([self isGameWon]) {
EasyEndGameScene *end = [EasyEndGameScene sceneWithSize:self.size];
[self.view presentScene:end transition:[SKTransition doorsCloseHorizontalWithDuration:1]];
}
}
-(BOOL)isGameWon {
unsigned count = 0;
for (SKNode* node in self.children)
if ([node.name isEqual:ball])
count++;
return count == 0;
}
- (void) addBalls:(CGSize)size {
for (int i = 0; i < 3; i++) {
//create brick sprite from image
SKSpriteNode *ball = [SKSpriteNode spriteNodeWithImageNamed:#"ball-image"];
ball.name = #"ball";
//resize balls
ball.size = CGSizeMake(self.size.width/5.5, self.size.width/5.5);
//position and space out balls
int xPos = size.width/3 * (i+.5);
int yPos = self.size.height - (self.size.width/7);
ball.position = CGPointMake(xPos, yPos);
ball.physicsBody = [SKPhysicsBody bodyWithCircleOfRadius:ball.size.width/2];
ball.physicsBody.categoryBitMask = ballCategory;
ball.physicsBody.contactTestBitMask = bottomEdgeCategory;
[self addChild:ball];
}
}
Remove the ball global variable, as it's not needed.
Test all nodes for the specified name of #"ball" and return YES if none were found:
-(BOOL)isGameWon {
for (SKNode* node in self.children)
if ([node.name isEqualToString:#"ball"])
return NO;
return YES;
}
I think it should be
return numberOfBalls == 0
in your isgameWon method
you forget an extra =

How to make a background continuously scroll vertically with spritekit

I have my background set and it is scrolling horizontally, here is my code:
-(void)initalizingScrollingBackground
{
for (int i = 0; i < 2; i++)
{
SKSpriteNode *bg = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
bottomScrollerHeight = bg.size.height;
bg.position = CGPointMake(i * bg.size.width, 0);
bg.anchorPoint = CGPointZero;
bg.name = #"background";
[self addChild:bg];
}
}
and also this code:
- (void)moveBottomScroller
{
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
CGPoint bgVelocity = CGPointMake(-BG_VELOCITY, 0);
CGPoint amtToMove = CGPointMultiplyScalar(bgVelocity,_dt);
bg.position = CGPointAdd(bg.position, amtToMove);
//Checks if bg node is completely scrolled off the screen, if yes then put it at the end of the other node
if (bg.position.x <= -bg.size.width)
{
bg.position = CGPointMake(bg.position.x + bg.size.width*2,
bg.position.y);
}
[bg removeFromParent];
[self addChild:bg]; //Ordering is not possible. so this is a hack
}];
}
The second part makes the background scroll. Without it, the background is still.
Also, without the movebottomscroller, my sprite appears on top of the background. With the movebottomscroller, he appears behind the scrolling background. Is there any command to bring him to the front, above any other backgrounds?
Thank you!
Try the approach below, hope that works for you.
#interface GameScene()
#property (nonatomic) NSTimeInterval lastTimeSceneRefreshed;
#end
#implementation GameScene
- (instancetype)initWithSize:(CGSize)size {
if (self = [super initWithSize:size]) {
[self buildBackground];
[self startScrolling];
}
return self;
}
// This method will add 3 background nodes
- (void)buildBackground {
float centerX = CGRectGetMidX(self.frame);
SKSpriteNode *firstBackgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
firstBackgroundNode.name = #"background";
firstBackgroundNode.position = CGPointMake(centerX,
firstBackgroundNode.size.height*firstBackgroundNode.anchorPoint.y);
[self addChild:firstBackgroundNode];
float previousYPosition = firstBackgroundNode.position.y;
for (int i = 0; i < 2; i++) {
SKSpriteNode *backgroundNode = [SKSpriteNode spriteNodeWithImageNamed:#"background"];
backgroundNode.position = CGPointMake(centerX,
previousYPosition + backgroundNode.frame.size.height);
previousYPosition = backgroundNode.position.y;
backgroundNode.name = #"background";
[self addChild:backgroundNode];
}
}
- (void)update:(CFTimeInterval)currentTime {
// Updating background nodes
// We don't want to update backgrounds each frame (60 times per second)
// Once per second is enough. This will reduce CPU usage
if (currentTime - self.lastTimeSceneRefreshed > 1) {
[self backgroundNodesRepositioning];
self.lastTimeSceneRefreshed = currentTime;
}
}
- (void)backgroundNodesRepositioning {
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode *backgroundNode = (SKSpriteNode *)node;
if (backgroundNode.position.y + backgroundNode.size.height < 0) {
// The node is out of screen, move it up
backgroundNode.position = CGPointMake(backgroundNode.position.x, backgroundNode.position.y + backgroundNode.size.height * 3);
}
}];
}
- (void)startScrolling {
SKAction *moveAction = [SKAction moveByX:0 y:-200 duration:1];
[self enumerateChildNodesWithName:#"background" usingBlock: ^(SKNode *node, BOOL *stop)
{
[node runAction:[SKAction repeatActionForever:moveAction] withKey:#"movement"];
}];
}
I don't fully understand your approach here, but I can answer your question so I'll just go ahead and do that - I hope I haven't misunderstood.
SKNode has a method insertChild:atIndex: that would allow you to put the background nodes at the back, or put the sprite at the front.
SKNode Class Reference

SpriteKit - FPS dropping after changing texture

I am trying to change a SKSpriteNode's texture while it is animated , but I faced with FPS dropping ! it goes down from 60 to 30 ! on both device and simulator ! the node is a parallel scrolling background , and here is the codes :
- (void)createBricksEdge {
brickEdges = [[SKSpriteNode alloc]initWithImageNamed:#"edge.png"];
brickEdges.position = CGPointMake(CGRectGetMidX(self.frame), CGRectGetMidY(self.frame));;
brickEdges.name = #"edge";
[self addChild:brickEdges];
bEdge1 = [[SKSpriteNode alloc]initWithImageNamed:#"edge.png"];
bEdge1.position = CGPointMake(brickEdges.position.x,brickEdges.position.y+(brickEdges.size.height));
bEdge1.name = #"edge";
[self addChild:bEdge1];
}
moving background :
- (void)moveBg
{
[self enumerateChildNodesWithName:#"edge" usingBlock: ^(SKNode *node, BOOL *stop)
{
SKSpriteNode * bg = (SKSpriteNode *) node;
if (isSpeedBonus == YES) {
SPEED = 200;
} else {
switch (speedCriteria) {
case 0:
SPEED = 7.0;
break;
case 1:
SPEED = 11;
NSLog(#"FPS drops");
[bg runAction:[SKAction setTexture:[SKTexture textureWithImage:[UIImage imageNamed:#"edgeSpeed.png"]]]];
//I also tried : [bg setTexture:textre];
break;
.
.
.
.
.
}
}
bg.position = CGPointMake(bg.position.x , bg.position.y - SPEED);
if (bg.position.y <= -bg.size.width)
{ bg.position = CGPointMake(bg.position.x ,bg.position.y + bg.size.height*2); }} ];
}
Updating frames :
- (void)speedOMeter
{
int i = (int)[scoreLabel.text integerValue];
if (i <= 1000) {
speedCriteria = 0;
} else if (i <= 1500) {
speedCriteria = 1;
} else if (i <=2000) {
speedCriteria = 2;
} else if (i <= 2500) {
.
.
.
.
}
- (void)update:(CFTimeInterval)currentTime {
if (_lastUpdateTime)
{
_dt = currentTime - _lastUpdateTime;
}
else
{
_dt = 0;
}
_lastUpdateTime = currentTime;
[self moveBg]; [self speedOMeter];
}
You're bypassing the Sprite Kit caching mechanisms by creating the image first as a UIImage. This will load the image from disk, possibly every time:
[bg runAction:[SKAction setTexture:[SKTexture textureWithImage:[UIImage imageNamed:#"edgeSpeed.png"]]]];
Besides that you're overdoing it with the action. Try again with the simplified version that'll give you the same result by simply assigning the texture:
bg.texture = [SKTexture textureWithImageNamed:#"edgeSpeed.png"];

NSArray removeObject removes all objects in array

My project creates a bomb, an explosion, then checks for collisions in the explosions and finally delete the bombs that didn't get hit in a collision. This is explained in more detail here. The following code does this.
-(void)placeBomb
{
NSLog(#"Bomb placed");
_circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
CGPoint circle0position = ccp(_cat.position.x , _cat.position.y);
CGPoint c0TileCoordt = [self tileCoordForPosition:circle0position];
CGPoint c0TileCoord = [self positionForTileCoord:c0TileCoordt];
_circle.position = c0TileCoord;
[self addChild:_circle];
id fade = [CCScaleTo actionWithDuration:3.5 scale:0];
[_circle runAction:fade];
double delayInSeconds = 3.0;
dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (int64_t)(delayInSeconds * NSEC_PER_SEC));
dispatch_after(popTime, dispatch_get_main_queue(), ^(void){
[self explosionFromPoint:c0TileCoordt withSprite:_circle];
});
}
- (BOOL)isLocationBombable:(CGPoint)tileCoord;
{
if ([self isValidTileCoord:tileCoord] && ![self isWallAtTileCoord:tileCoord])
{
return YES;
}
else
{
return NO;
}
}
-(void)explosionFromPoint:(CGPoint)explosionPoint withSprite:(CCSprite*)sprite;
{
//int
explosionLenght += 1;
if (explosionLenght >= 7) //Just for testing purposes, don't have a way to increase it naturally.
{
explosionLenght = 1;
}
BOOL topB = YES;
BOOL leftB = YES;
BOOL bottomB = YES;
BOOL rightB = YES;
int bombX = (explosionPoint.x + 1);
int bombY = (explosionPoint.y + 1);
int bombNegX = (explosionPoint.x - 1);
int bombNegY = (explosionPoint.y - 1);
CGPoint top = ccp(explosionPoint.x, bombY);
CGPoint left = ccp(bombNegX, explosionPoint.y);
CGPoint bottom = ccp(explosionPoint.x, bombNegY);
CGPoint right = ccp(bombX, explosionPoint.y);
if (![self isLocationBombable:top])
{topB = NO;}
if (![self isLocationBombable:left])
{leftB = NO;}
if (![self isLocationBombable:bottom])
{bottomB = NO;}
if (![self isLocationBombable:right])
{rightB = NO;}
for (int i = 0; i <= explosionLenght; i++) {
int bombX = (explosionPoint.x + i);
int bombY = (explosionPoint.y + i);
int bombNegX = (explosionPoint.x - i);
int bombNegY = (explosionPoint.y - i);
CGPoint top = ccp(explosionPoint.x, bombY);
CGPoint left = ccp(bombNegX, explosionPoint.y);
CGPoint bottom = ccp(explosionPoint.x, bombNegY);
CGPoint right = ccp(bombX, explosionPoint.y);
CCSprite *circleTop = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleLeft = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleBottom = [[CCSprite alloc]initWithFile:#"Circle.png"];
CCSprite *circleRight = [[CCSprite alloc]initWithFile:#"Circle.png"];
int scaleTime = 5;
if ([self isLocationBombable:top] && topB == YES)
{
circleTop.position = [self positionForTileCoord:top];
[self addChild:circleTop];
id fadeTop = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleTop.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleTop runAction:fadeTop];
}
if ([self isLocationBombable:left] && leftB == YES)
{
circleLeft.position = [self positionForTileCoord:left];
[self addChild:circleLeft];
id fadeLeft = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleLeft.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleLeft runAction:fadeLeft];
}
if ([self isLocationBombable:bottom] && bottomB == YES)
{
circleBottom.position = [self positionForTileCoord:bottom];
[self addChild:circleBottom];
id fadeBottom = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleBottom.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleBottom runAction:fadeBottom];
}
if ([self isLocationBombable:right] && rightB == YES)
{
circleRight.position = [self positionForTileCoord:right];
[self addChild:circleRight];
id fadeRight = [CCSequence actionOne:[CCMoveTo actionWithDuration:1 position:circleRight.position] two:[CCScaleTo actionWithDuration:scaleTime scale:0]];
[circleRight runAction:fadeRight];
}
}
[currentBombs addObject:sprite];
int a = [currentBombs count];
NSLog(#"cBCount: %i",a);
NSLog(#"Explosion done, call checkdamage");
[self schedule:#selector(checkDamageForBomb)];
[self performSelector:#selector(removeSprite:) withObject:sprite afterDelay:5];
}
-(void)removeSprite:(CCSprite *)sprite{
int aa = [currentBombs count];
NSLog(#"removeSprite startcall cbc: %i",aa);
if([currentBombs containsObject:sprite])
{
NSLog(#"Found sprite in array, deleted!");
[currentBombs removeObject:sprite];
int a = [currentBombs count];
NSLog(#"containObject cbc: %i",a);
}
else {
NSLog(#"Didn't find the object in array, didn't delete!");
int a = [currentBombs count];
NSLog(#"elseCO cbc: %i",a);
}
if (currentBombs.count == 0)
{
[self stopCheckDamage];
}
}
-(void)stopCheckDamage{
NSLog(#"StopCheckDamage");
[self unschedule:#selector(checkDamageForBomb)];
}
-(void)checkDamageForBomb{
for (CCSprite* bomb in currentBombs)
{
CGPoint bombPos = [self tileCoordForPosition:bomb.position];
for (int i = 0; i <= explosionLenght; i++) {
CGPoint playerPos = [self tileCoordForPosition:_cat.position];
int bombX = (bombPos.x + i);
int bombY = (bombPos.y + i);
int bombNegX = (bombPos.x - i);
int bombNegY = (bombPos.y - i);
CGPoint centre = bombPos;
CGPoint top = ccp(centre.x, bombY);
CGPoint left = ccp(bombNegX, centre.y);
CGPoint bottom = ccp(centre.x, bombNegY);
CGPoint right = ccp(bombX, centre.y);
//pastebin.com/biuQBfnv
if (CGPointEqualToPoint(top, playerPos) || CGPointEqualToPoint(left, playerPos) || CGPointEqualToPoint(bottom, playerPos) || CGPointEqualToPoint(right, playerPos))
{
playerHits += 1;
NSLog(#"Player hit %i",playerHits);
[currentBombs removeObject:bomb];
break;
}
}
}
}
My problem is with the -(void)removeSprite:(CCSprite *)sprite{method. This is supposed to delete only the one it got called with, but instead it kills them all, as you can see in this log.
15:14:02.499 Tile[1549:c07] Bomb placed
15:14:03.816 Tile[1549:c07] Bomb placed
15:14:05.501 Tile[1549:c07] cBCount: 1
15:14:05.501 Tile[1549:c07] Explosion done, call checkdamage
15:14:06.818 Tile[1549:c07] cBCount: 2
15:14:06.819 Tile[1549:c07] Explosion done, call checkdamage
15:14:06.819 Tile[1549:c07] CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: 0.00 to 0.00
15:14:10.503 Tile[1549:c07] removeSprite startcall cbc: 2 // has 2
15:14:10.503 Tile[1549:c07] Found sprite in array, deleted! //Line above and under
15:14:10.504 Tile[1549:c07] containObject cbc: 0 //Deleted 2, supposed to kill 1
15:14:10.505 Tile[1549:c07] StopCheckDamage
15:14:11.820 Tile[1549:c07] removeSprite startcall cbc: 0
15:14:11.820 Tile[1549:c07] Didn't find the object in array, didn't delete!
15:14:11.821 Tile[1549:c07] elseCO cbc: 0
15:14:11.821 Tile[1549:c07] StopCheckDamage
If you look at the log location in the code above, you will see that it deletes two instead of the one I wanted to. How can I prevent this behaviour or customise it to only kill the proper sprite?
Edit: To clarify
I thought that as I use the spritein the -(void)explosionFromPoint:(CGPoint)explosionPoint withSprite:(CCSprite*)sprite; this would somehow just give an unique ID to the object and know which one I was talking about. I'm new to coding.
You have the same sprite in the array twice. Both entries are removed. If you're going to use removeObject you need to create multiple sprite objects. Otherwise you need to use removeObjectAtIndex.
The problem as #HotLicks mentioned is you push the same instance in the array.
You should use a local CCSprite instance so your scheduled calls will use different instances. The reason for adding the same instance twice is because placeBomb is called twice before anything happens and in the second call you override the first instance you created. Since _circle is a pointer by the time both scheduled tasks will be called _circle will point to the same instance in both.
So change :
_circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
To :
CCSprite *circle = [[CCSprite alloc]initWithFile:#"Circle.png"];
and update the rest of the method with circle and not _circle.
There is something that is going wrong in your code. Try checking something like that when you remove object from array do this:
if([currentBombs count]>1 )
[currentBombs removeObjectAtIndex:1];
If your code works fine. that is only one object removed from your array. Then I suggest you to check your removeSprite method print sprite object to check what's going wrong.
I think you can use tag values if you using same sprite objects.
You probably added the same (indiviudal) sprite twice?
Instead of logging a variable that has the count, you can log the object itself. It will print the value of its description. That will print out the class and address for all NSObject subclasses per default. Actually NSMutableArray (and similar NS... classes) print quite well.
NSLog ("%#",myObj);
Doing so you probably see more clearly what really happens.
Don't use class variable while creating bombs and try....
CCSprite * _circle = [[CCSprite alloc]initWithFile:#"Circle.png"];

Delegate not working between HudLayer and GameLayer

I have created a HudLayer class which defines a protocol for when button presses happen.
-(id) init
{
if( (self=[super initWithColor:ccc4(255, 255, 0, 128)])) {
self.position=ccp(0,0);
self.contentSize=CGSizeMake(480, 40);
scoreLabel=[CCLabelTTF labelWithString:#"0" fontName:#"Marker Felt" fontSize:24];
scoreLabel.position=ccp(62,20);
scoreLabel.anchorPoint=ccp(0,0.5);
[self addChild:scoreLabel];
CCLabelTTF *textLabel=[CCLabelTTF labelWithString:#"Score:" fontName:#"Marker Felt" fontSize:24];
textLabel.position=ccp(5,20);
textLabel.anchorPoint=ccp(0,0.5);
[self addChild:textLabel];
CCMenuItemImage * item = [CCMenuItemImage itemWithNormalImage:#"fire.png" selectedImage:#"fire.png" target:self selector:#selector(projectileButtonTapped:)];
CCMenu *menu = [CCMenu menuWithItems:item, nil];
menu.position = ccp(150, 20);
[self addChild:menu];
}
return self;
}
- (void)projectileButtonTapped:(id)sender
{
NSLog(#"projectileButtonTapped HudLayer");
[self.delegate respondsToSelector:#selector(projectileButtonTapped:)];
}
Then in my HelloWorldLayer class I implement the protocol and set myself as the delegate.
+(CCScene *) scene
{
// 'scene' is an autorelease object.
CCScene *scene = [CCScene node];
// 'layer' is an autorelease object.
HelloWorldLayer *layer = [HelloWorldLayer node];
// add layer as a child to scene
[scene addChild: layer];
//add another layer to the scene
// HudLayer *anotherLayer = [HudLayer node];
// anotherLayer.delegate = self;
// [scene addChild: anotherLayer];
// layer.hud=anotherLayer;
// return the scene
return scene;
}
-(id) init
{
if( (self=[super init]) ) {
_tileMap = [CCTMXTiledMap tiledMapWithTMXFile:#"GridWars#medium.tmx"];
_background = [_tileMap layerNamed:#"Background"];
_foreground = [_tileMap layerNamed:#"Foreground"];
_meta = [_tileMap layerNamed:#"Meta"];
_meta.visible = NO;
CCTMXObjectGroup *objectGroup = [_tileMap objectGroupNamed:#"Objects"];
NSAssert(objectGroup != nil, #"tile map has no objects object layer");
NSDictionary *spawnPoint = [objectGroup objectNamed:#"SpawnPoint"];
int x = [spawnPoint[#"x"] integerValue];
int y = [spawnPoint[#"y"] integerValue];
_wizardHero = [[WizardHero alloc]init];
_redEnemy = [[RedEnemy alloc]init];
_wizardHero.position = ccp(x,y);
[self addChild:_wizardHero];
[self setViewPointCenter:_wizardHero.position];
[self addChild:_tileMap z:-1];
self.touchEnabled = YES;
self.enemies = [[NSMutableArray alloc] init];
self.projectiles = [[NSMutableArray alloc] init];
[self schedule:#selector(testCollisions:)];
for (spawnPoint in [objectGroup objects]) {
if ([[spawnPoint valueForKey:#"Enemy"] intValue] == 1){
x = [[spawnPoint valueForKey:#"x"] intValue];
y = [[spawnPoint valueForKey:#"y"] intValue];
[self addEnemyAtX:x y:y];
}
}
_hud = [HudLayer node];
_hud.delegate = self;
[self addChild:_hud];
}
return self;
}
- (void)projectileButtonTapped:(id)sender
{
// Find where the touch is
// CGPoint touchLocation = [touch locationInView: [touch view]];
// touchLocation = [[CCDirector sharedDirector] convertToGL: touchLocation];
// touchLocation = [self convertToNodeSpace:touchLocation];
if (self.wizardHero.selectedTargets.count > 0) {
// Create a projectile and put it at the player's location
CCSprite *projectile = [CCSprite spriteWithFile:#"Projectile.png"];
projectile.position = _wizardHero.position;
[self addChild:projectile];
// Determine where we wish to shoot the projectile to
int realX;
// Are we shooting to the left or right?
CGPoint diff = ccpSub(self.redEnemy.position, self.wizardHero.position);
if (diff.x > 0)
{
realX = (_tileMap.mapSize.width * _tileMap.tileSize.width) +
(projectile.contentSize.width/2);
} else {
realX = -(_tileMap.mapSize.width * _tileMap.tileSize.width) -
(projectile.contentSize.width/2);
}
float ratio = (float) diff.y / (float) diff.x;
int realY = ((realX - projectile.position.x) * ratio) + projectile.position.y;
CGPoint realDest = ccp(realX, realY);
// Determine the length of how far we're shooting
int offRealX = realX - projectile.position.x;
int offRealY = realY - projectile.position.y;
float length = sqrtf((offRealX*offRealX) + (offRealY*offRealY));
float velocity = 480/1; // 480pixels/1sec
float realMoveDuration = length/velocity;
// Move projectile to actual endpoint
id actionMoveDone = [CCCallFuncN actionWithTarget:self
selector:#selector(projectileMoveFinished:)];
[projectile runAction:
[CCSequence actionOne:
[CCMoveTo actionWithDuration: realMoveDuration
position: realDest]
two: actionMoveDone]];
[self.projectiles addObject:projectile];
}
}
The problem is that only the projectileButtonTapped: in the HudLayer is called (ie. the delegate's method never gets called).
P.S.
To save space I left out both .h files. But I assure you they are correct, with the #protocol used in HudLayer and that same protocol name put in <> within the HelloWorldLayer.
I think :
- (void)projectileButtonTapped:(id)sender
{
NSLog(#"projectileButtonTapped HudLayer");
if ([self.delegate respondsToSelector:#selector(projectileButtonTapped:)]) {
[self.delegate performSelector:#selector(projectileButtonTapped:) withObject:sender];
}
}

Resources