6. Did you get it?
6.1 Our final RubyRocks.rb
Here is a final snapshot of our project. As a bonus, I’ve made three additional enhancements:
- You can pause the game with the ‘p’ key. Press ‘p’ again to continue.
- I added some sound effects using NSSound.
- I fixed the annoying “startup deaths” that occur when a rock is randomly placed too close to the ship at startup.
RubyRocks.rb [ruby]
# # RubyRocks.rb # # Created by Tim Burks on 2/18/06. # Copyright (c) 2006 Neon Design Technology, Inc. Some rights reserved. # This sample code is licensed the same license as RubyCocoa. # Find more information about this file online at http://www.rubycocoa.com/ruby-rocks # NUMBER_OF_ROCKS = 10 MISSILE_SPEED = 10 MISSILE_LIFE = 50 TURN_ANGLE = 0.2 ACCELERATION = 1 KEY_SPACE = 49 KEY_LEFT_ARROW = 123 KEY_RIGHT_ARROW = 124 KEY_DOWN_ARROW = 125 KEY_UP_ARROW = 126 KEY_P = 35 class GameView < OSX::NSView ib_outlets :game def awakeFromNib window.setOpaque(false) @game.bounds = bounds @timer = OSX::NSTimer.scheduledTimerWithTimeInterval_target_selector_userInfo_repeats_(1.0/60.0, self, :tick, nil, true) end ns_overrides :drawRect_ def drawRect(rect) @game.draw end ns_overrides :acceptsFirstResponder def acceptsFirstResponder true end ns_overrides :keyDown_ def keyDown(event) @game.keyDown(event.keyCode) end ns_overrides :keyUp_ def keyUp(event) @game.keyUp(event.keyCode) end def tick(timer=nil) @game.tick(timer) setNeedsDisplay(true) end end class Game < OSX::NSDocument attr_accessor :bounds ns_overrides :windowNibName def windowNibName return "Game" end def awakeFromNib @paused = false @ship = Ship.alloc.initWithPosition_(OSX::NSPoint.new(@bounds.width/2, @bounds.height/2)) @rocks = [] NUMBER_OF_ROCKS.times {@rocks << Rock.alloc.initWithPosition_(OSX::NSPoint.new(rand(@bounds.width), rand(@bounds.height)))} @rocks.delete_if{|rock| rock.collidesWith_?(@ship)} @missiles = [] @sounds = { :shipDestroyed => OSX::NSSound.soundNamed(:Submarine), :rockDestroyed => OSX::NSSound.soundNamed(:Bottle), :shoot => OSX::NSSound.soundNamed(:Pop) } end def draw OSX::NSColor.blackColor.colorWithAlphaComponent(0.9).set OSX::NSRectFill(@bounds) @rocks.each {|rock| rock.draw} @ship.draw if @ship @missiles.each {|missile| missile.draw} end def tick(timer) return if @paused @rocks.each {|rock| rock.moveWithBounds_(@bounds)} @ship.moveWithBounds_(@bounds) if @ship @missiles.each {|missile| missile.moveWithBounds_(@bounds)} @rocks.each {|rock| @missiles.each {|missile| if missile.collidesWith_?(rock) missile.ttl = rock.ttl = 0 @sounds[:rockDestroyed].play end } if @ship and @ship.collidesWith_?(rock) @ship.ttl = rock.ttl = 0 @sounds[:shipDestroyed].play end } @ship = nil if @ship and @ship.ttl == 0 @rocks.delete_if {|rock| rock.ttl == 0} @missiles.delete_if {|missile| missile.ttl == 0} end def keyDown(code) case code when KEY_SPACE: if @ship @missiles << @ship.shoot @sounds[:shoot].play end when KEY_LEFT_ARROW: @ship.angle = TURN_ANGLE if @ship when KEY_RIGHT_ARROW: @ship.angle = -TURN_ANGLE if @ship when KEY_UP_ARROW: @ship.acceleration = ACCELERATION if @ship when KEY_DOWN_ARROW: @ship.acceleration = -ACCELERATION if @ship when KEY_P: @paused = ! @paused end end def keyUp(code) case code when KEY_LEFT_ARROW, KEY_RIGHT_ARROW: @ship.angle = 0 if @ship when KEY_UP_ARROW, KEY_DOWN_ARROW: @ship.acceleration = 0 if @ship end end end class Sprite < OSX::NSObject attr_accessor :position, :velocity, :radius, :color, :ttl def initWithPosition_(position) @position = position @velocity = OSX::NSPoint.new(0, 0) @ttl = -1 self end def moveWithBounds_(bounds) @ttl -= 1 if @ttl > 0 @position.x += @velocity.x @position.y += @velocity.y @position.x = bounds.width if @position.x < 0 @position.x = 0 if @position.x > bounds.width @position.y = bounds.height if @position.y < 0 @position.y = 0 if @position.y > bounds.height end 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 end class Rock < Sprite def initWithPosition_(position) super(position) @velocity = OSX::NSPoint.new(rand-0.5,rand-0.5) @color = OSX::NSColor.whiteColor @radius = 30 self end def draw @color.set OSX::NSBezierPath.bezierPathWithOvalInRect(OSX::NSRect.new(@position.x-radius, @position.y-@radius, 2*@radius, 2*@radius)).stroke end end class Ship < Sprite attr_accessor :direction, :angle, :acceleration def initWithPosition_(position) super(position) @radius = 10 @color = OSX::NSColor.redColor @direction = OSX::NSPoint.new(0, 1) @angle = @acceleration = 0 self end def moveWithBounds_(bounds) super(bounds) if @angle != 0 cosA = Math::cos(@angle) sinA = Math::sin(@angle) x = @direction.x * cosA - @direction.y * sinA y = @direction.y * cosA + @direction.x * sinA @direction.x, @direction.y = x, y end if @acceleration != 0 @velocity.x += @acceleration * @direction.x @velocity.y += @acceleration * @direction.y end end def draw @color.set x0,y0 = @position.x, @position.y x, y = @direction.x, @direction.y r = @radius path = OSX::NSBezierPath.bezierPath path.moveToPoint(OSX::NSPoint.new(x0 + r*x, y0 + r*y)) path.lineToPoint(OSX::NSPoint.new(x0 + r * (-x +y), y0 + r * (-x -y))) path.lineToPoint(OSX::NSPoint.new(x0, y0)) path.lineToPoint(OSX::NSPoint.new(x0 + r * (-x -y), y0 + r * (+x -y))) path.fill end def shoot missilePosition = OSX::NSPoint.new(position.x+direction.x, position.y+direction.y) missileVelocity = OSX::NSPoint.new(MISSILE_SPEED * direction.x + velocity.x, MISSILE_SPEED * direction.y + velocity.y) return Missile.alloc.initWithPosition_velocity_color_(missilePosition, missileVelocity, @color) end end class Missile < Sprite def initWithPosition_velocity_color_(position, velocity, color) initWithPosition_(position) @velocity = velocity @color = color @radius = 3 @ttl = MISSILE_LIFE self end def draw @color.set OSX::NSBezierPath.bezierPathWithOvalInRect(OSX::NSRect.new(@position.x-radius, @position.y-@radius, 2*@radius, 2*@radius)).fill end end
Did you find an error? Is something missing? Post your comment or suggestion below!
Comments (4) post
Hi, I’m quite new to Ruby and RubyCocoa, but grey haired (I note your self description) with programming years. I downloaded your Ruby Rocks code to test for a problem I keep encountering which you mention in your text. The NSView – super warning. I am using the latest verion of rubyCocoa (the universal one can’t remember the version number 4.3—d3(?) and I am still getting the warning with my code and your “Rocks” demo. Can you cast any light? Enjoyed reading through the stuff on your site. The best Ruby Tutorial/info I have found to date. Thanks Dave Simpson
Hi Dave, I believe that you’ll need to build and install RubyCocoa from the CVS sources to get this fix. It’s not difficult; you can find instructions in my introductory article. Thanks for your comments!
The game gets even more enjoyable if you exchange
to in Game.tickWhoops, I just realized that there was yet another chapter, sorry for the spoiler…