In this lab, you'll learn about basic memory management and collections by implementing the classic game, Asteroids.
ASViewNSView into which everything draws. Maintains an NSMutableArray of drawables. Redraws every 1/60th of a second using a timer.ASDrawableNSImage. It has a position and a rotation, as well as a velocity.ASKeyboardASShipASDrawable which represents your ship. Fires bullets and moves in response to keyboard input.ASBulletASDrawable which represents a bullet. Dies automatically after a set number of frames.ASShip creates the bullets in its update method. Go to ASShip.m and find this method.// create the bullet). Using the three rules of memory management, fix the leak.
If you forgot the rules, they are:
retain, you must release.alloc, you must release.copy, you must release.release.
ASAsteroid (using New File… in the File menu). The superclass should be ASDrawable. Make sure to #import "ASDrawable.h".ASAsteroid will initialize a new, large asteroid. The method should be called - (id)initLarge. Add this method to ASAsteroid.h.init method in the superclass using [super initWith...]. self.initWithImage:. You can make a large asteroid image using [NSImage imageNamed:@"asteroidLarge"], which finds an image with that name in the application's bundle.
- (void)awakeFromNib method (it should be near the top of the file). Create a new ASAsteroid (initializing it using your initLarge method) and add it to the list of drawables using addDrawable:. Look at how the ASShip is added if you aren't sure how to do this.#import "ASAsteroid.h" to the top of the file, so the compiler doesn't complain.xVelocity and yVelocity to some non-zero value using property syntax. Build and run again after doing this.ASView tells each drawable to update. You'll implement collision detection by overriding this method. Make an empty - (void)update method.NSArray of drawables on the screen is available in self.view.drawables. The loop should look like this:for (ASDrawable *drawable in self.view.drawables) { ... }drawable is a ship, you can use [drawable isKindOfClass:[ASShip class]] (make sure to #import "ASShip.h" so you can do this). If the drawable is a ship, and it collidesWith:self (a method on ASDrawable) then kill the ship using [drawable die].for loop which tests whether [drawable isKindOfClass:[ASBullet class]] (again remembering to #import "ASBullet.h"). If the bullet collidesWith:self, then both self and drawable (the bullet itself) should die.
NSArray. Add an NSArray *smallerAsteroids instance variable to ASAsteroid.ASAsteroid, - (id)initMedium and - (id)initSmall. They should be identical to - (id)initLarge except for the image name, which should be @"asteroidMedium" and @"asteroidSmall", respectively.- (id)initLarge, initialize smallerAsteroids to be an array of two ASAsteroids, each initialized with initMedium. Similarly, in - (id)initMedium, initialize three ASAsteroids with initSmall. Read the documentation on and use the initWithObjects: method on NSArray to initialize the array. Remember to end the argument list with nil. For example, - (id)initLarge should have a line like this:smallerAsteroids = [[NSArray alloc] initWithObjects:asteroid1, asteroid2, nil];
- (id)initSmall should make smallerAsteroids an empty array (which is different from nil). Just [[NSArray alloc] init] will create an empty array.- (void)dealloc method which releases the smallerAsteroids array, then sends [super dealloc]. Without this method, you would have allocated an NSArray without releaseing it, breaking the second rule of memory management.[self die] in your update method, enumerate through the smaller asteroids using fast enumeration, just like you did with self.view.drawables in the same method. Call [self.view addDrawable:smallerAsteroid] for each smallerAsteroid in the array.smallerAsteroid.x = self.x and smallerAsteroid.y = self.y as well. To make the asteroids come out at a random angle, set the velocity with a random component, like this: smallerAsteroid.xVelocity = self.xVelocity + (rand() % 7) - 3; smallerAsteroid.yVelocity = self.yVelocity + (rand() % 7) - 3;
alloc, there is a release or autorelease somewhere to go with it.
ASShip. Create both an instance variable ASDrawable *shield and a property with the nonatomic and retain attributes. Look at ASView.h for an example of property declaration.@synthesize or @dynamic in your .m file. Add the line @synthesize shield; inside of the @implementation block for ASShip in ASShip.m. This generates setters and getters for the shield property, including proper memory management.@synthesize won't release properties when your object is deallocated, however. Create a - (void)dealloc method for ASShip which releases shield and sends [super dealloc]. You must do this every time you have a property with the retain attribute.shieldPressed on ASKeyboard is YES whenever the shield button is pressed. In the - (void)update method of ASShip, add two if statements:
self.shield is nil and k.shieldPressed, set self.shield to a newly initialized ASDrawable with the image named @"shield". If you didn't check whether self.shield was nil, you would create a new ASDrawable each frame, which isn't very efficient. Using the setter will automatically retain the new drawable; make sure to release anything you may have allocated with alloc.self.shield is not nil, but k.shieldPressed is false, set self.shield to nil. The setter generated by @synthesize will automatically release the instance variable for you.- (void)update, send [self.shield draw] to draw the shield on your ship. Remember that if self.shield is nil, this statement will do nothing, so a nil check is redundant.update method in ASAsteroid.m and check if drawable.shield is not nil before destroying any drawable of class ASShip.drawable is just an ASDrawable, which doesn't have a shield property. You can either send [drawable shield], which is equivalent (but will give you only a warning), or make an intermediate variable of type ASShip and access the property on that.