リファクタリング歓迎!

サンプルの仕様

  • 画面の大きさは 640×480
  • FPS は 30
  • ウィンドウのキャプションとして "Sprites" と表示する
  • スプライトを 200 個表示する
  • スプライトの初期位置はランダム
  • スプライトは斜めに移動する
  • スプライトは画面枠で跳ね返る
  • ESC キーを押すと終了する

スプライト画像

ruby.png
  • 256 色インデックス形式 PNG
  • 透明色あり

フルカラー (αチャンネル付き) は扱えないライブラリもあるため今回は使用しません。

αチャンネル付きが扱えないライブラリ:

  • MyGame?

実行イメージ

screenshot.png

共通で使う (予定の) コード

sprite.rb

スプライトの座標と速度を管理するクラスです。 update メソッドで更新します。

class Sprite
  MAX_X = 640 - 32
  MAX_Y = 480 - 32
  
  attr_reader :x
  attr_reader :y
  
  def initialize
    @x = rand(MAX_X)
    @y = rand(MAX_Y)
    @vx = rand(2) * 2 - 1
    @vy = rand(2) * 2 - 1
  end
  
  def update
    @x += @vx
    @y += @vy
    if @x < 0
      @x = -@x
      @vx = 1
    end
    if @y < 0
      @y = -@y
      @vy = 1
    end
    if MAX_X <= @x
      @x = -(@x - MAX_X) + MAX_X
      @vx = -1
    end
    if MAX_Y <= @y
      @y = -(@y - MAX_Y) + MAX_Y
      @vy = -1
    end
  end
end

その他

  • Ruby 1.8.6 を用いました。
  • トップレベル include は使いません。ライブラリで定義されているクラス、モジュールとそうでないクラス、モジュールを見分けやすくするためです。

Miyako

Version 1.5.0a

  • SGE (SDL の描画ライブラリ) が必須のようです。
  • API リファレンスはありましたが、クラス、メソッドが豊富な分、それに相応するサンプルの量が足りないと感じました。
  • Miyako.main_loop メソッドを使用したサンプルはありませんでした (Scene クラスを使用した例はあります)。
  • Miyako::Sprite クラスの生成に使用する画像 (SDL_Surface オブジェクト) は、同じファイル名を指定すれば共有されるようです。この点は仕様として明記されてはいません(Miyako1.5.0aでは、この仕様をSprite.newメソッドの解説として追加しました)。
require "Miyako/miyako"
require "Miyako/idaten_miyako"
require "sprite"

# Sprite の配列を生成する。
sprites = Array.new(200){ Sprite.new }

# Miyako::Sprite の配列を生成する。 sprites のスプライトと一対一対応する。
msprites = Array.new(sprites.size) do

  # Miyako::Sprite オブジェクトを生成する。
  # デフォルトでは非表示なので show メソッドを呼ぶ。
  # show メソッドは self を返す。
  Miyako::Sprite.new(:filename => "ruby.png").show
end

# ウィンドウのキャプションを設定する。 
Miyako.setTitle("Sprites")

# FPS を設定する。
Miyako::Screen.fps = 30

# メインループ
Miyako.main_loop do

  # ウィンドウが閉じられるか ESC キーが押されていたら終了する。
  break if Miyako::Input.quit_or_escape?

  # [[Sprite オブジェクト, Miyako::Sprite オブジェクト],
  #  [Sprite オブジェクト, Miyako::Sprite オブジェクト], ...]
  # という配列を生成し、一つ一つに対して処理を行う。
  sprites.zip(msprites).each do |sprite, msprite|

    # Sprite オブジェクトを更新する。
    sprite.update

    # Miyako::Sprite オブジェクトの座標を設定する。
    msprite.move_to(sprite.x, sprite.y)
  end
end

【補足1】round_rev2メソッドを使った例

sample.rbをインクルードしてないことに注目!

require "Miyako/miyako"
require "Miyako/idaten_miyako"

# 移動量 の配列を生成する。
vs = Array.new(200){ [rand(2)*2-1,rand(2)*2-1] }

# Miyako::Sprite の配列を生成する。 sprites のスプライトと一対一対応する。
msprites = Array.new(vs.size) do

  # Miyako::Sprite オブジェクトを生成する。
  # 位置をランダムに設定する
  # roundメソッドは相対位置の移動メソッドだが、初期の位置は(0,0)なので問題は無い
  # デフォルトでは非表示なので show メソッドを呼ぶ。
  # show メソッドは self を返す。
  spr = Miyako::Sprite.new(:filename => "ruby.png")
  spr.round(rand(640), rand(480))
  spr.show
end

# ウィンドウのキャプションを設定する。 
Miyako.setTitle("Sprites")

# FPS を設定する。
Miyako::Screen.fps = 30

# メインループ
Miyako.main_loop do

  # ウィンドウが閉じられるか ESC キーが押されていたら終了する。
  break if Miyako::Input.quit_or_escape?

  # [[Array オブジェクト, Miyako::Sprite オブジェクト],
  #  [Array オブジェクト, Miyako::Sprite オブジェクト], ...]
  # という配列を生成し、一つ一つに対して処理を行う。
  vs = vs.zip(msprites).map do |v, msprite|
    # Miyako::Sprite オブジェクトの座標を更新する
    # (更新された移動量がround_rev2メソッドの返却値となり、更新されたvs配列の新しい要素となる)。
    msprite.round_rev2(*v)
  end
end

【補足2】補足1をSceneモジュールで表現した例

Miyakoでは、ゲームやシナリオの構成をシーン単位(Sceneモジュールをmixinしたクラス)で管理できる。 また、これらのシーン全体をストーリー(Storyクラス)で管理(実行管理)する。

require "Miyako/miyako"
require "Miyako/idaten_miyako"

# ウィンドウのキャプションを設定する。 
Miyako.setTitle("Sprites")

# FPS を設定する。
Miyako::Screen.fps = 30

class Hikaku
  # Sceneモジュールをmixinして、このクラスはシーンクラスだとStoryクラスのインスタンスに認識させる
  include Miyako::Story::Scene

  def init # 初期化メソッド
    # 移動量 の配列を生成する。
    @vs = Array.new(200){ [rand(2)*2-1,rand(2)*2-1] }

    # Miyako::Sprite の配列を生成する。 sprites のスプライトと一対一対応する。
    @msprites = Array.new(@vs.size) do

      # Miyako::Sprite オブジェクトを生成する。
      # 位置をランダムに設定する
      # roundメソッドは相対位置の移動メソッドだが、初期の位置は(0,0)なので問題は無い
      spr = Miyako::Sprite.new(:filename => "ruby.png")
      spr.round(rand(640), rand(480))
    end
  end

  def setup # シーン開始時処理メソッド
    # スプライトを表示させる(デフォルトでは非表示なので show メソッドを呼ぶ)
    @msprites.each{|spr| spr.show }
  end

  def update # シーン更新メソッド
    # Input.updateとScreen.updateはupdateメソッドの前後で呼ばれているので
    # 明示的に呼び出す必要がない

    # ウィンドウが閉じられるか ESC キーが押されていたら終了する。
    # updateの戻り値をnilにすると、ストーリーが終了する
    return nil if Miyako::Input.quit_or_escape?

    # [[Array オブジェクト, Miyako::Sprite オブジェクト],
    #  [Array オブジェクト, Miyako::Sprite オブジェクト], ...]
    # という配列を生成し、一つ一つに対して処理を行う。
    @vs = @vs.zip(@msprites).map do |v, msprite|
      # Miyako::Sprite オブジェクトの座標を更新する
      # (更新された移動量がround_rev2メソッドの返却値となり、更新された@vs配列の新しい要素となる)。
      msprite.round_rev2(*v)
    end

    return @now # @now(=現在実行しているシーンクラス名)を返すと、シーンの更新を繰り返す
    # シーン移動を行うときは、移動先シーンクラス名を戻り値として指定する
  end

  def final # シーン終了時処理メソッド
    # スプライトの表示をやめる(このサンプルでは必要無いが、習慣的に付けておく)
    @msprites.each{|spr| spr.hide }
  end
end

# シーンのObserver、Storyクラスのインスタンス(ストーリー)を作成
hikaku_sample = Miyako::Story.new
# シーンHikakuからストーリーの実行を開始する
hikaku_sample.run(Hikaku)

MyGame?

Version 0.9.1

  • 透明色を指定すると、画像内の同じ色すべてがそうなってしまいます。 TransparentImage? オブジェクトは左上点が自動的に透明色となりますが、今回用いた画像はそこの色とたまたま同じである色が全て透明色扱いとなってしまいます。
  • ウィンドウのキャプションの指定方法はない?
  • API リファレンスは、すべてのクラス、メソッドについてのリファレンスが存在します。書籍もあるので使い方には困らないと思われます。
require "mygame/boot"
require "sprite"

# Sprite オブジェクトの配列を生成する。
sprites = Array.new(200){ Sprite.new }

# 画像 TransparentImage オブジェクトを生成する。
# 今回左上点がたまたま透明色だったので、 Image オブジェクトでなく
# TransparentImage オブジェクトがそのまま使える。
image = TransparentImage.new("ruby.png")

# メインループ。引数は FPS を表す。
# ウィンドウが閉じられた場合は自動的に終了する。
# 画面は毎フレームリセットされる。
main_loop(30) do

  # ESC キーが押された場合終了する。
  exit if key_pressed?(Key::ESCAPE)

  # Sprite オブジェクトそれぞれについて処理する。
  sprites.each do |sprite|

    # Sprite オブジェクトを更新する。
    sprite.update

    # 画像の座標を設定する。
    image.x, image.y = sprite.x, sprite.y

    # 画像を画面に対して描画する。
    image.render
  end
end

Ruby/SDL

Version 2.0.1

  • FPS の管理は自力で行う必要があります。
  • リファレンス、サンプル、使用実績が豊富です。
  • SDL の関数に対応させるように Ruby のメソッドが定義されているので、一部オブジェクト指向らしくない箇所があります (SDL.blit_surface メソッドなど)。
require "sdl"
require "sprite"

# Sprite オブジェクトの配列を生成する。
sprites = Array.new(200){ Sprite.new }

# SDL の初期化処理を行う。
SDL.init(SDL::INIT_VIDEO)

# ウィンドウのキャプションを設定する。
SDL::WM.set_caption("Sprites", "")

# 画面を初期化する。
flags = SDL::SWSURFACE | SDL::SRCALPHA | SDL::DOUBLEBUF
screen = SDL.set_video_mode(640, 480, 32, flags)

# スプライト画像を生成する。
# display_format メソッドで、画像のフォーマットを画面のそれに合わせる。
image = SDL::Surface.load("ruby.png").display_format

# 1 フレームあたりにかかる秒数
interval = 1 / 30.0

# メインループ
loop do

  # 現在の時間を取得し、秒単位に直す。
  loop_start = SDL.get_ticks / 1000.0

  # キューにたまっているイベントを処理する。
  while event = SDL::Event2.poll

    # event が SDL::Event2::Quit オブジェクトならば終了する。
    # このイベントはウィンドウを閉じようとしたときに発生する。
    exit if SDL::Event2::Quit === event
  end

  # 入力状態を更新する。
  SDL::Key.scan

  # ESC キーが押されていたら終了する。
  exit if SDL::Key.press?(SDL::Key::ESCAPE)

  # 画面を黒色に塗りつぶす。
  screen.fill_rect(0, 0, 640, 480, [0, 0, 0])

  # 各 Sprite オブジェクトに対して処理を行う。
  sprites.each do |sprite|

    # Sprite オブジェクトを更新する。
    sprite.update

    # 画面 (screen) にスプライト画像 (image) を描画する。
    SDL.blit_surface(image, 0, 0, 0, 0, screen, sprite.x, sprite.y)
  end

  # 画面のバッファを交換する。
  screen.flip

  # 現在の時刻を取得し、秒単位に直す。
  loop_end = SDL.get_ticks / 1000.0

  # 1 フレームにかかる予定の時間 (1/30 秒) から、実際にかかった時間 (loop_end - loop_start) を差し引いた分だけスリープする。
  # 予定の時間よりも時間がかかりすぎた場合は何もしない。
  if 0 < (d = interval - (loop_end - loop_start))
    sleep(d)
  end
end

Star Ruby

Version 0.3.0

  • API リファレンスはありますがサンプルが少ないです。
require "starruby"
require "sprite"

# Sprite オブジェクトの配列を初期化する。
sprites = Array.new(200){ Sprite.new }

# スプライトに用いる画像を初期化する。
texture = StarRuby::Texture.load("ruby.png")

# メインループ。ウィンドウのキャプション、 FPS も指定する。
# ウィンドウが閉じられたら自動的に終了する。
StarRuby::Game.run(640, 480, :title => "Sprites", :fps => 30) do |game|

  # ESC キーが押されていたら終了する。
  break if StarRuby::Input.keys(:keyboard).include?(:escape)

  # 画面を初期化する。
  game.screen.clear

  # 各 Sprite オブジェクトに対して処理を行う。
  sprites.each do |sprite|

    # Sprite オブジェクトを更新する。
    sprite.update

    # 画面 (game.screen) に対してスプライト画像 (texture) を描画する。
    game.screen.render_texture(texture, sprite.x, sprite.y)
  end
end

DXRuby

Version 1.0.0

  • この中では唯一DirectX使用でWindows専用です。
require "dxruby"
require "sprite"

# Sprite オブジェクトの配列を生成する。
sprites = Array.new(200){ Sprite.new }

# Imageオブジェクトを作成
image = Image.load("ruby.png")

# ウィンドウのキャプションを設定する。 
Window.caption = "Sprites"

# fpsを設定する。
Window.fps = 30

# メインループ。
# ウィンドウが閉じられた場合は自動的に終了する。
# 画面は毎フレームリセットされる。
Window.loop do

  # ESC キーが押されたら終了する。
  exit if Input.keyPush?(K_ESCAPE)

  # Sprite オブジェクトそれぞれについて処理する。
  sprites.each do |sprite|

    # Sprite オブジェクトを更新する。
    sprite.update

    # 画面に対してスプライト画像(image)を描画する。
    Window.draw(sprite.x, sprite.y, image)
  end
end

コメント

  • さて次はどれを書こう -- 星一 2008-07-16 (水) 06:43:58
  • どれが良いのか選ぼうとしている人は、Rubyに付いて余り知らないかも知れません -- 9? 2008-07-16 (水) 15:54:37
  • どんな事をしているのかコメントを入れた方がよいかと思います -- 9? 2008-07-16 (水) 15:55:16
  • どのレベルまで合わせるかなどを考えるときりがないのですが。○○の行の意味が分からんから教えろ、などはコメントによろしくお願いします。 (あとは誰かが解説をつけてくれることをひそかに期待) -- 星一 2008-07-16 (水) 19:57:18
  • 自分はソースは読めるのですが、意外と処理時間やメモリ消費量の比較をみれたらうれしいと思いました。大体SDLとかだから変わらんかな? -- 2008-07-17 (木) 04:27:58
  • メモリ使用量と、あと処理時間は全部 30 FPS であわせているので、 CPU 使用率でしょうか。このサンプルだと差異はほとんど出ないと思いますが。 -- 星一 2008-07-17 (木) 04:42:46
  • rubyでゲームを作る人を増やす為に作っているんだと思っていました。 これから学ぼうとする余りrubyを知らない人を対象にするのが普通だと思うのですが。  -- 9? 2008-07-17 (木) 07:44:42
  • リファレンスの充実やサポートやらの情報、インストールから実際にコードを書くまでの手間など 評価に入れると良いと思います 差のないサンプルではどれでも良いと言う事になってしまいます 比較していないのと同じです αチャンネルが使えないのはどれなんですか?  -- 9? 2008-07-17 (木) 07:58:18
  • 星一さんが管理人さんですか?http://ruby-game-dev.org/index.php?%E3%83%A9%E3%82%A4%E3%83%96%E3%83%A9%E3%83%AA などのページに「扱えるフォーマットやライセンス」などの情報を追記してもかまいませんか? -- 2008-07-17 (木) 12:37:34
  • 客観的評価は難しいですが、おいおいそういう情報も書いていきたいと思います。 > 9 さん -- 星一 2008-07-17 (木) 20:09:27
  • ↑↑私が管理人です。了解しました。が、手伝ってくださるとさらに助かります。 -- 星一 2008-07-17 (木) 20:10:16
  • ↑↑↑おっと、「追記してもかまいませんか」を「追記してくれ」と読み間違えました。失礼しました。追記してくださって全然構いません。助かります。 -- 星一 2008-07-17 (木) 20:13:25
  • Star Rubyでサンプル実行するとメモリが食われ続けるのはバグですか? -- 2008-07-19 (土) 05:50:55
  • Windows (Ruby-mswin32) で実行しましたが、メモリ使用量は食われ続ける (単調増加する) ことなく、増減しました。ご使用の環境をお教え願えますか。 -- 星一 2008-07-19 (土) 17:56:35
  • WindowsXP(HE)SP3,Athlon64x2(Dual Core 4200+), MEM1.0GB, ruby-1.8.7(mswin32), star ruby 0.3.0, UTF8N です。 -- 2008-07-19 (土) 23:31:35
  • メモリ使用量が増え続けてずっと減らないってことですよね? どれくらいの間動作させました? -- 星一 2008-07-20 (日) 00:08:39
  • こちらは Windows XP (HE, SP2)、 Intel Pentium M 1.6GHz, Mem 512MB, Ruby 1.8.6, Star Ruby 0.3.0, UTF8N です。メモリ使用量は徐々に増えますが、大体一分程度経つとガクッと減って元に戻ります。 -- 星一 2008-07-20 (日) 00:10:43
  • あ、30秒くらいたつと戻って止まりますね。ご迷惑かけました。 -- 2008-07-20 (日) 01:13:13
  • よかったー。了解です。 -- 星一 2008-07-20 (日) 02:10:17
  • Miyakoのroundメソッド使った例とScene使った例を追加しました。 -- サイロス? 2008-07-21 (月) 19:18:17
  • TODO: インスコ手順を書く -- 星一 2008-07-22 (火) 03:59:59
  • どうもどうも > サイロスさん -- 星一 2008-07-22 (火) 04:00:12
  • RubyとPythonにてるから、PyGame?の層のユーザーなどを取り込みたいですのう -- だいご? 2008-07-23 (水) 14:19:01
  • アルファベット順かなとか思いつつ、一番上に入れるのはオコガマシイ気がしたから一番下にDXRuby追加しときました。 -- mirichi? 2009-05-15 (金) 20:46:35


添付ファイル: filescreenshot.png 518件 [詳細] fileruby.png 486件 [詳細]