Skip to content

Instantly share code, notes, and snippets.

@myokoym
Last active August 25, 2024 23:59
Show Gist options
  • Save myokoym/7148859 to your computer and use it in GitHub Desktop.
Save myokoym/7148859 to your computer and use it in GitHub Desktop.
The Japanese translation of https://github.com/jlnr/gosu/wiki/Ruby-Tutorial (GosuのRubyチュートリアルの日本語訳)

Rubyチュートリアル

※この文書は、Ruby Tutorial · jlnr/gosu Wikiの日本語訳です。

翻訳

このチュートリアルの翻訳(中国語、スペイン語、フランス語)へのリンクについては、Homeページを参照してください。

ソースコード

このチュートリアルや他のサンプルのコードやアセット(画像など)はgosu-examples gemに含まれています。 gem install gosu-examples してから gosu-examples を実行するだけで試せます。

シンプルなゲームを書こう

1. ウィンドウのコールバックを上書く

すべてのGosuアプリケーションはGosu::Windowのサブクラスから開始されます。最小のGameWindowクラスはこのようになります:

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

コンストラクタは、Gosu::Windowベースのクラスを初期化します。ここで示されているパラメータが指定された場合、フルスクリーンでない640×480ピクセルのウィンドウを作成します。また、それまで空だったウィンドウのキャプションを変更しています。

update()draw()は、Gosu::Windowが持っているメソッドを上書きします。update()はゲームの主要ロジック(オブジェクトの移動、衝突判定など)が含まれるべきで、(デフォルトでは)毎秒60回呼び出されます。

その後(他の理由でウィンドウの再描画が必要になった場合にも)draw()が呼ばれますが、FPSが低下している場合は一定時間置きにスキップされる場合もあります。draw()には画面全体を再描画するためのコードのみが含まれるべきで、ロジックを含めてはいけません。

その下はメインプログラムです。ウィンドウはshow()メソッドが呼ばれてから作成され、ユーザーかコード自身によって閉じられるまで呼び出し元に返りません。(ジャジャーン!)今、あなたは、あなたがタイトルを選んだ小さな黒いウィンドウを持っています!

メインループの図解は、Window Main Loopのページに示されています。

2. 画像の使用

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

Gosu::Image#initializeは3つの引数を取ります。最初に、ほぼ全てのメディアリソースは、ウィンドウ(self)と繋げられます。Gosuの全てのリソースは初期化するためにウィンドウが必要なので、そのウィンドウへの内部参照を保持します。二番目に、画像ファイルのファイル名が与えられます。三番目の引数には、画像の境界をはっきりさせるかどうかを指定します。詳細はBasicConceptsを参照してください。

前の課題で述べたように、ウィンドウのdraw()メソッドは描画するための場所なので、背景画像もここで描画します。

引数は、ほぼ明白です。この画像は (0, 0) の位置に描画されます(三番目の引数はZ軸の位置です。再度、Basic Conceptsを参照してください)。

2.1 プレイヤーと動作

ここに簡単なプレイヤークラスを挙げます。

class Player
  def initialize(window)
    @image = Gosu::Image.new(window, "media/Starfighter.bmp", false)
    @x = @y = @vel_x = @vel_y = @angle = 0.0
    @score = 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

これについて、いくつか言うべきことがあります。

Angles in Gosu

  • Player#accelerateは、クラスメソッドのoffset_xoffset_yを利用しています。これらはsin/cosと同じように使えます。例えば、何かが30°の角度で100ピクセル移動した場合、それは水平にoffset_x(30, 100)、垂直にoffset_y(30, 100)移動したことになります。
  • BMPファイルを読み込むとき、Gosuは0xff00ff(fuchsia/magenta:みにくいピンク色)を透明なピクセルに置き換えます。
  • 注:draw_rotは (x, y) に画像の中心を置きます(左上ではありません!)。これはcenter_xcenter_yの引数によって制御できます。
  • (当然ながら)プレイヤーは、z=1、つまり背景の上に描画されます。この数値はいつでも変更できます。
  • また、全ての描画メソッドと引数については、rdocを参照してください。

2.2 プレイヤーとウィンドウの統合

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::KbLeft or button_down? Gosu::GpLeft then
      @player.turn_left
    end
    if button_down? Gosu::KbRight or button_down? Gosu::GpRight then
      @player.turn_right
    end
    if button_down? Gosu::KbUp or button_down? Gosu::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::KbEscape
      close
    end
  end
end

見ての通り、キーボードとゲームパッドの入力を取り込んでいます! update()およびdraw()と同様に、Gosu::Windowは上書き可能な2つのメソッドbutton_down(id)button_up(id)を提供しており、デフォルトでは何もしません。ここでは、ユーザーがESCを押すとウィンドウが閉じるようにしています。(あらかじめ定義されたボタン定数の一覧については、rdocを参照してください)。 button_down(id)button_up(id)でボタンが押されたことを検知している間は、ジャンプやタイプ、対話的なUIなどの単発イベントにするのが適切で、ボタンを押しながら移動するなどの複数のフレームにまたがった動作には適していません。そのような用途でプレイヤーの動作メソッドを呼び出すには、update()メソッドを使用します。この課題のコードを実行すれば、あなたは飛び回ることができるはずです!

3. 簡単なアニメーション

まず、Z軸の位置のマジックナンバーを取り除き、以下の定数に置き換えます。

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

アニメーションとは何でしょうか?それは、一連の画像です。我々は、それらを格納するために、Rubyの組み込み配列を使います。(実際のゲームでは、個々のニーズに合わせてクラスを作成しなければなりませんが、今は簡単な方法を採ります。)

この課題の中心的なオブジェクトである星たちを紹介しましょう。星たちは画面上のランダムな場所にどこからともなく現れ、プレイヤーが収集するまでアニメーションしながら生存します。星クラスの定義はかなり単純です。

class Star
  attr_reader :x, :y

  def initialize(animation)
    @animation = animation
    @color = Gosu::Color.new(0xff000000)
    @color.red = rand(256 - 40) + 40
    @color.green = rand(256 - 40) + 40
    @color.blue = rand(256 - 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, :add)
  end
end

個々の星について何度もアニメーションを読み込みたくないので、アニメーションの読み込みは星クラスのコンストラクタでは行えず、代わりにここではないどこかから渡すようにします。(このウィンドウはアニメーションを三段階で読み込みます。)

星のアニメーションの異なるフレームを100ミリ秒ごとに表示するため、Gosu::millisecondsによって返された時間を100で割って、フレーム数の剰余を取ります。この画像は、星の位置を中心にコンストラクタで生成されたランダムな色で付加的に描画されます。

では、プレイヤーに配列からできた星を絶えず収集するための簡単なコードを追加してみましょう。

class Player
  ...
  def score
    @score
  end

  def collect_stars(stars)
    if stars.reject! {|star| Gosu::distance(@x, @y, star.x, star.y) < 35 } then
      @score += 1
    end
  end
end

さあ、アニメーションを読み込み、新しい星を生成し、プレイヤーがそれらを収集し、そして残りのオブジェクトが描画されるようにウィンドウを拡張しましょう。

...
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
  ...

これで、星を集めることができるようになりました!

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

プレイヤーに残されたのは何でしょうか?正解は、スコアがカウントされたらサウンドを読み込んで再生することです。

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

ご覧のように、サウンドや効果音を再生するのはこの上なく簡単です!サウンドを再生するためのより強力な方法(ボリューム周りや、再生位置と速度)については、RDocを参照してください。

以上です!あとはあなたの想像力にかかっています。もし、ゲームを作るのに十分な想像ができないなら、Gosu Showcase boardの例を見てみましょう。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment