5. The rules of the game
5.1 Missile lifetimes
We’ve got to stop those missiles from flying around indefinitely. Let’s do that by giving them a fixed lifetime. Each time a missile is created, we’ll give it a positive time-to-live value (TTL) which we’ll decrement on every tick of the clock. When a missile’s TTL reaches zero, we’ll delete the missile.
This TTL value will be generally useful, so let’s add it as an attribute of the Sprite class.
class Sprite < OSX::NSObject attr_accessor :position, :velocity, :radius, :color, :ttl
Initialize it to -1 in Sprite initWithPosition_. We’ll say that objects with negative TTL live indefinitely.
def initWithPosition_(position) @position = position @velocity = OSX::NSPoint.new(0, 0) @ttl = -1 self end
Decrement the TTL value in Sprite moveWithBounds_. Add the following to the beginning of the method.
@ttl -= 1 if @ttl > 0
Add MISSILE_LIFE = 50 to the top of your file, then add the following line to Missile initWithPosition_velocity_color_:
@ttl = MISSILE_LIFE
5.2 Removing dead objects
We need to remove missiles when their TTL value reaches zero. We can do this by adding a single line of Ruby code at the end of the Game tick method:
@missiles.delete_if {|missile| missile.ttl == 0}
Try it! Your missiles will now fly about halfway across the screen and then disappear.
5.3 Collisions
One more step and we’ll have a playable game.
We need to detect collisions between objects. We can make this easy by treating all our objects as circles. Objects collide when the distance between their centers is less than the sum of their radii. We add a collision detection method to our Sprite class:
def collidesWith_?(sprite) dx = @position.x - sprite.position.x dy = @position.y - sprite.position.y r = @radius + sprite.radius return false if dx > r or -dx > r or dy > r or -dy > r dx*dx + dy*dy < r*r end
You may notice two tests in the above code. The first excludes obvious misses where the x or y coordinates are too far apart. The second test compares distance but uses distance squared to avoid calculating unnecessary square roots.
Now we modify our Game tick function to check for collisions and delete objects that are affected by them. Here’s the entire function:
def tick(timer) @ship.moveWithBounds_(@bounds) if @ship @rocks.each {|rock| rock.moveWithBounds_(@bounds)} @missiles.each {|missile| missile.moveWithBounds_(@bounds)} @rocks.each {|rock| @missiles.each {|missile| missile.ttl = rock.ttl = 0 if missile.collidesWith_?(rock) } @ship.ttl = rock.ttl = 0 if @ship and @ship.collidesWith_?(rock) } @ship = nil if @ship and @ship.ttl == 0 @rocks.delete_if {|rock| rock.ttl == 0} @missiles.delete_if {|missile| missile.ttl == 0} end
Now play it! As we’ve currently written our game, you only get one ship per game, but you can easily start over by creating a new game with File->New. And since this is a Cocoa document-based application, you can create as many games as you want and play them all concurrently! (yes, that would be weird, but you don’t have to tell anyone.)
Did you find an error? Is something missing? Post your comment or suggestion below!
Comments (0) post