:wq

Done and done! We’re going to package this up and send BlockHopper out to Apple tomorrow! I still have to run through all the documents and find out what needs to be done for submission. Hopefully not too much!

The sound effect saga had one final chapter. It turned out when I used the second method, only one sound effect at a time could play haha. Not so good! I finally did what I should have done in the first place: added OpenAL. (thanks Raj!) It really only took about an hour to get it all up and running and the hardest part was actually converting all the wavs to caf files in the correct mono format. :D Ben Britten explains the process of setting up OpenAL and playing a sound better than I ever can!

We’re pretty excited right now. :D

Sound Effects Revisitied

Ok, so it turns out building a sound effects class around AVAudioPlayer is a bad idea as it’s pretty slow. The game became really choppy as the sound effects were getting fired off and I’m not the world’s best programmer, so I need all the help I can get! :D System Sound Services to the rescue! There are a few drawbacks to using System Sounds like the sounds can’t be over 30 seconds and they must be in WAV, AIF, or CAF format. Not really a big deal for sound effects and I’m still using AVAudioPlayer for the background music.

The reimplemented SoundPlayer class looks much the same as before:


#import "SoundPlayer.h"

@implementation SoundPlayer

@synthesize isFinished;

void audioDidFinish(SystemSoundID sound, void *isFinished);

- (id)init
{
    self = [super init];
    isFinished = true;
    return self;
}

- (void)play:(SystemSoundID)sound
{
    isFinished = false;
    AudioServicesAddSystemSoundCompletion(sound, NULL, NULL, audioDidFinish, &isFinished);
    AudioServicesPlaySystemSound(sound);
}

void audioDidFinish(SystemSoundID sound, void *isFinished)
{
    *(BOOL *)isFinished = true;
    AudioServicesRemoveSystemSoundCompletion(sound);
}

- (void)dealloc
{
    [super dealloc];
}

@end

Something to note: The callback function that fires when the sound finishes is just straight C, not Objective-C. You’ll have to pass any class variables you want to manipulate as the last parameter of the AudioServicesAddSystemSoundCompletion function.

And to initialize the System Sound IDs:


for (i = 0; i < MAX_SOUNDS; i++)
{
    NSString *filePath = [[NSBundle mainBundle] pathForResource:[soundPaths objectAtIndex:i] ofType:@"wav"];
    AudioServicesCreateSystemSoundID((CFURLRef)[NSURL fileURLWithPath:filePath], &soundEffects[i]);
}

One last little thing, the System Sounds will play through the phone’s speaker out of the box, but to get them also to play through headphones, you’ll have to add this bit of code. I just stuck it in the viewDidLoad function.


AVAudioSession *session = [AVAudioSession sharedInstance];
[session setCategory:AVAudioSessionCategoryAmbient error:NULL];

Meeeemories.

Yesterday, I was checking our game for memory leaks and leared a few things! It is the most terrible of sins to create an uninitialized object such as:


connection = [NSURLConnection alloc];

The game kept crashing in iOS4.2 as I was trying to release an uninitialized object. Unfortunately, iOS5 had no problem with this, so I’m glad we have an iPhone and an iPod Touch to test different versions!

I also had a pretty major memory leak in the object that plays the back ground music. My initial plan was to alloc once and keep the instance around so I was calling initWithData each time the music changed. It never ocurred to me that initWithData might also be creating objects and I was never releasing the instance. Oops. All fixed now.

Regarding total progress, I have a small list of about 10 things left to do and then give it some last-minute testing and we should be ready to roll!

We Be Networking

Oh man, writing network code is the pits haha. This is pretty much my first time and it can be overwhelming at times trying to make sure you’ve covered all the exceptions and errors. We have a level editor built into our game and you can upload the levels you create. In order to keep some kind of quality control, we thought it would be best if everyone had to have a username to upload. Extending this idea, we thought it would be good to tie the username into the greenpixel.ca forums. It all works fairly smoothly, but the downside is I had to add logins and registration to the app. This spawns a number of cases like:

Try to upload -> is there a saved username?
      Yes -> upload and handle errors
      No -> go to login screen. Do you already have a username pass?
           Yes -> Upload and handle errors save credentials
           No -> Register new user. Is the username or email unique? etc etc.

So much sending and receiving and popping up error messages. :D

Looks like it all works now, but I will have to test it all strenuously later and try to simulate the phone losing its network connection.

The networking portion is fairly straightforward, in order to talk to a server, you just need to create an NSURLConnection object. I chose to make a global object that keeps getting initted as I wanted the ability to cancel it.


NSURLConnection *connection = [NSURLConnection alloc];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:[NSString stringWithFormat:@"http://www.website.com/php/dothings.php?id=%d", id]]];
[request setHTTPMethod:@"GET"];
connection = [connection initWithRequest:request delegate:self];

Which sends a GET request. You can attach simple variables to the URL itself, but for more complicated variables, like strings that contain spaces or &, you’ll need to use the function “stringByAddingPercentEscapesUsingEncoding” in the NSString class. Maybe it’s best to use it anyway!

There are 4 delegate methods you have to implement if you want to get anything back from the server. They are:
…didReceiveResponse
…didReceiveData
…didFailWithError
…didFinishLoading

(the exact method names are found in the NSURLConnectionDelegate class)

You’ll need a global NSData object to collect the data as it is returned from the server. This should be reset each time:


NSData *serverData = [[NSMutableData alloc] initWithCapacity:0];

...didReceiveResponse
{
    [serverData setLength:0];
}

The data is received in chunks if the total size is more than the iPhone can handle. Our map data has this problem, so just keep appending to the serverData object:


...didReceiveData:(NSData *)data
{
    [serverData appendData:data];
}

This will keep getting called until it is done. When everything is finished, either …didFinishLoading or …didFailWithError will get called. Then it is time to decide what to do with the data.

Something to keep in mind! The minimum timeout for an iOS app is 240 seconds regardless of what you set it to. 240 seconds is a ridiculously long time to realise the server isn’t going to talk to you. Instead of setting a timeout, I just run a counter in the gameloop. If the counter exceeds a timeout limit, I pull the plug and run the connection fail code.

Sorry if this is hard to read or unhelpful, my brain is fairly fried right now! Time for some Mega Man 2 and then sleep.

Sound Effects Manager

Yesterday, I put the sound effects system into the game. It’s really pretty simple, but it seems to do the job. :D

Sound effects behave differently from the background music because they don’t (usually) loop and they probably need to be able to play simultaneously.

As with the background music, I just went with the AVAudioPlayer class. There’s not much headache involved, you just allocate memory for it and feed it a path to the sound. One small problem popped up: how to tell when it is done? Well, there’s a property “playing” which returns a BOOL depending on its state. Excellent, so every time I need to play a new sound, I check this property? No! There is a line in the documentation,

Important:¬†Do not poll this property (that is, do not use it inside of a loop) in an attempt to discover when playback has stopped.”

So much for that.

It turns out the complete solution was not much more work. There is a second class called AVAudioPlayerDelegate. I don’t know all the technical details, but delegates in Objective-C work a little like interfaces in Java or like callback functions. Basically, it’s a list of functions you implement that get called by the object using the delegate. With the AVAudioPlayerDelegate you can get a tonne of information, but the only one I really needed was “audioPlayerDidFinishPlaying:successfully:”

I just used an array to deal with the simultaneous problem and now I knew when a sound finished playing. Everything was almost done except how do you know if one of the audio players can be used? I ended up making a small wrapper class that holds a BOOL isFinished and an AVAudioPlayer object. The wrapper class has two functions, play and stop, that it just passes on to the AVAudioPlayer object while setting the isFinished flag appropriately. It also extends¬† AVAudioPlayerDelegate and in the “audioPlayerDidFinishPlaying” function, I set isFinished to “true”.

Now the main class can allocate an array of these Sound objects and you should be good to go. I encountered one small problem when multiple instances of the same sound would play on the same frame. I fixed this by using an int and setting its bits depending on which sound it had already playing that frame. Then I clear the int at the beginning of each frame. A better solution would probably be to add in some sort of scheme where you clear the bits based on elapsed time, but this works for now!


int soundFlag = 0; //beginning of each loop
- (void)playSound:(int)index
{
  int i;

  for (i = 0; i < MAX_SOUNDS; i++)
  {
    SoundPlayer *sc = soundChannels[i];

    if (sc.isFinished && !(soundFlags & (index << 1)))
    {
      NSString *filePath = [[NSBundle mainBundle] pathForResource:[soundPaths objectAtIndex:index] ofType:@"wav"];
      NSData *sound = [NSData dataWithContentsOfFile:filePath];

      soundFlags |= (index << 1);
      [sc play:sound];
      return;
    }
  }
}

And here is the SoundPlayer implementation. I’m sure you can figure out the header file. :D It’s pretty simple.


#import "SoundPlayer.h"

@implementation SoundPlayer

@synthesize isFinished;

- (id)init
{
  self = [super init];

  isFinished = true;

  return self;
}

- (void)play:(NSData *)sound
{
  if (avPlayer)
  {
    [avPlayer release];
  }

  avPlayer = [[AVAudioPlayer alloc] initWithData:sound error:NULL];
  avPlayer.delegate = self;
  [avPlayer play];
  avPlayer.numberOfLoops = 0;
  isFinished = false;
}

- (void)audioPlayerDidFinishPlaying:(AVAudioPlayer *)player successfully:(BOOL)flag
{
  isFinished = true;
}

- (void)dealloc
{
  if (avPlayer)
  {
    [avPlayer release];
  }

  [super dealloc];
}

@end