突然ですが・・・
ruby2dの奴が生き返りました!
私、仕事の傍らキッズプログラミングスクールでRubyを使って子供たちにRubyの素晴らしさを日々啓蒙しているのですが、そのツールとして断固Rubyとruby2dを使う事にしております。
Rubyは子供にも書きやすいし、ruby2dはWSL環境でも動かせるので学習コストはもちろん、物理的コストがお安いのです。MacでもWSL環境化でも使えて、Rubyらしさを失わないシンプルで素晴らしいライブラリなので、子供たちにはちょっと小難しい、クラスや継承をゲームで遊びながら学べるという神ツールなのです。
そんなruby2dで、ちょっと授業の調べ物をしようと 公式ページ をみてみた所
ずっとメンテナンスされていなかったruby2dの公式ページが更新されている・・・しかもかわいい。
まさかと思って Github ものぞいてみると・・・
なんということでしょう!ruby2dのメジャーバージョンが変わっているではありませんか。
気がついたのが更新日翌日だったのでさらに大興奮です!
内容としては↑のSDL3対応が大きなトピックになります。
ということは、私が今まで塩漬けにしていた「子供が作ったゲームを公開する」という事が可能になってくるはず!!
Githubのアカウントさえ持てる年であれば無料で自作ゲームが公開できちゃうというなんとも有難い世の中
ちょっと触ってみたところ
一ヶ所目立つところで、メソッドの破壊的移行がありまして、
text = Text.new("hello")
text.text = "yamada"
これがなくなりまして、
text = Text.new("hello")
text.content = "yamada"
になってるようです。
確かにこれネーミング気になってましたが、まさかの過去資産書き直しw
ruby2d自体元々かなりプレーンなイメージだったので、もしかしたら他に何か便利になったものもあるかもしれませんね。
実際、まだ触ってませんが、Buttonクラスもできていて、
btn = Button.new(x: 0, y: 0, width: 100, height: 40, color: '#333')
btn.on(:mouse_down) { btn.color = '#555' }
btn.on(:mouse_up) { btn.color = '#333' }
btn.on(:click) { fire }
btn.on(:hover) { puts 'enter' }
btn.on(:hover_out) { puts 'leave' }
btn.on(click: :left) { puts 'left click' }
btn.on(click: [:left, :right]) { puts 'either click' }
こんな感じでイベントを仕込んだり、
既存のオブジェクトを第一引数に取る、カスタムイベントボタンを実装できるようになっています。
circle = Circle.new(x: 200, y: 200, radius: 30, color: 'tomato')
btn = Button.new(circle) { puts 'Circle clicked!' }
これがSpriteクラスでも使えると、色々と幅が広がりますね。
で、本題のwasm化ですが
現在はWindows、またはMacにのみ公式に対応とのことではありますが、びっくりするくらい簡単で、下準備としてまず
①emsdkのインストール
% git clone https://github.com/emscripten-core/emsdk.git ~/emsdk
% cd ~/emsdk
% ./emsdk install latest
% ./emsdk activate latest
% source ~/emsdk/emsdk_env.sh
これだけやっていれば良いです。
ただ、これはセッション単位で読ませる必要があるようですので、
source ~/emsdk/emsdk_env.sh
こんな感じで仕込んでおくのも良いでしょう。
あとは、
②Ruby4系が必須
とのことで、3系だとコンパイルが通せずアウトです。
確かに、以前のバージョンでもwasm化のコマンド自体はあったのですが、コケ倒してどうにもならない状態でした。
③require を使ったファイル連携は死ぬ
これは mruby の仕様上仕方がないので、最初に試す時は1ファイルで完結したもの・・例えば、
% mkdir ruby2d_kawa
% touch main.rb
で作業ディレクトリにmain.rbだけを置いて、
昔懐かし川下りゲーム(逆走Ver)
require "ruby2d"
# 川(背景)
river = Rectangle.new(width: Window.width, height: Window.height, color: "blue")
# ボート
boat = Rectangle.new(width: 60, height: 30, color: "brown")
boat.x = (Window.width - boat.width) / 2
boat.y = Window.height - boat.height * 2
# 岩(3つ)
rock_count = 3
rocks = rock_count.times.map do |i|
Circle.new(radius: 15, color: "gray", x: rand(0..Window.width), y: -i * 150)
end
status = :playing
# ゲームオーバー画面
message = [
Text.new("GAME OVER", size: 30, x: 220, y: 200),
Text.new("press key 'R' to restart", size: 20, x: 190, y: 260)
]
message.each { |text| text.remove }
on :key_down do |event|
if status == :playing
case event.key
when "left"
boat.x -= 20 if boat.x > 0
when "right"
boat.x += 20 if boat.x < Window.width - boat.width
end
end
if event.key == "r" && status == :game_over
status = :playing
boat.x = (Window.width - boat.width) / 2
rocks.each_with_index do |rock, i|
rock.y = -i * 150
rock.x = rand(0..Window.width)
end
message.each { |text| text.remove }
end
end
update do
if status == :playing
rocks.each do |rock|
rock.y += 4
if rock.y > Window.height
rock.y = 0
rock.x = rand(0..Window.width)
end
if boat.contains?(rock.x, rock.y)
status = :game_over
message.each { |text| text.add }
end
end
end
end
show
これは
% ruby2d build --web main.rb
を打つだけですんなりコンパイルできます。
ruby2d_kawa (main #%)% ruby2d build --web main.rb
◆ Building web
wrote build/web/app.html
wrote build/web/app.js
wrote build/web/app.wasm
wrote build/web/app.data
Run `ruby2d launch --web` to view
そうすると、起動コマンド打てと出てくるので、
ruby2d_kawa (main #%)% ruby2d launch --web
Serving at http://localhost:8080/app.html
Press Ctrl-C to stop.
これで自動でブラウザが開いて、localhost:8080で遊べます。
どうやら、ローカルで build/web/app.html をそのまま file:// で開いても動かない(ブラウザのセキュリティで .wasm の読み込みがブロックされる)ので、ローカル確認だけ簡単な HTTP サーバーが必要という流れのようです。
早い話、S3とかgithub.ioとかに
この静的ファイル部分をあげてしまえば誰でも遊べちゃうという事ですね。
これはゲームじゃなくても色々なツールに応用効きそうで夢がありますね。
じゃあ複数ファイルに跨った場合は?
はい。今回お伝えしたいのはここなのですが、
テトリスに似た何かを作ったとします。
当然ちょっとしたファイル数になります。
ruby2d_tetraminos/
├── app/
│ ├── main.rb
│ └── models/
│ ├── blocks/
│ │ ├── block.rb
│ │ └── block_map.rb
│ ├── game/
│ │ └── game.rb
│ ├── stage/
│ │ └── stage.rb
│ └── tetrominos/
│ ├── tetromino_base.rb
│ ├── tetromino_i.rb
│ ├── tetromino_j.rb
│ ├── tetromino_l.rb
│ ├── tetromino_o.rb
│ ├── tetromino_s.rb
│ ├── tetromino_t.rb
│ └── tetromino_z.rb
├── config.rb
├── settings.rb
├── Gemfile
└── Rakefile
これらをmainに集約してコーディングするのはちょっと現実的でないので、一旦コンパイル前の1ファイルへの集約を含めてタスクを用意します。
config.rb の中にはシンプルに呼び出し順を考慮た読み込みを書いていましたので、
require "ruby2d"
require "./settings"
require "./app/models/stage/stage"
require "./app/models/blocks/block"
require "./app/models/blocks/block_map"
require "./app/models/tetrominos/tetromino_base"
require "./app/models/tetrominos/tetromino_i"
require "./app/models/tetrominos/tetromino_j"
require "./app/models/tetrominos/tetromino_l"
require "./app/models/tetrominos/tetromino_o"
require "./app/models/tetrominos/tetromino_s"
require "./app/models/tetrominos/tetromino_t"
require "./app/models/tetrominos/tetromino_z"
require "./app/models/game/game"
これをRakefileに直読みさせるよう書きます。
task default: %w[game]
task :game do
ruby "app/main.rb"
end
task :wasm do
require "fileutils"
FileUtils.mkdir_p ".bundle"
files = [
"settings.rb",
"app/models/blocks/block.rb",
"app/models/blocks/block_map.rb",
"app/models/tetrominos/tetromino_base.rb",
"app/models/tetrominos/tetromino_i.rb",
"app/models/tetrominos/tetromino_j.rb",
"app/models/tetrominos/tetromino_l.rb",
"app/models/tetrominos/tetromino_o.rb",
"app/models/tetrominos/tetromino_s.rb",
"app/models/tetrominos/tetromino_t.rb",
"app/models/tetrominos/tetromino_z.rb",
"app/models/stage/stage.rb",
"app/models/game/game.rb",
]
bundle = %(require "ruby2d"\n\n)
files.each { |f| bundle += File.read(f) + "\n" }
main_body = File.read("app/main.rb").gsub(/^require.*\n/, "")
bundle += main_body
File.write(".bundle/bundle.rb", bundle)
puts "Bundled → .bundle/bundle.rb"
sh "ruby2d build --web .bundle/bundle.rb"
FileUtils.cp "web/index.html", "build/web/app.html"
puts "Template applied → build/web/app.html"
end
task :serve do
puts "Serving at http://localhost:8080/app.html"
sh "python3 -m http.server 8080 --directory build/web"
end
これで3つのコマンド使えます。
% rake game
% rake wasm
% rake server
で、当然HTMLなので、
こんな感じに整形する事も可能です。
ruby2dは教材としてすごくやりやすかったのですが、唯一発表の場のことだけは気になっていました。
ここまでできるようになると、是非とも子供達の作ったオリジナルとかを世に放ちたいですね。
ruby2dコミュニティや、コミッターの方々に感謝です。



