利用Ruby製作遊戲 – Gosu教學

註:這篇文章翻譯自http://code.google.com/p/gosu/wiki/RubyTutorial

RubyTutorial

利用Ruby/Gosu製作一個小遊戲的教學

原始碼

完整遊戲的程式碼,以及需要的額外媒體檔案都在你下載的Gosu版本中(example/Tutorial.rb)

如果你是用RubyGems安裝Gosu,這個範例將會在你的gem資料夾中

如果你不是用RubyGems安裝Gosu,你必須將gosu.so(或是gosu.bundle)複製到範例資料夾中。接著執行tutorial.rb。

如果你沒有一個可以直接執行檔案的編輯器(TextMate, SciTE…),利用cd指令進入範例目錄,接著輸入ruby tutorial.rb。

1. 複寫Windows的callback

要建立一個完整的Gosu應用程式,最簡單的方法就是寫一個繼承自Gosu::Window的新class(參考文件以得到完整的介面解說),以下是一個最簡單的遊戲視窗的長相:

require 'gosu'

class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"
  end

  def update
  end

  def draw
  end
end

window = GameWindow.new
window.show
&#91;/sourcecode&#93;

該建構子利用super初始化了最基本的Gosu::Window類別。參數代表著這是一個640*480大小,非全螢幕(這就是false代表的意義),接著設定了視窗的標題,在設定之前標題都是空白的。

update()跟draw()都複寫了Gosu::Window的成員函數,在預設狀態下,update()每秒會被呼叫60次,主要的遊戲邏輯:物體移動,碰撞偵測等都應該在這裡處理。

draw()在每次視窗需要重繪的時候都會被呼叫,當FPS太低的時候也有可能會被跳過。函式內應該包含所有重繪整個視窗所需的東西,不應該有任何的遊戲邏輯在其中。

接著,隨著主要程式的進行,當一個視窗的show()函式被呼叫的時候,該視窗才會被建立。show()在視窗被關閉之前不會回傳。鏘鏘~現在你已經建立一個有著你自己設定的標題的黑色視窗了!
<h5>2. 使用影像</h5>

class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"

    @background_image = Gosu::Image.new(self, "media/Space.png", true)
  end

  def update
  end

  def draw
    @background_image.draw(0, 0, 0);
  end
end
&#91;/sourcecode&#93;

Gosu::Image#Initialize需要三個參數。首先,就像所有的媒體資源一樣,他需要被綁在一個視窗上(self)。所有的Gosu資源都需要一個視窗作為初始化以及內部參照的目標。第二個參數是影像檔案的名稱。第三個參數代表著是不是要用Hard border建立影像,關於Hard border請參考<a href="http://code.google.com/p/gosu/wiki/BasicConcepts" target="_blank">這裡</a>的解說

就像上一節提到的,視窗的draw()函數應該放著所有的繪圖處理。所以我們把顯示背景圖片的部份放在draw()中。參數也相當明確,這個影像將會被顯示在座標(0,0)的地方,第三個0代表著Z Order。(一樣參考<a href="http://code.google.com/p/gosu/wiki/BasicConcepts" target="_blank">這裡</a>,可以想像成影像之間的先後順序。)
<h6>玩家與移動</h6>
這是一個簡單的玩家類別:

class Player
  def initialize(window)
    @image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
    @x = @y = @vel_x = @vel_y = @angle = 0.0
  end

  def warp(x, y)
    @x, @y = x, y
  end

  def turn_left
    @angle -= 4.5
  end

  def turn_right
    @angle += 4.5
  end

  def accelerate
    @vel_x += Gosu::offset_x(@angle, 0.5)
    @vel_y += Gosu::offset_y(@angle, 0.5)
  end

  def move
    @x += @vel_x
    @y += @vel_y
    @x %= 640
    @y %= 480

    @vel_x *= 0.95
    @vel_y *= 0.95
  end

  def draw
    @image.draw_rot(@x, @y, 1, @angle)
  end
end

這有幾個東西要解釋:

  • Player#accelerate利用了offset_x跟offset_y這兩個函數,這兩個函數很類似sin跟cos的用途。舉例來說,如果某個東西朝著30度的方向移動了100個像素。那水平部份將會移動offset_x(30,100)個像素,而垂直部份將會移動offset_y(30,100)個像素。
  • 當讀取BMP檔案的時候,Gosu將會將0xFF00FF(非常醜的粉紅色)取代為透明色。
  • 注意draw_rot將影像的中央放在(x,y),而不是影像的左上角!
  • 玩家將會被顯示在z=1上,也就是在背景之上。待會我們會將這些數字取代為更好理解的東西。
  • 另外,參考RubyReference以了解所有的繪圖函式跟參數。
將玩家與視窗連接在一起
class GameWindow < Gosu::Window
  def initialize
    super(640, 480, false)
    self.caption = "Gosu Tutorial Game"

    @background_image = Gosu::Image.new(self, "media/Space.png", true)

    @player = Player.new(self)
    @player.warp(320, 240)
  end

  def update
    if button_down? Gosu::Button::KbLeft or button_down? Gosu::Button::GpLeft then
      @player.turn_left
    end
    if button_down? Gosu::Button::KbRight or button_down? Gosu::Button::GpRight then
      @player.turn_right
    end
    if button_down? Gosu::Button::KbUp or button_down? Gosu::Button::GpButton0 then
      @player.accelerate
    end
    @player.move
  end

  def draw
    @player.draw
    @background_image.draw(0, 0, 0);
  end

  def button_down(id)
    if id == Gosu::Button::KbEscape
      close
    end
  end
end&#91;/sourcecode&#93;

就像你所看到的一樣,我們引進了鍵盤跟搖桿的輸入!跟update()還有draw()類似,Gosu::Window提供兩個成員函數:button_down(id)跟button_up(id)可以被複寫,並且預設不做任何事情。我們這邊做的是當使用者按下ESC(完整的按鍵列表可以參考<a href="http://code.google.com/p/gosu/wiki/RubyReference">RubyReference</a>)時關閉視窗。當我們的事件只需要做一次的時候,直接在button_down內定義是很適合的方式,例如介面互動,跳躍或是輸入文字。然而當我們需要的動作會跨越很多個frame時,例如,按著某個按鈕來移動,就應該寫在update()中了。update()中只呼叫Player的移動函數。如果你執行到目前為止的程式碼,你就可以四處飛行了!
<h5>3. 簡易動畫</h5>
首先,我們要除掉Z Order那些神奇的數字,利用如下的常數取代:

module ZOrder
  Background, Stars, Player, UI = *0..3
end

什麼是動畫?一連串的圖片,所以我們用Ruby內建的Array來儲存他們(在真實遊戲中,很可能需要特別設計一個類別來處理特別的需求,但是我們目前先用最簡單的解法。)

讓我們引入這些會閃爍的星星當我們的主題。星星會在某個隨機的地方出現,並且不停播放動畫,直到玩家吃到他們。Star類別的定義很簡單:

class Star
  attr_reader :x, :y

  def initialize(animation)
    @animation = animation
    @color = Gosu::Color.new(0xff000000)
    @color.red = rand(255 - 40) + 40
    @color.green = rand(255 - 40) + 40
    @color.blue = rand(255 - 40) + 40
    @x = rand * 640
    @y = rand * 480
  end

  def draw
    img = @animation[Gosu::milliseconds / 100 % @animation.size];
    img.draw(@x - img.width / 2.0, @y - img.height / 2.0,
        ZOrder::Stars, 1, 1, @color, :additive)
  end
end

由於我們不想要每個星星都重新讀取一個自己的動畫,我們不能在建構子中做這件事情,而是由某個地方將動畫傳給他。

為了每100毫秒顯示星星不同的frame,Gosu::milliseconds回傳的值被我們除以100,然後取餘數得到對應的frame。然後額外加上我們在建構子中隨機取得的顏色。

現在讓我們加上一些程式碼來讓玩者可以從一個array中吃到星星:

class Player

def collect_stars(stars)
stars.reject! do |star|
Gosu::distance(@x, @y, star.x, star.y) < 35 end end end[/sourcecode] 現在我們擴充視窗以讀取星星的動畫、建立新的星星、讓玩家吃掉他們然後畫出剩下的星星。 [sourcecode language="ruby"]class Window < Gosu::Window def initialize super(640, 480, false) self.caption = "Gosu Tutorial Game" @background_image = Gosu::Image.new(self, "media/Space.png", true) @player = Player.new(self) @player.warp(320, 240) @star_anim = Gosu::Image::load_tiles(self, "media/Star.png", 25, 25, false) @stars = Array.new end def update ... @player.move @player.collect_stars(@stars) if rand(100) < 4 and @stars.size < 25 then @stars.push(Star.new(@star_anim)) end end def draw @background_image.draw(0, 0, ZOrder::Background) @player.draw @stars.each { |star| star.draw } end …[/sourcecode] 完成!現在你可以蒐集星星了!

4. 文字跟音效

最後,我們想要利用點陣字型來顯示出目前玩者的分數,並且在每次玩者吃到一個星星的時候發出一聲音效。視窗將會處理文字的部份,讀取一個20像素高的字型:

class Window < Gosu::Window def initialize … @font = Gosu::Font.new(self, Gosu::default_font_name, 20) end … def draw @background_image.draw(0, 0, ZOrder::Background) @player.draw @stars.each { |star| star.draw } @font.draw("Score: #{@player.score}", 10, 10, ZOrder::UI, 1.0, 1.0, 0xffffff00) end end[/sourcecode] 那Player類別要幹麼?沒錯,一個分數的紀錄,跟讀取音效,還有播放。 [sourcecode language="ruby"]class Player attr_reader :score def initialize(window) @image = Gosu::Image.new(window, "media/Starfighter.bmp", false) @beep = Gosu::Sample.new(window, "media/Beep.wav") @x = @y = @vel_x = @vel_y = @angle = 0.0 @score = 0 end … def collect_stars(stars) stars.reject! do |star| if Gosu::distance(@x, @y, star.x, star.y) < 35 then @score += 10 @beep.play true else false end end end end[/sourcecode] 就像你看到的一樣,讀取跟播放音效真是太簡單了!參考RubyReference來了解更多強大的播放音效方式:調整音量、位置、音調等等

就這樣!剩下的部份都取決於你的想像力。如果你無法想像這些東西如何拿來建立一個遊戲,你可以參考GosuUsers取得更多別的程式碼。

廣告

發表迴響

在下方填入你的資料或按右方圖示以社群網站登入:

WordPress.com Logo

您的留言將使用 WordPress.com 帳號。 登出 / 變更 )

Twitter picture

您的留言將使用 Twitter 帳號。 登出 / 變更 )

Facebook照片

您的留言將使用 Facebook 帳號。 登出 / 變更 )

Google+ photo

您的留言將使用 Google+ 帳號。 登出 / 變更 )

連結到 %s