0
votes

Have recently been teaching myself objective-c in order to learn Cocos2d.

Started a new project to teach myself the basics with a classic falling gems tetris-like game. The first step I took was to enable the project for ARC using edit > refactor, and selecting the last 4 files in the template.

So far have been able to add a single gem with a dynamically colored sprite through subclassing CCSprite, and move it along the XCoordinates while having it fall from the top. The gems are given a gemPos location – eg 5,3 – in order for me to later implement the matching methods.

Am getting an EXC_BAD_ACCESS warning when a few of the blocks stack up on top of each other after testing out the project for a while. Am confused as to why this is happening if ARC is enabled.

My clumsy code is as follows (have left out .h files):

Default helloworld layer:

-(id) init
    {

        if( (self=[super init]) ) {

            oldGems = [[NSMutableArray alloc]init];

            [self setIsTouchEnabled:YES];

            [self newGem];

            [self scheduleUpdate];

        }
        return self;
    }

    - (void)registerWithTouchDispatcher
    {
        [[[CCDirector sharedDirector] touchDispatcher] addTargetedDelegate:self priority:0 swallowsTouches:YES];
    }

    - (BOOL)ccTouchBegan:(UITouch *)touch withEvent:(UIEvent *)event
    {
        CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
        oldPos = touchLocation;
        return TRUE;
    }

    - (void)ccTouchMoved:(UITouch *)touch withEvent:(UIEvent *)event
    {
        CGPoint touchLocation = [self convertTouchToNodeSpace:touch];
        [self updateXCoord:(CGPoint)touchLocation];
    }

    - (void)updateXCoord:(CGPoint)touchLocation
    {


        CGPoint distance = ccpSub(touchLocation, oldPos);
        float xDistance = distance.x;

        if (abs(xDistance) >= 36) {
            if (touchLocation.x > oldPos.x) {
                [_thisGem setPosition:ccp([_thisGem position].x + 36, [_thisGem position].y)];
                [_thisGem setGemPos:ccp([_thisGem gemPos].x+1,[_thisGem gemPos].y)];
                NSLog(@"The gem position is %@", NSStringFromCGPoint([_thisGem gemPos]));
                oldPos = touchLocation;
            } else {
                [_thisGem setPosition:ccp([_thisGem position].x - 36, [_thisGem position].y)];
                [_thisGem setGemPos:ccp([_thisGem gemPos].x-1,[_thisGem gemPos].y)];
                NSLog(@"The gem position is %@", NSStringFromCGPoint([_thisGem gemPos]));
                oldPos = touchLocation;
            }
        }


    }

    - (void)newGem
    {
        if (_thisGem) {
            PCGem *oldGem = _thisGem;
            NSLog(@"Old gem position at %@", NSStringFromCGPoint([oldGem gemPos]));
            [oldGems addObject:oldGem];
        }

        _thisGem = [[PCGem alloc] initWithGemColor];
        [_thisGem setPosition:ccp(160, 450)];
        [self addChild:_thisGem];
        [_thisGem setGemPos:ccp(4,10)];

        NSLog(@"Gem added with %@ color", [_thisGem gemColorName]);
    }

    - (void)update:(ccTime)dt
    {

        if ([_thisGem gemPos].y > 0)  {

            [self spaceBelowOccupied];

            [_thisGem setPosition:ccp([_thisGem position].x, [_thisGem position].y-1)];
            [_thisGem setGemPos:ccp([_thisGem gemPos].x, floor([_thisGem position].y / 36))];

        } else {
            [self newGem];
        }
    }

    - (void)spaceBelowOccupied
    {    
            for (PCGem *occupiedTile in oldGems) {
                if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
                    NSLog(@"Collision detected!");
                    [self newGem];
                }
            }
    }

And my clumsy PCGem class:

    - (id)initWithGemColor
    {
        if ((self = [super initWithFile:@"gemGS.png"])) {


            NSArray *gemColorList = [NSArray arrayWithObjects:(NSString *)@"blue", (NSString *)@"red", (NSString *)@"green", nil];

            NSInteger randColor = arc4random_uniform(3);

            switch (randColor) {
                case 0:
                {
                    [self setGemColorName:[gemColorList objectAtIndex:0]];

                    ccColor3B gemColorRGB = {0,0,255};
                    [self setColor:gemColorRGB];
                    break;
                }
                case 1:
                {
                    [self setGemColorName:[gemColorList objectAtIndex:1]];

                    ccColor3B gemColorRGB = {255,0,0};
                    [self setColor:gemColorRGB];            
                    break;
                }
                case 2:
                {
                    [self setGemColorName:[gemColorList objectAtIndex:2]];

                    ccColor3B gemColorRGB = {0,255,0};
                    [self setColor:gemColorRGB];
                    break;
                }
            }

        }

        return self;

    }

Which leads me to another question, which I can't seem to find the answer to. In examples of ARC enabled Cocos2d projects, I've seen the node convenience method used, which of course is alloc]init]autorelease]. But I thought you can't use Autorelease with ARC and this would cause a crash? How does this work?

Any ideas to my woes? Would greatly appreciate clarification :)

Cheers,

Adrian

1
Which line actually crashes? - Ben Trengrove
When I click the EXC_BAD_ACCESS notifier thing, it takes me to main.m (is this indicating what lines crashes? Sorry, new.), and points to: int retVal = UIApplicationMain(argc, argv, nil, @"AppController"); Which is in an @autoreleasepool. Although this confuses me as I thought enabling ARC would not necessitate this? - Adrian Norman
Also, through using NSLogs I found that a new gem is added as I'm receiving the "Gem added with %@ color" message at the end of my -(void)newGem method, so I assume it's got something to do with the update method? Perhaps I'm trying to do too much at once at 60 frames a second (fast enumerating through all gems not in play to detect collisions with the currently falling gem - could prob be handled a lot more efficiently)? - Adrian Norman

1 Answers

0
votes

I think it might be caused by editing an array while enumerating through it. In your update function you call spaceBelowOccupied:

- (void)spaceBelowOccupied
{    
        for (PCGem *occupiedTile in oldGems) {
            if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
                NSLog(@"Collision detected!");
                [self newGem];
            }
        }
}

then if newGem gets called while in your for loop and this loop succeeds:

if (_thisGem) {
        PCGem *oldGem = _thisGem;
        NSLog(@"Old gem position at %@", NSStringFromCGPoint([oldGem gemPos]));
        [oldGems addObject:oldGem];
    }

then you are adding an object to an array while enumerating through it. So change your spaceBelowOccupied to this and see if it works:

 - (void)spaceBelowOccupied
{    
        for (PCGem *occupiedTile in [oldGems copy]) {
            if (CGRectIntersectsRect(occupiedTile.boundingBox, _thisGem.boundingBox)) {
                NSLog(@"Collision detected!");
                [self newGem];
            }
        }
}

that way you make a copy of oldGems to enumerate through that will get autoreleased once you are done going through it.