In this lab, you'll learn about basic memory management and collections by implementing the classic game, Asteroids.
ASView
NSView
into which everything draws. Maintains an NSMutableArray
of drawables. Redraws every 1/60th of a second using a timer.ASDrawable
NSImage
. It has a position and a rotation, as well as a velocity.ASKeyboard
ASShip
ASDrawable
which represents your ship. Fires bullets and moves in response to keyboard input.ASBullet
ASDrawable
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 release
s the smallerAsteroids
array, then sends [super dealloc]
. Without this method, you would have alloc
ated an NSArray
without release
ing 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 release
s 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.