I have a class called Projectiles which inherits from CCSprite class,
Currently there are 2 types of projectiles, rain1 and rain2.
I have a method that creates a bunch of these sprites every 2 seconds to give the illusion of pulsating rain.
Each one of these rain sprites is added to the array, _projectiles and it is effected by gravity.
In fact, its working just about perfectly, except for the memory management and soon after this rain loop keeps creating sprites I get massive frame rate drops.
Ideally, if the rain (under the constant of gravity) drops below the height of the screen, I want the rain sprite to be deleted. Deleted from the _projectiles array, deleted from the view completely!
My code isn't doing this! Please I need some assistance...
Here is a snippet:
for (Projectile *rain1 in _projectiles){
if (rain1.position.y < -winSize.height) {
rain1 = nil;
[_projectiles removeObject: rain1];
[self removeChild:rain1 cleanup:YES];
[rain1 release];
}
}
for (Projectile *rain2 in _projectiles){
if (rain2.position.y < -winSize.height) {
rain2 = nil;
[_projectiles removeObject: rain2];
[self removeChild:rain2 cleanup:YES];
[rain2 release];
}
}
remove the rain1 = nil, that should work.
you change the rain1 pointer to nil, thats why when you call [self removechild] it cannot find the rain1 sprite to remove.
Related
I am making a game, and part of the game is a rabbit collecting eggs. I am having an issue with when the rabbit intersects the egg the game is crashing. the error I am getting is Collection <__NSArrayM: 0x17805eed0> was mutated while being enumerated.'
I have an image of 1 egg, and the egg appears after every couple of seconds, when the rabbit intersects the egg, I just want the egg to disappear and give 1 point.
Here is the code I am using
In the header file I have
#property (strong, nonatomic) NSMutableArray *eggs;
and the implementation file I have this for adding the egg
UIImageView *egg = [[UIImageView alloc] initWithFrame:CGRectMake(CGRectGetWidth([[self gameView] frame]), holeBottom - 115 , 50, 60)];
[egg setImage:[UIImage imageNamed:#"easterEgg.png"]];
[[self gameView] insertSubview:egg belowSubview:[self counterLabel]];
[[self eggs] addObject:egg];
And this for detecting collision and trying to remove the egg
for (UIView *egg in [self eggs]) {
[egg setFrame:CGRectOffset([egg frame], [link duration]*FBSidewaysVelocity, 0)];
if ( CGRectIntersectsRect (rabbit.frame, CGRectInset ([egg frame], 8, 8))) {
[[self eggs]removeLastObject];
[self incrementCount];
}
}
I'm hoping you can see where I have gone wrong with this code and help me to rectify the problem.
Thank you in advance for your time
the error message if very clear that you cannot mutable collection (e.g. remove element) while enumerating it (e.g. using for in loop)
the easiest solution is to copy the collection and enumerate the copied one
for (UIView *egg in [[self eggs] copy]) { // <-- copy
// you can modify `[self eggs]` here
}
or
NSMutableArray *tobeRemoved = [NSMutableArray array];
for (UIView *egg in [self eggs]) {
if (condition)
[tobeRemoved addObject:egg];
}
[[self eggs] removeObjectsInArray:tobeRemoved];
Collection <__NSArrayM: 0x17805eed0> was mutated while being enumerated is being caused because you are looping over an array, while at the same time deleting the objects in that array. There are a few ways to get around this, one way is to create a new array of the objects that you want to delete while looping over the original array eggs and after the loop is finished, looping over this new array and performing the remove.
Code example:
NSMutableArray *eggs;//this is full of delicious eggs
//...
NSMutableArray *badEggs = [NSMutableArray array];//eggs that you want to removed will go in here
for(NSObject *egg in [self eggs]){
if([egg shouldBeRemoved]){//some logic here
[badEggs addObject:egg];//add the egg to be removed
}
}
//now we have the eggs to be removed...
for(NSObject *badEggs in [self badEggs]){
[eggs removeObject:badEgg];//do the actual removal...
}
note: your line of code [[self eggs]removeLastObject]; looks like a mistake in any case... this removes the object at the end of the array (I don't think you want to do this...)
I am trying to find if collision occurs between two rectangles in objective-c. I thought one way to accomplish this would be detect the rectangle that is closest to 0,0 point then do rest of the work.
I wrote a function that takes two rectangle objects as parameters and does the math to calculate area, distance to origin etc....
So lets say rect1 is at (100,200) and rect1's width is 100 height 200, rect2 is at 150,150 and rect2's width is 100 height 200 this is calculated by function well enough.
If I switch rec1 and rect2 properties, so rect1 will be at 150,150 while rect2 will be at 100,200. And call following function
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
Rectangle *temporary = [[Rectangle alloc] init];
temporary=rectA;
rectA=rectB;
rectB=temporary;
[temporary release];
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;
When I enable guard malloc and zombies I get following error:
-[Rectangle origin]: message sent to deallocated instance 0x100acffd0
As soon as rectA.origin.x; is called I get the error.
So Howcome rectA or rectB is deallocated? What is the correct way to switch two objects that has bunch of properties ?
There is a built in function for comparing CGRects CGRectIntersectsRect(rectA, rectB) that you can use to check your rectangle's frames :)
As far as your code for switching you have created a third object by allocing temporary. Then you set the temporary pointer at rectA and then you release rectA at the end since its pointing to temporary. Leaving the newly created object as a leak and then sending messages to the released rectA.
You don't really want to swap object pointers like that if you can help it in my experience. But if you absolutely have to and understand what's going on you could do it like this:
// Create copies of your objects
Rectangle *rectACopy = [rectA copy];
Rectangle *rectBCopy = [rectB copy];
// release the originals.
[rectA release];
[rectB release];
// Set your copies to the original pointers.
rectA = rectBCopy;
rectB = rectACopy;
NSCopying Protocol
First you need to implement the protocol.
#interface Rectangle : NSObject <NSCopying>
Then you need to create the new method. This will create a new object but with all the same values.
- (id)copyWithZone:(NSZone *)zone
{
id copy = [[[self class] alloc] init];
if (copy) {
// Copy NSObject based properties like UIViews, NSArrays, Subclassed objects.
[copy setObjectProperty:[self.objectProperty copy]];
// Set primitives like ints, CGRects, Bools.
[copy setPrimitiveProperty:self.primitiveProperty];
}
return copy;
}
You don't need to allocate a new object instance for temporary (and therefore you don't need to release it either). You are just taking your 2 existing pointers and switching them around. You're correct to use a 3rd variable (temporary) but you don't need to allocate any new space because you're not moving anything in memory, just swapping which variables point to the existing objects.
-(Rectangle*)intersect:(Rectangle*)rectA:(Rectangle*)rectB{
//check closest rectangle to 0,0 and switch rectangles
if (rectA.origin.x>rectB.origin.x) {
//Rectangle *temporary = [[Rectangle alloc] init]; // here you don't need to allocate as you are not using this object
// So use
Rectangle *temporary=rectA;
rectA=rectB;
rectB=temporary;
//[temporary release]; //you don't need this. Here you were releasing rectA not the temp rect that you allocated just below if, as you assign rectA to temporary
}
float rectAX = rectA.origin.x;
float rectAY = rectA.origin.y;
float rectBX = rectB.origin.x;
float rectBY = rectB.origin.y;
I've got a GameScreen.m file like (this is a simplified piece of the code):
- (IBAction) onCellClick:(id) sender
{
points +=1;
self.myScore.text = [[NSNumber numberWithInt: points] stringValue];
//myScore is a label in GameScreenViewController xib
}
That is, upon clicking a cell in the view, it will increase a text label by 1. So far so good.
then, in the same code, I've got a timer:
- (void) startTimer
{
[NSTimer scheduledTimerWithTimeInterval:1.0f
target:self
selector:#selector(updateCounter:)
userInfo:nil
repeats:YES];
}
its updateCounter method is:
- (void) updateCounter:(NSTimer *)theTimer
{
int seconds;
static int count = 0;
count +=1;
timeElapsed = [[NSString alloc] initWithFormat:#"%d", seconds + count];
self.time.text = timeElapsed;
//time is a label in GameScreenViewController xib
}
the thing is that "time" label is not updated (1 sec each time) in this case. I've inserted an AlertView to check if the startTimer method is valid and correctly called, and it actually is (it shows an annoying alertview each second with the timeElapsed value). However, I can' get the time label value to be changed.
Why is my score label updated upon action, while time label isn't updated every second? Is there any way I can update it without including my code in the ViewController?
//note: my coding splits into three files: the appDelegate flips screens and sends values among them; my viewControllers just the windows and, finally, my GameScreen class manages all the processes. From the xib, File's Owner is connected to the ViewController, and the view is connected to GameScreen class.
Thanks a lot for any feedback, please feel free to ask for any piece of additional code needed.
You have to do that (UI related operations) in main thread.
Instead of the line,
self.time.text = timeElapsed;
do as follows:
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed waitUntilDone:NO];
Edit:
- (void) updateCounter:(NSTimer *)theTimer
{
//int seconds;
static int count = 0;
count +=1;
NSString *timeElapsed1 = [[NSString alloc] initWithFormat:#"%d", count];
[self.time performSelectorOnMainThread:#selector(setText:) withObject:timeElapsed1 waitUntilDone:NO];
[timeElapsed1 release];
//time is a label in GameScreenViewController xib
}
I have gone through an UGLY walkaround. It works to some extent, but I went through such a crappy fix that I'm too embarassed to share...
Basically, I moved my timer directly to ViewController since I want it to be fired upon view load and can't get it to work with a call from ViewController's -(void)viewDidLoad to GameScreen's -(void) startTimer. All other stuff involving both methods is, pretty much, duplicated (ok, not duplicated, let's say 'polymorphed' since I handle some variables to fire them).
It seems my GameScreen.m IBActions can only fire other methods within my GameScreen.m, not on GameScreenViewController.m. Thus, I'm handling my buttons' behavior on GameScreen.m and, on GameScreenViewController.m, I just handle 'automatic' stuff; that is, anything not depending on user interaction. It made me have some IBOutlets duplicated depending on input/output needed so I guess that, since it's now working, you can't tell the difference if you don't go under the hood...
Thanks everyone for their feedback though.
I have an array of ten sprites; they all sit in the same area of the screen For ease of explanation, I'll say at the bottom-middle, stacked one on top of each other. What I need to do is take the top sprite and throw it off the screen by swiping it forward (bottom to top). When I have a single sprite, I don't have an issue with this; it's when I have multiple sprites that I have the issues. I add the items to the layer like so:
for(int i=0; i<maxCount; i++){
CCSprite *x = [listOfItems objectAtIndex:i];
//NSLog(#"(%f, %f)", x.position.x, x.position.y);
[self addChild:x];
}
They are created in a step above:
for (int i=0; i<maxCount; i++) {
CCSprite *o = [CCSprite spriteWithFile:#"image.png"];
o.position = ccp(windowSize.width/2,windowSize.width/2);
[listOfItems addObject:o];
[o release];
}
I guess the real issue is I don't know how to handle the
"current" top. So, if I have array index 0 as the top, I can just go ahead and use the touch gesture and "flick" it off the screen. The first one works all right, but once I touch the second one it crashes with EXC_BAD_ACCESS. Here's the TouchesBegan method:
-(void) ccTouchesBegan:(NSSet *)touches withEvent:(UIEvent *)event{
UITouch *touch = [touches anyObject];
CGPoint location = [touch locationInView:[touch view]];
location = [[CCDirector sharedDirector] convertToGL:location];
startingPoint = location;
//Trying something here....
//if(current)
// [current release];
current = [listOfItems objectAtIndex:currentPosition];
[self reorderChild:current z:2];
actionStartTime = [NSDate timeIntervalSinceReferenceDate];
}
When the "Trying something here..." lines are uncommented, that's where the crash happens, and when they are commented out, it happens when assigning current. I know I'm missing something here, but I just can't figure it out.
If your objects are are all added in an array I find the easiest thing to do is set a different tag to each object.
for(int i = 0; i < NUMOFOBJECTS; i++)
{
CCSprite *sprite = blah blah whatever;
// I do something like 100+i so that each time I want to
// create different types of objects I can subtract the "100"
// or whatever it is to get a base index value for arrays.
sprite.tag = 100+i;
}
Inside of your CCTouchesBegan you check the touch to make sure it is on the items and then you iterate through your items there and move only the item with the correct tag.
for(CCSprite *sprite in objects)
{
if(sprite.tag - 100 == [objectOrderList objectAtIndex:0].tag)
{
// Do whatever
}
}
Treat that as pseudo code as I am just writing off the top of my head. In this situation you would keep track of object order in an NSMutableArray, removing/replacing/adding objects whenever order is changed.
One point would be to remove [o release]; CCSprites are created as autoreleased objects.
Also, to find the reason of a crash, try opening the Debug Console which is under the Run menu. The last thing in the console will often display a message with why the error occured.
Look at how you set current — it's set to [listOfItems objectAtIndex:currentPosition], which is an object you don't own. So by the next go-round, that object might have been deallocated, and sending it any message once it's deallocated (including release) is undefined behavior, which will result in EXC_BAD_ACCESS if you're lucky. (It can result in much subtler and harder to catch bugs if you're unlucky.)
This is my 'tick' function:
- (void) tick: (ccTime) dt
{
NSLog(#"%d",ticker);
if(fbut.Adown == YES && ticker > 4)//fbut is a button
{
elayer = [[effectsLayer alloc] init]; // each effectlayer draws a //projectile that moves forward 'x' ticks
elayer.e_playpos = glayer.playerpos; // player position
[self addChild:elayer z:2];
[mutable addObject: elayer];
[elayer release];
if(mutable.count > 20) // when there are 20 projectiles drawn, start //destroying the last one.
{
NSLog(#"mutable: %d", mutable.count);
[mutable removeLastObject];
}
ticker=0;
}
ticker++;
// . . .
this is what the running program looks like
http://screencast.com/t/LpNHL2kJIVpu
looks like more than 20..
interesting thing though, is that the array is holding steady at 20 objects. so if the objects are being 'removed' (via [mutable removeLastObject];) how come they show up on the screen?
Here's the next pickle...
Now i change init to retain( look for the *****'s )
- (void) tick: (ccTime) dt
{
NSLog(#"%d",ticker);
if(fbut.Adown == YES && ticker > 4)//fbut is a button
{
elayer = [[effectsLayer alloc] retain]; // *********each effectlayer draws //a projectile that moves forward 'x' ticks
elayer.e_playpos = glayer.playerpos; // player position
[self addChild:elayer z:2];
[mutable addObject: elayer];
[elayer release];
if(mutable.count > 20) // when there are 20 projectiles drawn, start //destroying the last one.
{
NSLog(#"mutable: %d", mutable.count);
[mutable removeLastObject];
}
ticker=0;
}
ticker++;
// . . .
And now no effect layers are being drawn, but still the NSArray holds 21 - 20 objects. all of these objects are uninitialized. so i added init to the end:
elayer = [[[effectsLayer alloc] retain] init];
now i have the same effects from before.
so i try autorelease..
same effect, lots and lots of pew pew pew's, way more than 20.
my goal is to only have 20 alowed to be drawn at a time and once the 20 are drawn they are deallocated. right now, with out deallocation, the program runs ok untill about 4 mins in when there are about 2000 e layers and the fps is about 5..
why won't the ship pew pew right?
(BTW i m using cocos2d frameworks) this is a project that is copyrighted by me, alex earley 2009.
First, [[effectsLayer alloc] retain] is terrible. Never do that. Ever. Never use an allocated object that has not been initialized. Furthermore, this will retain the object at least twice, since a call to +alloc returns a retained object, and you're retaining it again, then adding it to an array (which retains it a third time), but it's only getting released twice (when it's removed from the array and your singular release).
I suspect that the problem is with this line: [self addChild:elayer z:2]; What does this method do? Is it in charge of actually drawing the elayer onto the screen? If so, then that means its probably also retaining elayer, which means it's not getting deallocated, because you don't appear to be doing any sort of "removeChild" call when popping things out of your mutable array.
In short: just because something's no longer in your array doesn't mean it's also not on the screen.