I'm very new to Xcode/programming and trying to modify existing code
I'm having a small problem where I have an amount of objects (enemies) on the screen at one particular time and cannot redefine their value. I set my enemies to begin with 3 on the screen.
My objective is to change the amount of enemies based on the current score.
I've attached snippets of the code below.
int numberOfEnemies;
if (self.score>=0) {
numberOfEnemies = 3
}
else if (self.score>=100) {
numberOfEnemies = 4
}
// Setup array
enemyArray = [[NSMutableArray alloc] init];
for(int i = 0; i < numberOfEnemies; i++) {
[enemyArray addObject:[SpriteHelpers setupAnimatedSprite:self.view numFrames:3
withFilePrefix:#"enemyicon" withDuration:((CGFloat)(arc4random()%2)/3 + 0.5)
ofType:#"png" withValue:0]];
}
enemyView = [enemyArray objectAtIndex:0];
What do I need to do to parse the new value of numberOfEnemies into the array when my score updates?
I'm going to move our conversation into an answer since I don't want it to get too long winded, and I can easily edit and expand on this.
So far, we've established that the reason that you're having issues is that you execute the above code in the viewDidLoad function, which will run at least once when the application is first started. The problem with this is as you've found out, that you arent getting a chance to see a new score, and then update the number of enemies.
I know that game update loops for iOS are usually done in the following structure, but I would recommend finding a tutorial online to get what may be a more efficient/correct way to do it.
From your current structure, I would take the code you have above and create a new function out of it:
-(void) updateDifficulty:(NSTimer *)gameTimer
{
//This can be the code you have above for now
}
Afterwards, inside of your viewDidLoad, I would put the following code:
-(void) viewDidLoad:
{
gameTimer = [NSTimer timerWithTimeInterval:1.0 target:self selector:#selector(updateDifficulty:) userInfo:nil repeats:YES];
[[NSRunLoop mainRunLoop] addTimer:gameTimer forMode:NSDefaultRunLoopMode];
}
What that does is it declares a timer that will keep track of the game time, and with how it was declared, every 1 second it will call the updateDifficulty method. This is the general structure that you want, but again I would highly suggest you check out a game tutorial from Ray Wenderlich for example.
Hope that helps!
Related
Ok so I've recently decided to try to teach myself Objective-C (I'm interested in iPhone development), however I've never used C or any of its derivatives before, and as such am running into some problems.
I decided to start out by writing a very basic card application that creates a deck of cards, shuffles the deck, and then displays the cards on the screen using UIButtons, however I'm having a problem with my shuffling algorithm. Every time it gets called I get an EXC_BAD_ACCESS error, which I know means there's something desperately wrong with my code, but I just can't figure out what it is.
- (void) randomize {
NSMutableArray *tmpDeck = [[NSMutableArray alloc] init];
for(Card *tmp in _cards) {
BOOL didInsert = NO;
while(!didInsert) {
NSUInteger random = arc4random_uniform(54);
if([[tmpDeck objectAtIndex:random] isEqual:nil]) {
[tmpDeck insertObject:tmp atIndex:random];
didInsert = YES;
}
}
}
_cards = tmpDeck;
_hasBeenRandomized = YES;
}
_cards is a pointer to an NSMutableArray containing the unshuffled deck of card objects, and _hasBeenRandomized is a boolean (obviously) that keeps track of whether or not the deck has been randomized.
I've tried to use the debugger to work out what exactly is going on here, but I can't even step into the method without my program crashing. This leads me to believe that the problem has to come from the very first line, but it's just a straightforward creation of an NSMutableArray, so I don't know how it could be that. This method is being called from within viewDidLoad. This is the entirety of the viewDidLoad method currently.
- (void)viewDidLoad
{
[super viewDidLoad];
_deck = [[Deck alloc] init];
[_deck randomize];
}
Any and all help will be appreciated. Sorry if the answer is dead obvious.
This is because you are trying to insert into an index that doesn't exist yet. You need to initialize the array with as many places in the array as you need for your cards. Either that or use a NSMutableDictionary and just insert the object with the index being the key.
To add another note, calling initWithCapacity on the array wouldn't solve this for you either since this just gives a "hint" at the size. You need the count property of the array to actually be at least as large as the index you are trying to insert. If you wanted to do an array, then you would first need to populate something in each index first. You could define this in the new array literal format or use a for loop that loops the number of times you need (your max index) and insert a dummy object in it's place.
for (int i=0; i< _cards.count; ++i)
{
[tmpDeck insertObject:#"dummy" atIndex:i];
}
Then instead of checking for 'nil' before you replace, you check if it is equal to the dummy object you inserted. This would give you an array that you can insert into any of these indexes. I personally would still probably store them in an NSMutableDictionary. But if you need it in an array for some other purpose then this is a way to do it.
You also will need to be sure to replace the object instead of inserting, otherwise you will just keep adding indexes.
[tmpDeck replaceObjectAtIndex:random withObject:tmp];
If you still get the same error, set a breakpoint in your debugger and check what the random number is and what the count of your array is. If your random number is ever greater than your array count, then you will get this error.
In my app I want to stack a large number of (identical PNG) files on top of each other and show the user the image-result as well as the progress-bar. In order to improve performance I came up with the following code, but only when the for-loop finishes I'll see the end-result.
For large sets of images this is not what I want, and I really want to see intermediate results. The following code probably contains errors, which I can't find yet:
- (IBAction)doDrawLayers:(NSArray *)drawImages
{
// Use dispath-queues for drawing intermediate result and progress.
_startDrawing = [NSDate date];
dispatch_queue_t calcImage = dispatch_queue_create("calcImage", NULL);
dispatch_async(calcImage, ^{
_sldProgress.maximumValue = drawImages.count;
_sldProgress.minimumValue = 0;
_imageNr = 0;
[_sldProgress setMaximumValue:drawImages.count];
for (NSString *imageFile in drawImages) {
#autoreleasepool {
// Update in main queue.
dispatch_async(dispatch_get_main_queue(), ^{
UIGraphicsBeginImageContext(_imageSize);
// Show progress update.
_lblProgress.text = [NSString stringWithFormat:#"%d %.2f sec", ++_imageNr, -[_startDrawing timeIntervalSinceNow]];
_sldProgress.value = _imageNr;
NSString *filePath = [[NSBundle mainBundle] pathForResource:imageFile ofType:nil];
[[UIImage imageWithContentsOfFile:filePath] drawAtPoint:CGPointMake(0., 0.)];
// Get CGImage from the offscreen image context.
_imageView.image = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
});
}
}
});
// Finished calculating image, release dispatch.
dispatch_release(calcImage);
}
I'm also struggling with the question of whats the best place to put the UIGraphicsBeginImageContext/UIGraphicsEndImageContext-pair, since I want to minimize the amount of memory being used and maximize the overall performance.
Finally found a solution by merging code from Apple's PhotoScroller (mainly based on CATiledLayer) and LargeImageDownsizing example code.
I ran the following code expecting to schedule three subsequent calls, at different time intervals (e.g. after 1 sec, after 2.6sec etc..) on the method "displayWarningMessage" but didn't work (it displayed the massage only the first time).
I don't find a method signature in the scheduler that would do the job of displaying it multiple times and with a specific delay. Anyone has some suggestion?
[self scheduleOnce:#selector(displayWarningMessage) delay:0.7f];
[self scheduleOnce:#selector(displayWarningMessage) delay:1.7f];
[self scheduleOnce:#selector(displayWarningMessage) delay:3.7f];
Problem here is, when you call first schedule it is scheduled successfully. But the next immediate call is throwing warning something
CCScheduler#scheduleSelector. Selector already scheduled. Updating interval from: X.2 to X.2
you can see this in the log.
What you can do is when the selector is called, at the end of the method you can schedule it again for the next time, until you are done. You may take a counter to keep track of how many times it has been called, put all of your intervals in an array and then schedule next selector for the interval at the specific index identified by counter. like this:
NSArray *intervals = [NSArray arrayWithObjects:[NSNumber numberWithFloat:0.7],[NSNumber numberWithFloat:1.7],[NSNumber numberWithFloat:3.7], nil];
int counter = 0;
//schedule it for the first time with object at index counter/index 0
[self scheduleOnce:#selector(displayWarningMessage) delay:[(NSNumber *)[intervals objectAtIndex:counter]] floatValue];
now in your selector, do something like this:
-(void)displayWarningMessage
{
//do all your stuff here
//increment counter
counter ++;
if(counter < [intervals count])
{
//schedule it for the next time with object at index counter/index
[self scheduleOnce:#selector(displayWarningMessage) delay:[(NSNumber *)[intervals objectAtIndex:counter]] floatValue];
}
}
intervals and counter should be class ivars of-course.
Try this:
- (void)displayWarningMessage {
//Stuff
}
- (void)callStuff {
CCCallFunc *call = [CCCallFunc actionWithTarget:self selector:#selector(displayWarningMessage)];
CCDelayTime *delay1 = [CCDelayTime actionWithDuration:0.7f];
CCDelayTime *delay2 = [CCDelayTime actionWithDuration:1.7f];
CCDelayTime *delay3 = [CCDelayTime actionWithDuration:3.7f];
CCSequence *actionToRun = [CCSequence actions:delay1, call, delay2, call, delay3, call, nil];
[self runAction:actionToRun];
}
That should work for what you're trying to do, at least that's how I'd imagine doing it. I'm fairly sure you can call that CCCallFunc multiple times in one CCSequence without having to create it three individual times. You could also make those delays variable based if need be, of course. Let me know how it goes.
Method is created.
[self schedule: #selector(displayWarningMessage:) interval:3.2f];
-(void) displayWarningMessage:(ccTime) delta
{
CCLOG(#"alert........!!!!!!");
}
Use the Calling method in not warning message detected.
I am trying to understand how to set a label to be the text from an array when you press the button. When I press the button, the label disappears, and then nothing comes up. No crashes in code.
Relevant code:
-(void)setupArray {
wordArray = [[NSMutableArray alloc] init];
[wordArray addObject:#"test1"];
[wordArray addObject:#"test2"];
[wordArray addObject:#"test3"];
}
- (IBAction)start:(id)sender {
int value = (arc4random() % 3) + 1;
self.typeThis.text = [self.wordArray objectAtIndex:value];
}
typeThis is the label name, and I think I have hooked up everything already, i.e. set up the buttons/delegates/etc...I don't understand why it isn't working. Can anybody help?
considering you have bound everything properly and you are not under ARC. Here is a thing that might cause you the issue.
when you are allocating wordArray you can try using following code snippet.
NSMutableArray tempArray = [[NSMutableArray alloc] init];
self.wordArray = tempArray;
[tempArray release];
if you are under ARC you can try self.wordArray = [NSMutableArray array];
then add objects to self.wordArray i.e.[self.wordArray addObject:#"test1"];. Here is some explanation about arc4random().
EDIT :
Here's a public spec for Automatic Reference Counting and a quote from the public iOS 5 page:
Automatic Reference Counting (ARC) for Objective-C makes memory
management the job of the compiler. By enabling ARC with the new Apple
LLVM compiler, you will never need to type retain or release again,
dramatically simplifying the development process, while reducing
crashes and memory leaks. The compiler has a complete understanding of
your objects, and releases each object the instant it is no longer
used, so apps run as fast as ever, with predictable, smooth
performance.
It is possible to detect if ARC is enabled. Simply add the following snippet to any file that requires ARC.
#ifndef __has_feature
#define __has_feature(x) 0 /* for non-clang compilers */
#endif
#if !__has_feature(objc_arc)
#error ARC must be enabled!
#endif
More info :
http://clang.llvm.org/docs/LanguageExtensions.html#__has_feature_extension
HTH.
Your '+1' is giving you a result between 1 and 3, and your indexes are from 0 to 2, so I'd expect it to go wrong one time in 3.
Is this under ARC? If so, is wordArray declare as strong?
I have a computational process that takes quite a bit of time to perform so a UIActivityIndicatorView seems appropriate. I have a button to initiate the computation.
I've tried putting the command [calcActivity startAnimating]; at the beginning of the computation in an IBAction and [calcActivity stopAnimating]; at the end of the computation but nothing shows.
Next, I created a new IBAction to contain the starting and stopping with a call to the computation IBAction and a dummy for loop just to give the startAnimating a little chance to get started between the two. This doesn't work either.
The skeletal code looks like this:
- (IBAction)computeNow:(id)sender {
[calcActivity startAnimating];
for (int i=0; i<1000; ++i) { }
[self calcStats];
[calcActivity stopAnimating];
}
- (IBAction)calcStats {
// do lots of calculations here
return;
}
Ok, as I commented, you should never performe complex calculations in your main thread. It not only leads to situations like yours, your app might also be rejected from the store.
Now, the reason for the UIActivityIndicatorView not being updated is, that the UI doesn't actually update itself e.g. when you call [calcActivity startAnimating]; Instead, it gets updated after your code ran through. In your case, that means that startAnimating and stopAnimating are getting called at once, so nothing really happens.
So, the 'easy' solution: Start a new thread, using either this techniques or, probably better, GCD.
Thanks for the nudge, Phlibbo. I'm new to this game and appreciate all the help. I didn't comprehend all the info on the links you provided, but it did prod me to search further for examples. I found one that works well. The IBAction 'computeNow' is triggered by the calculation button. The code now looks like this:
- (IBAction)computeNow {
[calcActivity startAnimating];
[self performSelector:#selector(calcStats) withObject:nil afterDelay:0];
return;
}
- (void) calcStats {
// Lots of tedious calculations
[calcActivity stopAnimating];
}