Rubyで始めるゲームプログラミング - DXOpal編 -
初稿:2018-02-11
はじめに
本稿ではDXOpalを使ってブラウザで動くゲームを作ってみます。Rubyでこんなこともできるんだ!と思ってもらえれば幸いです。
DXOpalとは
DXOpalは筆者が作っている、Rubyでブラウザ用ゲームを作るためのライブラリです。
DXOpalの「DX」はDXRubyから来ています。DXRubyはRubyでWindows用ゲームを作るためのライブラリです(このRubyist Magazineにも記事がありましたね)。
DXOpalの「Opal」はRubyのコードをJavaScriptに変換してくれるソフトウェアです。DXOpalは内部でOpalを利用しています。
DXOpalは、DXRubyの命令を「だいたいそのまま」ブラウザに移植したものです。そのため、DXRubyのリファレンスを見れば使い方はだいたい同じです。もしDXRubyの命令でDXOpalで動かないものがあったら、githubかtwitterで教えてください。
準備
DXOpalのインストール
Rubyをインストール後、RubyGemsからDXOpalをインストールできます。
インストールするとdxopalコマンドが使えるようになります。dxopal initで、カレントディレクトリにファイルの雛形ができます。
initしたあと、dxopal serverコマンドを実行するとWebサーバが起動します。
ブラウザで http://localhost:7521/index.html を開くと、以下のように表示されるはずです。
サンプルパックのダウンロード
本稿で使う画像と効果音をまとめたものを http://route477.net/files/rubima_dxopal.zip に置きました。これを展開するとimagesとsoundsというディレクトリができるので、index.htmlと同じところに移動させてください。以下のような感じです。
画面を出してみよう
では早速、初めてのDXOpalアプリケーションを書いてみましょう。
main.rb というファイルがあるので、これをテキストエディタで開き、中身をいったん全部消して、以下のように書き換えてください。
ブラウザをリロードすると、真っ黒な四角だけの画面に変わったはずです。
変わらなかった場合は、ブラウザが古いデータをキャッシュしているかもしれません。以下の手順で、一時的にキャッシュさせないようにしましょう。
何か描いてみよう
真っ黒なウィンドウを出すだけでは寂しいので、何か描いてみましょうか。
main.rb を以下のように書き換えて保存してください。 (以下では、前のスクリプトから変更する部分にはコメントを付けてあります。 スクリプトを写すときにはまず「#」を探してみてください。)
空と大地が表示されましたか?:-)
画像を表示してみよう
ゲームには主人公が必要ですよね。画像を表示してみましょう。imagesというディレクトリにサンプル画像 (player.png) が入っていることを確認してください。
実行すると、地面の上にキャラクターが表示されます。少しゲームらしくなってきました。
画像を動かしてみよう
次はビットマップ画像を動かしてみましょう。 キャラクターを少しずつ位置をずらしながら描画することで、パラパラマンガのように絵を動かすことができます。
このように、ゲームプログラミングでは
- キーボードやジョイパッドの入力を受け付ける (入力)
- キャラクターの座標を少し動かす (移動)
- キャラクターを描画する (描画)
という手順を何度も繰り返すことでゲームを進めていきます。この「入力→移動→描画」1回分を1フレームと呼びます。
DXOpalでは基本的に1秒60フレームです。(参考)
キーボードから操作できるようにしよう
次はキーボードの矢印キーでキャラクターが左右に移動するようにしてみましょう。
ブラウザをリロードして、カーソルキーの左右を押すとキャラクターが動くはずです。(カーソルがアドレスバーにあったりすると動かないかもしれません。ページ内を一度マウスでクリックしてみてください。)
ところで、上のプログラムだと画面の端にたどりついてもキャラクターが止まらず、画面外に隠れてしまうはずです。if式の条件を以下のようにすると、左右に行きすぎないようになります。
Spriteクラスを使ってみよう
さて、主人公の次は敵キャラ出して、アイテム出して……と行きたいところですが、変数名に「x」を使っているのが ちょっと気になります。 例えば敵キャラを出すなら、プレイヤーの座標は player_x、敵キャラの座標は enemy_x のように改名しないといけないですよね。 さらにアイテムの座標も……と考えると、似たような変数がたくさんあって混乱してしまいそうです。
Rubyはオブジェクト指向言語なので、こういうときはクラスを作ります。特にDXRubyではゲームの各要素はSpriteクラスを継承しておくと、当たり判定が簡単に実装できたりして便利です。
動作としては同じですが、機能を増やしていくための土台ができました。
Spriteクラスはx座標、y座標、画像を持ち、player.drawのようにしてdrawメソッドを呼ぶとx座標、y座標の場所に画像が表示されます。これらのデータはPlayerクラスの内部からはself.x, self.y, self.imageのようにしてアクセスできます。
Spriteクラスを使うときは、移動などの更新処理はupdateというメソッドに書くことになっています。(Sprite.updateでまとめて呼べたりします)
アイテムを降らせてみよう
主人公だけでは寂しいので、他の物も描画してみましょう。imagesディレクトリにりんごと爆弾の絵があるので、「爆弾を避けつつリンゴを集める」ゲームにしてみましょうか。まずは、images/apple.pngにりんごの絵があるのでそれを使います。
アイテムを表すItemクラスと、アイテムの生成・削除を行うItemsクラスを作りましょう。
こんな感じになったでしょうか。
ItemクラスはPlayerクラスと同様に、Spriteクラスを継承しています。
Itemsクラスはアイテムの個数を管理するクラスで、Spriteの機能は特に使わないため普通のクラスにしています。ただし、更新を行うメソッドはupdate、描画を行うメソッドはdrawのように、名前だけ合わせています。同じことをするメソッドは同じ名前にしたほうが分かりやすいですからね。
Itemクラスのupdateメソッドでは、y座標を少しずつ増やすことでアイテムの落下を実装しています。y座標がWindow.heightより大きくなったら、画面外に出たということなので、vanishメソッドを呼んでいます。vanishを呼ぶと、Spriteオブジェクトのvanishフラグが立ちます。
vanishフラグは、Sprite.cleanと組み合わせて使います。Spriteクラスには、Spriteオブジェクトの配列を渡して一括で操作するメソッドがいくつかあります。(以下ではSpriteオブジェクトのことを、単に「スプライト」と呼びます)
アイテムを二種類にしよう
次はアイテムの種類を増やしてみましょう。images/bomb.pngに爆弾の絵があるのでそれを使います。
アイテムの絵を変えるのはどうしましょうか。一番単純なのはItemクラスにフラグを持たせる方法ですね。絵を変えるだけならそれでもいいですが、今回はりんごの方は「当たってもいいアイテム」、爆弾は「当たってはいけないアイテム」と、違う動作をさせたいので、別々のクラスにしておきます。とはいえ落下などの基本的な動作は同じなので、Itemクラスを継承してAppleクラスとBombクラスを作ります。
りんごと爆弾がまぜこぜに発生するよう、Itemsクラスを修正しています。
当たり判定を付けてみよう
上から落ちてきたものがすり抜けてしまうのではゲームになりませんね。次は当たり判定を付けて、
- リンゴに当たったらスコアが増える
- 爆弾に当たったらスコアが0になる
という風にしてみましょう。
最初にGAME_INFOという定数を用意しています。今はスコアしか入れていませんが、あとでゲームのいろいろな状態を持たせるために使います。
Spriteクラスは当たり判定の機能を持っています。collision=メソッドを呼ぶことで当たり判定が設定されます。当たり判定の形状は点、円、長方形、三角形の4種類があり、配列の長さによって指定します。
今回はプレイヤー、リンゴ、爆弾のいずれも円で当たり判定を指定することにしました。絵に対して厳密ではありませんが、そのほうがゲームとしては面白く感じることもあります。特に加点アイテムは当たり判定を広めに、減点アイテムは少し狭めにしておくと、気持ちいいゲームになります。
collision=で当たり判定を設定したあとは、Sprite.checkというメソッドでスプライト同士が衝突したかをチェックできます。衝突している場合、衝突されたオブジェクトのhitメソッドが呼ばれます。
Sprite.checkは他にもいろいろな機能があるので知りたい場合はマニュアルを見てください。
効果音を鳴らしてみよう
DXOpalにはWebAudioを使って効果音を鳴らす機能があります。アイテムに当たったときに音を鳴らすようにしてみましょう。
効果音は画像と同じように、Sound.registerで名前とファイル名を宣言します。そうするとWindow.load_resourcesの中で「Sound[名前]」という形でアクセスできるようになります。
タイトル画面を付けてみよう
だいぶゲームらしくなりましたね。最後の仕上げとして、タイトル画面とゲームオーバー画面を作ってみましょう。
GAME_INFOに:scene (シーン)という項目を追加しました。今回は:title (タイトル画面)、:playing (ゲーム中)、:game_over (ゲームオーバー画面)という3つのシーンを用意しました。
Window.loopでGAME_INFO[:scene]によって別々の処理をしています。GAME_INFO[:scene]にシーン名を代入することで、シーンが切り替わります。最初はシーン:titleで、スペースキーが押されたら:playingになって、爆弾に当たったら:game_overになります。ゲームオーバー画面でスペースキーを押すと:playingに戻ります。(この辺は好みで、タイトル画面に戻るようにしても良いでしょう)
余談
ここまでで今回のゲームはいったん完成とします。以降では補足として、ゲームがもっと大きくなったときのためのヒントをいくつか紹介します。
メイン部分をクラスにする
上ではWindow.load_resourcesの中にゲーム本体の処理を書いていましたが、規模が大きくなるとload_resourcesの中が長くなりすぎて大変かもしれません。こういうときは、ゲーム本体を表すクラスを作るという方法があります。以下は例です。
こうしておけば、シーンが増えてrunメソッドが長くなっても、メソッドを分割することで整理できます。
ファイルを分割する
今回はmain.rbに全てのプログラムを書きましたが、クラスが増えてくるとこの方法では大変です。main.rbが長くなってきたら、クラスごとにファイルを分けるのが良いでしょう。DXOpalではrequire_remoteでファイルをロードすることができます。例えばPlayerクラスをplayer.rbというファイルに切り出した場合は、main.rbに以下のように書くとplayer.rbをロードできます。
(通常のRubyのrequireと違い、「.rb」は省略できません。)
デバッグする
Rubyのプログラムをデバッグするときは「p」メソッドをよく使います。pメソッドはOpal/DXOpalでも使えます(開発者コンソールに表示されます)が、ゲームプログラミングでは同じ処理が1秒に60回実行されたりするので、この方法だと表示が出すぎて困ることがあります。
そこでDXOpalでは、「p_」というメソッドを用意しています。p_にハッシュを渡すとその内容が開発者コンソールに出力されますが、10回以上実行した場合はそれ以上出力しなくなります。例えば、Itemクラスのupdateメソッドに以下の行を書いてみてください。
おわりに
実は本稿は2007年のRubyist Magazine記事のリライト版なのでした。
- yhara(原 悠)
- 島根県在住のRubyプログラマ。滋賀から松江に引っ越してそろそろ10年になります。