NSMutableArray and batchNode problems - iphone

I'm making a little game, here is some example code of whats going on:
-(id) init
{
self.arrowProjectileArray = [[[NSMutableArray alloc] init] autorelease];
self.batchNode = [CCSpriteBatchNode batchNodeWithTexture:[[CCTextureCache sharedTextureCache] addImage:#"arrow.png"]];
[self addChild:_batchNode z:2];
for (CCSprite *projectile in _arrowProjectileArray) {
[_batchNode removeChild:projectile cleanup:YES];
}
[_arrowProjectileArray removeAllObjects];
self.nextProjectile = nil;
}
}
-(void) callEveryFrame:(ccTime)dt{
for (int i = 0; i < [_arrowProjectileArray count];i++) {
CCSprite *cursprite = [_arrowProjectileArray objectAtIndex:i];
if (cursprite.tag == 1) {
float x = theSpot.x+10;
float y = theSpot.y+10;
cursprite.position = ccp(x, y);
}
}
- (void)ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
[_batchNode addChild:_nextProjectile z:1 tag:1];
[_arrowProjectileArray addObject: _nextProjectile];
[self spriteMoveFinished];
}
-(void) dealloc
{
self.arrowProjectileArray = nil;
self.nextProjectile = nil;
[super dealloc];
}
The only code that I included was code that is relevant to the arrow's projection.
The arrow shoots fine, the problem is every time I shoot the stupid thing, I think it shoots a new arrow, but puts multiple arrows onto of that 1 arrow and makes it look like a fat ugly arrow pixel thing. What am I doing wrong? I'm not too familiar with NSMutableArray, but I'm currently stuck.

In init method, you create a new NSMutableArray instance and assign it to self.arrowProjectileArray, then you traverse the arrowProjectileArray in the following lines using a for loop. If addChild: method does not add anything to arrowProjectileArray, then your code has a logic mistake, because what you do by traversing arrowProjectileArray is traversing an empty array, which means you do nothing in that code.
You should double-check what you intend to do and what your code is doing actually.

I solved my own problem by doing a little bit of research, I also got rid of the batch node.

Related

What is wrong with this attempt to create and populate an NSMutableArray?

In my .h file, I have:
#property (nonatomic) NSMutableArray *cards;
In my .m file, I have, in the initializer:
- (id) init
{
self = [super init];
self.cards = [NSMutableArray alloc];
return self;
}
In a loop that populates a number of items visible and on-screen:
[self.cards addObject:noteView];
And in a touch event handler, I have:
- (void) touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event
{
NSLog(#"In touchesBegan.");
UITouch *touch = [touches anyObject];
UIView *selectedView = nil;
CGPoint touchLocation = [touch locationInView:self.view];
for (UIView *card in _cards)
{
CGRect cardRect = [card frame];
NSLog(#"%f", cardRect.origin.x);
NSLog(#"%f", cardRect.origin.y);
NSLog(#"%f", cardRect.size.height);
NSLog(#"%f", cardRect.size.width);
if (CGRectContainsPoint(cardRect, touchLocation)) {
NSLog(#"Match found.");
selectedView = card;
CGRect selectedFrame = selectedView.frame;
selectedFrame.origin.y = -selectedFrame.size.height;
selectedFrame.size = selectedView.frame.size;
float heightRatio = (float) floor([[UIScreen mainScreen] bounds].size.height + .4) / (float) selectedFrame.size.height;
float widthRatio = (float) floor([[UIScreen mainScreen] bounds].size.width + .4) / (float) selectedFrame.size.width;
float ratio = MIN(heightRatio, widthRatio);
selectedFrame.size.height *= ratio;
selectedFrame.size.width *= ratio;
selectedFrame.origin.x = -selectedFrame.origin.x * ratio;
}
}
}
The output for every touch I've made has been that the unconditional NSLog statement is output, but none of the "Log this float" statements execute. It seems that I have not correctly initialized or not correctly populated NSMutableArray.
I seem to get the same behavior whether I refer to _cards or self.cards.
Thanks for any help,
--EDIT--
I seem to be sticking on something else than originally thought of. I now have self.cards = [[NSMutableArray alloc] init], but identical behavior: I go through a loop and populate a bunch of cards, but when I tap one of them, the touch handler outputs "In touchesBegan." but none of the floats. Given an updated init, why would touchesBegan be acting as if it hadn't seen any cards after a number of cards are visible onscreen? (The other output should give a number of lines of floats whether or not the touch was on target for a particular card.)
You need to init the array!
self.cards = [[NSMutableArray alloc] init];
Or
self.cards = [NSMutableArray new];
They are both equivalents
With alloc the only thing you are doing is reserve space in the memory for that variable.
From Apple Documentation:
alloc
Returns a new instance of the receiving class.
init
Implemented by subclasses to initialize a new object (the receiver) immediately after memory for it has been allocated.
Are you using a storyboard for this? Try to initialize your cards property in viewDidLoad.
As a quick check, try to initialize that property right before the loop that is adding objects to it.

Cocos2d NSMutable Array mutated while enumerated

I've been working on a game, and at a point I didn't have issues removing the enemy game objects (right now they are subclassed from CCSprite and I know that's not the best way)
But I'm not sure what I changed to make it crash when the program attempts to removeChild from _targets after they have been added to targetsToDelete.
I tried moving things around, I just don't know how I am adding or editing the array while its being created... Any help or advice would be great!
And actually if you had any pointers on how best to create game enemies, do you subclass NSObject or CCNode? I heard to divide them into component classes but I had no clue what they meant.
//Projectile Target collision
-(void)update:(ccTime)dt {
for (spygot *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
//Collision Detection Player
CGRect playerRect2 = CGRectMake(
_controlledSprite.position.x - (_controlledSprite.contentSize.width/2),
_controlledSprite.position.y - (_controlledSprite.contentSize.height/2),
_controlledSprite.contentSize.width,
_controlledSprite.contentSize.height);
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (Projectile *projectile in _projectiles)
{
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
BOOL monsterHit = FALSE;
if (CGRectIntersectsRect(projectileRect, targetRect))
{
NSLog(#"hit");
target.mhp = target.mhp - 1;
monsterHit = TRUE;
if (target.mhp <= 0)
{
[targetsToDelete addObject:target];
}
}
for (spygot *target in targetsToDelete)
{
[self removeChild:target cleanup:YES];
[_targets removeObject:target];
}
if (monsterHit)
{
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (Projectile *projectile in projectilesToDelete)
{
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
It looks like the code that you've pasted is all from within a for loop iterating over _targets. How does the variable target get initialized?
Usually when I get this sort of error it's because I have the code in a block or am in some other way on a nebulous thread. How sure are you that this bit of code is not running more than once at the same time?
You could try wrapping it in the following:
dispatch_async(dispatch_get_main_queue(), ^{
// do everything here.
});
As for advice about using CCSprite for your game enemy objects, my advice is fix it when it becomes a problem. Are you seeing issues with it right now? Premature optimization is almost as bad as doing it wrong in the first place. You'll know better at the end of the project how you should have done it earlier. ;)
I guess you know that you cannot remove elements from the array while you iterate over it. That is why you have targetsToDelete array.
But it looks to me that you do remove targets to soon.
Try this:
finish iterating the main loop and finish collecting targets to the targetsToDelete array and only after main loop is done remove the targets.

accessing instance variable in cocos2d scheduled method crashes

fresh to objC and cocos2d :)
i'm following "learn cocos2d game development with iOS5", in chapter4, there is a "DoodleDrop" game.
define some variable in GameScene.h like this
#interface GameScene : CCLayer
{
CCSprite *player;
CGPoint playerVelocity;
CCArray *spiders;
CGSize screenSize;
int dropedSpidersCount;
float duration;
}
+ (CCScene *)scene;
#end
in GameScene.m the init method looks like this
- (id)init
{
if (self = [super init]) {
duration = 4.0;
[self createPlayer];
[self createSpiders]; // spiders were inited here.
[self resetSpiders];
[self schedule:#selector(chooseSpider:) interval:0.7];
}
return self;
}
while in chooseSpider, i cannot access spiders, xcode broke
in other methods, spiders or duration just behave normally, why does this happens?
gist code added
https://gist.github.com/2940466
After inspecting your code, I suggest you to try this fix:
- (void)createSpiders
{
CCSprite *tempSpider = [CCSprite spriteWithFile:#"spider.png"];
CGSize spiderSize = [tempSpider texture].contentSize;
int spiderCount = screenSize.width / spiderSize.width;
spiders = [[CCArray arrayWithCapacity:spiderCount] retain];
for (int i = 0; i < spiderCount; i++) {
CCSprite *spider = [CCSprite spriteWithFile:#"spider.png"];
[self addChild:spider];
[spiders addObject:spider];
}
}
where the only difference is in the line:
spiders = [[CCArray arrayWithCapacity:spiderCount] retain];
Indeed, if you do not retain you spiders object, it will be autoreleased at the next run loop iteration.
OLD ANSWER:
Without seeing more code it is not possible to say exactly what is happening, but it seems that in the interval between creating the spiders and the actual execution of chooseSpiders, your spiders array gets deallocated.
As a quick try, I would suggest adding:
[spiders retain];
before calling
[self schedule:#selector(chooseSpider:) interval:0.7];
and see wether the crash keeps happening.
if you provide more code, it could be possible to help you further.

Removing sprite from layer not removing rect with it (Cocos2d)?

I am making a game in Cocos2d. Everything is okay, so far. I used Ray Wenderlich's tutorial to get collision detection to work. It works, but whenever an 'enemy' spawns where a bullet was deleted (because the bullet that was deleted hit a target, therefore, was deleted), the enemy is automatically deleted, too. I think it's because it doesn't remove the rect that was declared for the sprite. Note, it also can go through more than one enemy, even though the bullet is deleted. Any help is appreciated. Thanks!
EDIT:
I found out what the problem was. I had the shoot method set in a schedule:#selector method, with no set interval. That meant that it would fire bullets 60fps fast. So I was getting TWO bullets with ONE click. They were so close together, that it took me a while to notice it. I won't make that mistake again!!!
Are you using the following code? (from How To Make A Simple iPhone Game with Cocos2D Tutorial)
- (void)update:(ccTime)dt {
NSMutableArray *projectilesToDelete = [[NSMutableArray alloc] init];
for (CCSprite *projectile in _projectiles) {
CGRect projectileRect = CGRectMake(
projectile.position.x - (projectile.contentSize.width/2),
projectile.position.y - (projectile.contentSize.height/2),
projectile.contentSize.width,
projectile.contentSize.height);
NSMutableArray *targetsToDelete = [[NSMutableArray alloc] init];
for (CCSprite *target in _targets) {
CGRect targetRect = CGRectMake(
target.position.x - (target.contentSize.width/2),
target.position.y - (target.contentSize.height/2),
target.contentSize.width,
target.contentSize.height);
if (CGRectIntersectsRect(projectileRect, targetRect)) {
[targetsToDelete addObject:target];
}
}
for (CCSprite *target in targetsToDelete) {
[_targets removeObject:target];
[self removeChild:target cleanup:YES];
}
if (targetsToDelete.count > 0) {
[projectilesToDelete addObject:projectile];
}
[targetsToDelete release];
}
for (CCSprite *projectile in projectilesToDelete) {
[_projectiles removeObject:projectile];
[self removeChild:projectile cleanup:YES];
}
[projectilesToDelete release];
}
whenever an 'enemy' spawns where a bullet was deleted, the enemy is automatically deleted, too.
It sounds that the bullet is removed from the layer, but it is not removed from _projectiles array.
[_projectiles removeObject:projectile];
Are you sure that this code works?
Rect is not a seperate entity from your bullet. Rect is the property associated with the bullet. As soon as your "bullet is deleted" the rect will no longer be valid.
What you should be looking at is your collision checking code.
You probably want to surround your bullet collision check code with a condition like:
if(bullet exists)
{
check for collision
}
Since you haven't posted code I could only post pseudo code here. Maybe if you post your collision checking code I could show you in more detail.

Need help with a memory management problem in my game model

I'm a beginner level programmer trying to make a game app for the iphone and I've encountered a possible issue with the memory management (exc_bad_access) of my program so far. I've searched and read dozens of articles regarding memory management (including apple's docs) but I still can't figure out what exactly is wrong with my codes. So I would really appreciate it if someone can help clear up the mess I made for myself.
//in the .h file
#property(nonatomic,retain) NSMutableArray *fencePoleArray;
#property(nonatomic,retain) NSMutableArray *fencePoleImageArray;
#property(nonatomic,retain) NSMutableArray *fenceImageArray;
//in the .m file
- (void)viewDidLoad {
[super viewDidLoad];
self.gameState = gameStatePaused;
fencePoleArray = [[NSMutableArray alloc] init];
fencePoleImageArray = [[NSMutableArray alloc] init];
fenceImageArray = [[NSMutableArray alloc] init];
mainField = CGRectMake(10, 35, 310, 340);
..........
[NSTimer scheduledTimerWithTimeInterval:0.05 target:self selector:#selector(gameLoop) userInfo:nil repeats:YES];
}
So basically, the player touches the screen to set up the fences/poles
-(void)touchesBegan:(NSSet *)touches withEvent:(UIEvent *)event {
if(.......) {
.......
}
else {
UITouch *touch = [[event allTouches] anyObject];
currentTapLoc = [touch locationInView:touch.view];
NSLog(#"%i, %i", (int)currentTapLoc.x, (int)currentTapLoc.y);
if(CGRectContainsPoint(mainField, currentTapLoc)) {
if([self checkFence]) {
onFencePole++;
//this 3 set functions adds their respective objects into the 3 NSMutableArrays using addObject:
[self setFencePole];
[self setFenceImage];
[self setFencePoleImage];
.......
}
}
else {
.......
}
}
}
}
The setFence function (setFenceImage and setFencePoleImage is similar to this)
-(void)setFencePole {
Fence *fencePole;
if (!elecFence) {
fencePole = [[Fence alloc] initFence:onFencePole fenceType:1 fencePos:currentTapLoc];
}
else {
fencePole = [[Fence alloc] initFence:onFencePole fenceType:2 fencePos:currentTapLoc];
}
[fencePoleArray addObject:fencePole];
[fencePole release];
and whenever I press a button in the game, endOpenState is called to clear away all the extra images(fence/poles) on the screen and also to remove all existing objects in the 3 NSMutableArray. Point is to remove all the objects in the NSMutableArrays but keep the array itself so it can be reused later.
-(void)endOpenState {
........
int xMax = [fencePoleArray count];
int yMax = [fenceImageArray count];
for (int x = 0; x < xMax; x++) {
[[fencePoleImageArray objectAtIndex:x] removeFromSuperview];
}
for (int y = 0; y < yMax; y++) {
[[fenceImageArray objectAtIndex:y] removeFromSuperview];
}
[fencePoleArray removeAllObjects];
[fencePoleImageArray removeAllObjects];
[fenceImageArray removeAllObjects];
........
}
The crash happens here at the checkFence function.
-(BOOL)checkFence {
if (onFencePole == 0) {
return YES;
}
else if (onFencePole >= 1 && onFencePole < currentMaxFencePole - 1) {
CGPoint tempPoint1 = currentTapLoc;
CGPoint tempPoint2 = [[fencePoleArray objectAtIndex:onFencePole-1] returnPos]; // the crash happens at this line
if ([self checkDistance:tempPoint1 point2:tempPoint2]) {
return YES;
}
else {
return NO;
}
}
else if (onFencePole == currentMaxFencePole - 1) {
......
}
else {
return NO;
}
}
So the problem here is, everything works fine until checkFence is called the 2nd time after endOpenState is called. So its like tap_screen -> tap_screen -> press_button_to_call_endOpenState -> tap screen -> tap_screen -> crash
What I'm thinking of is that fencePoleArray got messed up when I used [fencePoleArray removeAllObjects] because it doesn't crash when I comment it out. It would really be great if someone can explain to me what went wrong. And thanks in advance.
First, a couple of suggestions:
if (!elecFence) {
fencePole = [[Fence alloc] initFence:onFencePole
fenceType:1 fencePos:currentTapLoc];
}
else {
fencePole = [[Fence alloc] initFence:onFencePole
fenceType:2 fencePos:currentTapLoc];
}
You’re making this too hard, how about this:
const int fenceType = elecFence ? 2 : 1;
Fence *fencePole = [[Fence alloc] initFence:onFencePole
fenceType:fenceType fencePos:currentTapLoc];
And this:
int xMax = [fencePoleArray count];
int yMax = [fenceImageArray count];
for (int x = 0; x < xMax; x++) {
[[fencePoleImageArray objectAtIndex:x] removeFromSuperview];
}
for (int y = 0; y < yMax; y++) {
[[fenceImageArray objectAtIndex:y] removeFromSuperview];
}
Could be shortened using makeObjectsPerformSelector:
const SEL remove = #selector(removeFromSuperview);
[fencePoleImageArray makeObjectsPerformSelector:remove];
[fenceImageArray makeObjectsPerformSelector:remove];
This is shorter and safer, as the xMax bound in your code is computed from fencePoleArray and used to iterate over fencePoleImageArray. (Could be right, could be wrong.)
Now to the objectAtIndex: call. If the array is still in memory and you tried to access an object beyond the array bounds, you would get an exception. So that I guess that either the array or some of the objects in it got released without you knowing it. You could try to NSLog the array and the object on given index and try to log their retainCount. If the logging line crashes, you have found the object that’s been released and can start looking for the cause.
(And one more thing: You should split the game logic into a separate model class. This simplifies the code and makes it easier to reason about.)
If you want to use properties, you should use self.propertyName = ... instead of propertyName = ....
Hope this will help.