書いた人:yhara
前編では、Ruby/SDL を利用して簡単なアクションゲームを作成しました。 後編では効果音や点数表示の実装など、よりゲームとしての完成度を高めていきたいと思います。
まだ Ruby/SDL をインストールしていない場合は、前編を参考に開発環境を整えてください。
それができたら、サンプルパック 2 をダウンロードし解凍してください。音声やフォントなどゲームに必要なファイルが入っています。(Ruby/SDLスターターキットにはほぼ同じデータが入っているのですが、title.png と game_over.png だけ入っていないのでダウンロードして image/ 以下にコピーしてください。)
前編の内容を実際に試してみた方なら、手元に main.rb、player.rb、items.rb という 3 つのファイルができているはずです。サンプルパック 2 にはこれらのファイルも同梱してあります。以下ではこれらのスクリプトを少しずつ改造して機能を付け加えていきます。
前編では、爆弾に当たったらすぐにゲームを終了するようになっていました。 これではゲームオーバーになるたびにゲームを起動し直さなくてはならず、面倒ですよね。 ゲームにタイトル画面を付け、ゲームオーバーになったらタイトル画面に戻るようにしてみましょう。
さて、「タイトル画面」「ゲーム画面」「ゲームオーバー画面」のように複数の場面 (シーン) を用意するにはどうすれば良いでしょうか?「メインループを複数用意する」というのが最も簡単なやり方ですが、この方法は似たようなコードをあちこちに書くはめになるのであまりお薦めしません。代わりに、ここでは別のやり方を考えてみます。
「タイトル画面」「ゲーム画面」「ゲームオーバー画面」のいずれのシーンでも、1 秒に 60 回画面を更新することは変わりません。違うのは 1 フレームごとに行う処理、act と render の内容だけです。となれば、これらをあらわすオブジェクトを用意してやれば良さそうですね。
ここでは TitleScene、GameScene、GameOverScene という 3 つのクラスを作り、それぞれに act と render というメソッドを定義することにします。act にはそのシーンでやりたいことを書きます。render にはそのシーンの画面を表示するコードを書きます。
ついでにもう一つ、シーンが始まったときに呼び出される start というメソッドも定義しています。
場面の切り替えを行うために、act の戻り値を以下のようにしています。
言葉で説明するより、実際にコードを見てもらった方が早いかも知れません。main.rb の class Input 以下を、以下のコードに置き換えてください。
次に、items.rb の class Items のところを以下のように書き換えてください。
ここで一度 main.rb を実行してみてください。タイトル画面で Enter を押すとゲームが始まります。爆弾に当たるとゲームオーバーなのは前回と同じです。ゲームオーバーになったら画像が表示され、一定時間後にタイトル画面に戻るはずです。
main.rb の一番最後にメインループがあります。前編ではここで player や items の act と render を呼んでいましたが、改造後は scene.act と scene.render だけを呼び出すようになっています。プレイヤーやアイテムを動かす処理は、GameScene の act と render に移動しています。
Items クラスにも一つだけ clear というメソッドを増やしています。何回ゲームをプレイしても GameScene や Items のインスタンスは最初に作った一つを使いまわすので、ゲームが開始するたびに Items も初期化してやらないといけないわけです (これをしないと、2 回目のゲームが始まった瞬間に爆弾に当たってゲームオーバーになってしまいます)。
次は BGM を鳴らしてみましょう。音楽があるだけで、雰囲気が全然違いますよ。
BGM として利用可能なフォーマットは以下のいずれかです。
サンプルパック 2 には MOD 形式の音楽ファイル (famipop3.it) が入っているので、これを鳴らしてみましょう。main.rb を変更して、
という風にします。
Ruby/SDL で音を鳴らすには、最初に初期化を行う必要があります。 まず、SDL.init の引数で SDL::INIT_AUDIO (または SDL::INIT_EVERYTHING) を指定します。 次に、SDL::Mixer.open で音声の初期化を行います。
main.rb の初期化部分を以下のように変更してください。
次に、SDL::Mixer::Music.load で音楽ファイルを読み込み、 SDL::Mixer.play_music で再生します。 引数には読み込んだ BGM と再生する回数を指定します。回数に -1 を指定すると無限ループになります。 停止には SDL::Mixer.halt_music を使います。 halt_music には引数がありません (一度に 2 つ以上の BGM を鳴らすことはできないからです)。
では、main.rb を BGM を鳴らすように書き換えてみましょう。音楽を鳴らすのはゲーム中だけなので、GameScene を書き換えます。
書き換えたら、main.rb を実行してみてください。正しく音は鳴りましたか?
さて、肝心の音楽をどこから手に入れるかですが、以下の 2 通りが考えられます。
自分で作る場合は DTM の知識が必要になりますが、作りたいゲームに合った曲を用意できるという利点があります。 MIDI なら MIDI シーケンサ (google 検索)、 MOD ならトラッカー (modplug tracker など) を使うことになるでしょう。MP3 や Ogg Vorbis は単に音声データを圧縮したものなので、wav 形式で保存できる音楽ソフトなら何でも使えます。
自分で作るのが難しければ、フリー素材を利用するという手もあります。 「音楽 素材」で検索すればいろいろなサイトが見つかると思います。作者の方に感謝しつつゲームに組み込みましょう。
ただ、フリー素材は利用に条件が課せられていることもあります (商用利用は不可、など)。事前によく確認しておきましょう。
BGM が付いたところで、もう一種類の音、効果音を付けてみましょう。
効果音として利用可能なフォーマットには Wave, AIFF, RIFF, Ogg, VOC がありますが、Wave 形式を使うのが一般的です。 効果音を非常にたくさん使う場合は、Ogg 形式に圧縮すればゲーム配布時のファイルサイズを抑えることができます。 (ただし、効果音をロードすれば元の Wave ファイルと同じだけのメモリが消費されるので、メモリ不足には注意してください。)
効果音を鳴らすには、最初に SDL::Mixer の初期化が必要です。これは BGM のときと同じです。
効果音を読み込むには SDL::Mixer::Wave.load を使います。
効果音を再生するには SDL::Mixer.play_channel を使います。 SDL::Mixer ではチャンネルという仕組みを利用して、複数の効果音を同時に鳴らせるようになっています。 play_channel の引数には再生に使うチャンネル番号、読み込んだ効果音、(再生回数 - 1)を指定します。
チャンネル番号を考えるのが面倒なときは、-1 を指定すると空いているチャンネルを適当に選んでくれます。 ただしチャンネル数には限りがあるので、1 秒に何十回も効果音の再生を始めるとチャンネルが足らなくなり、例外 SDL::Error が発生してしまいます。 これを避けるには、
など、何らかの工夫が必要になります。
では実際に効果音を鳴らしてみましょう。 サンプルパック 2 ではリンゴを取ったとき (get.wav) と爆弾に当たったとき (bom08.wav) の 2 種類の音を用意しています。どちらも「プレイヤーと物が接触したとき」に鳴らす音なので、当たり判定のところを書き換えるのが良さそうですね。
items.rb を以下のように書き換えてください。
書き換えたら、main.rb を実行してみてください。正しく効果音が鳴りましたか?
環境によっては、効果音が遅れて再生される場合があります。こんなときは、SDL::Mixer.open の引数でバッファサイズを小さくすると直ることがあります。
例えばバッファサイズをデフォルト (4096 バイト) の半分にするには以下のようにします。
効果音は単なる Wave ファイルなので、コンピュータにマイクを繋いで録音したり、音楽ソフトを使って自作することができます。
が、爆発音など自分で作るのが難しい音もあります。そういうものに関してはフリー素材を利用するのが良いでしょう。 「効果音」で検索すればいろいろなサイトが見つかると思います。作者の方に感謝しつつゲームに組み込みましょう。
ただ、フリー素材は利用に条件が課せられていることもあります (商用利用は不可、など)。事前によく確認しておきましょう。
サンプルパック 2 の爆発音は以下のサイトのものを使わせていただいています。ありがとうございます。
だいぶゲームらしくなってきましたが、ただ一つゲームと呼ぶには決定的に足りないものがあります。そう、得点 (スコア) です。次はスコア表示を付けてみましょう。
スコアを表示するには Ruby/SDL のフォント機能を使います。
フォントファイルには以下の形式が利用可能です。
それぞれどういうものか……の説明はあと回しにして、とりあえずスコア表示を実装してみましょう。 サンプルパック2 には boxfont2.ttf という TTF フォントが入っているので、これを使います。
TTF フォントの表示には、最初に初期化が必要です。
次に、SDL::TTF.open でフォントファイルを読み込みます。引数にはファイル名と文字のサイズを指定します。
フォントを描画するためには 3 種類のメソッドが用意されています。それぞれの特徴は、ごく大雑把に言えば以下のようになります。
基本的には、ゲーム中は高速に描画できる方がいいので draw_solid_utf8 を、タイトル画面は動きが少ないので draw_blended_utf8 を……といった感じで使い分けるのが良いでしょう。
SDL::TTF#draw_solid_utf8 の使い方は以下のような感じです。
スコアの計算方法にはいろいろなものが考えられますが、ここでは簡単に「リンゴを一つ取ったら 10 点」ということにしましょう。(別に 1 点でも良いのですが、10 点のほうがスコアの桁が大きくなってなんとなく気分がいいですよね。) また、ゲーム中の最高得点を「ハイスコア」として表示したいと思います。
必要な処理は
といったところでしょうか。
基本的には main.rb を書き換えていきますが、 (3) のところだけ items.rb を書き換える必要があります。 「リンゴを取る」処理が Items#act に書かれているからです。
Items#act を以下のように書き換えてください。配列を返り値にすることで、「爆弾に当たったかどうか」と「取ったリンゴの数」という 2 種類の値を返しています。
さて、ではこれを使って main.rb を書き換えてみましょう。まず、初期化のところに SDL::TTF.init を追加します。
次に、GameScene を以下のように書き換えます。initialize でフォントを読み込んで、render で文字を描画しています。
ここまでできたら、main.rb を起動してみてください。最初はスコアもハイスコアも 0 であること、リンゴを 1 個取るごとにスコアが 10 点増えること、最高得点を出すとハイスコアが更新されることを確認してください。
さて、今回はサンプルパック2 付属のフォントを使いましたが、ゲームの雰囲気によってはもっと丸い感じのフォントが欲しい……と思うこともあるでしょう。そういう時は「フリーフォント」で検索すれば、フリーで公開されている TTF フォントがいくつも見つかります。
が、ちょっと待ってください! TTF フォントの場合、「画像の一部に使うのは自由だが、.ttf ファイルそのものを再配布するのは不可」というライセンスになっているものが多くみられます。これでは、いい感じのフォントが見つかったとしてもゲームに組み込んで公開することができません。
そんな時に役に立つのがビットマップフォントです。
ビットマップフォントは、ASCII コード 0 から 255 までの全ての文字を横に並べた画像をフォントとして使用するものです。一度画像に変換してしまえば .ttf ファイルを再配布する必要はないので、多くのフリーフォントが使用可能になります。
TTF フォントからビットマップフォントを自動生成するツールを以下の URL で公開しています。どうぞご利用ください。
まず SDL::BMFont.open でフォントを読み込みます。フラグに SDL::BMFont::TRANSPARENT を指定すると、(画像のカラーキーのように) 文字の背景を透化することができます。
描画は SDL::BMFont#textout で行います。
単色のフォントの場合は、SDL::BMFont#set_color で文字の色を変えることができます。ただし open のときに SDL::BMFont::PALETTE を指定しておいてください。
その他のフォント形式についても少しだけ解説しておきます。
SFont はビットマップフォントと似ていますが、文字ごとに幅を変えられたり、半透明が使えるなどより高機能になっています。
SFont に関するより詳しい情報については Linux-games.com を参照してください。Linux-games.com では SFont のサンプル もいくつか公開されています。
また、TTF フォントから SFont を自動生成するツールを現在製作中です。以下の URL にて公開予定です。
SFont の読み込みは、ビットマップフォントと同じく SDL::BMFont.open を使います (フラグに SDL::BMFont::SFONT を指定すると SFont の読み込みになります)。SFont の描画には SDL::BMFont#textout を使います。
BDF フォントは Unix 系 OS を中心に利用されているフォーマットで、漢字・ひらがな・カタカナなど英数記号以外の文字を表示できるのが特徴です。 ノベルゲーム・アドベンチャーゲームなど、日本語の文章を表示するなら事実上唯一の選択肢でしょう。 1
BDF フォントの入手先については (X11 を中心とした) フリーの日本語ビットマップフォント一覧等が参考になります。
BDF フォントの表示には SDL::Kanji が必要です (Windows 版バイナリには最初から組み込まれています)。
BDF フォントを使用するには、まず SDL::Kanji.open にファイル名と文字のサイズを指定して読み込みを行います。次に、SDL::Kanji#set_coding_system で表示する文字列の文字コードを指定します。.rb の文字コードに合わせて、SJIS, EUC, JIS のいずれかを指定してください。
文字の表示には SDL::Kanji#put を使います。
現在の仕様では、一度ゲームを終了するとハイスコアは消去されてしまいます。 最後の仕上げとして、ゲーム終了時にハイスコアをファイルに保存し、次の起動時にも引き継げるようにしてみましょう。
セーブデータを簡単に保存する方法としてはテキストファイルや YAML 形式 などが考えられますが、ここでは Ruby の標準ライブラリである Marshal モジュールを使ってみます。Marshal は Ruby のオブジェクトをバイナリデータに変換するモジュールで、Ruby のほとんどのオブジェクトをファイルに保存することができます (保存できないものは IO オブジェクトなど。詳細は Marshal#dump の項を参照してください)。
Marshal は Ruby の標準ライブラリなので、特にインストールや require などは必要ありません。
あるデータをファイルに Marshal 形式で保存するには以下のようにします。
ここで、ファイルモードに「wb」を指定することに注意してください。Marshal 形式のデータはバイナリデータなので、ファイルモードに b を付けてバイナリモードにしないと Windows 上でデータがうまく読み書きできません。
逆にファイルから Marshal 形式のデータを読み込むには以下のようにします。
ここでもファイルモードに b を付けています。
では、実際にハイスコアの保存処理を書いてみましょう。 ハイスコアの読み込み・保存はゲームの開始時と終了時に行うので、main.rb のトップレベルに書くのが良さそうです。しかしハイスコアのデータは GameScene が持っているので、これを外からアクセスできるようにしないといけませんね。
ということで、まず GameScene の定義を以下のように書き換えます。
次に、セーブデータを表すクラスを定義します。 今回は保存するデータが単純なのでありがたみが薄いですが、「ランキングも保存したい」「プレイ時間も記録したい」など保存するデータが複雑になってくれば、クラスに分けておいた方が便利です。
これを使うと、ハイスコアの読み込みは以下のように書けます。main.rb の Scenes を定義しているところを以下のように書き換えてください。
また、ハイスコアの保存は以下のように書けます。main.rb の一番最後に以下を付け足してください。
ここまでできたら main.rb を起動し、ハイスコアがちゃんと保存されるか確認してみてください。
本稿では 2 回にわたり、Ruby/SDL でのゲーム製作について解説してきました。
ごく簡単なゲームだったので、ゲーマーな方には物足りなかったかも知れません。しかし本稿を読み終えたあなたなら、どこをいじればゲームを難しくできるかはご存知のはずです。:-) 爆弾の数を多くする? 落下速度を速くする? それとも、落ちてくる物の種類を増やす? 空を飛べるようにしてみるとか、色違いのキャラクターを用意して 2 人対戦なんてのも良いかも知れませんね。
何にせよソースコードがあなたの手にある以上、このゲームの行く末はあなたのアイデア次第です。お好きなように改造してみてください。
「ゲームを作ってみたい」と思っている方にとって、本稿が少しでも後押しになれば幸いです。
Windows の「MS ゴシック」など OS 固有の日本語フォントを使うという選択肢もありますが、他の OS で動かせなくなるのでおすすめしません。 ↩