DXRuby Advent Calendar 2015も6日目になりました。本日は、しのかろがお送りします。
前日はあおいたくさんの記事でした。あおいたくさんには昨年のDXRuby Advent Calendaで大変お世話になりました。(※詳細は今年の記事、8の項目参照)。あの時は私がGitの使い方に不慣れであった事もあり、ご迷惑をおかけしたと思います。リファクタリングのお付き合い、ありがとうございました。
デジゲー博とはインディー・ゲームの展示発表イベントです。インディー・ゲームとは自主制作ゲームのことです。開催日は11月15日、会場は秋葉原UDKです。
当日は朝から雨が降っていましたが、会場は入場開始から大入りでイベント閉会まで人がたくさんいました。内容としては今年も色々な自主制作ゲームが発表されていました。テーブルサイズの大型モニターを利用したゲームなんかもありました。特殊なデバイスを利用したゲームをプレイアブル展示ができるのがイベントの醍醐味ですね!
もちろん自分の所属するサークル「ばったりワークス」も昨年に引き続き、デモンストレーションの展示で参加しました。
サークルの趣旨はRubyとDXRubyでゲームが作れるぞ! です。そして、今年の展示作品はオリジナルのパチスロ・ゲーム(※リンク先に当日の画像あり)です。
今回のデモンストレーション作成にあたって衝撃の紅さん、ヴェクトルさんの協力してもらいました。衝撃の紅さんは企画、キャラクターデザイン、演出、イラストを担当していただきました。ヴェクトルさんはキャラクターのセリフとパチスロについてレクチャーをしていただきました。
パチスロはスロットとは違い、日本独自のゲームシステムです。そのため、まずパチスロの仕組みやルールを調べることから始めました。製作前に実際に本物のパチスロを打ったり、法律を調べたりしました。
その成果により実物に近いリールの制御を行っています。分間80回転が法律の上限なので、45フレームで1回転、1回転あたり21絵柄で……と地道な設計からはじめました。実機と同じで停止ボタンを押したあと、すこし滑ります。法律の通りに内部抽選結果を全力で引き込むプログラムは苦労しました。
7名ほど、コントローラーを手にとってプレイしていただきました。一番受けたのは、やはりパチスロ経験者の方でした。リール停止時に僅かに絵柄がずれるところまで作りこんだ甲斐がありました。
おかげさまで、当日に演出の実装が間に合いませんでした。しかし、PC持込でのデモンストレーションです。そこでイベント中にプログラムを実装しました。その場(ライブ)で演出コードが書けたのは後述する仕組みを完成させていたからです。
余談ですが当日、私はキーボードを叩きながら「なぜ、今、みんなプログラミングしないんだ!」と思ってしまいました。しかしそれはイベントの趣旨を履き違えでしたね。来年は展示物を完成させた上で、展示中にプログラミングしようかと考えています。
ゲーム演出をRubyのコードで書ける様にする。それがFlowクラスとそのインスタンスの目的です。Flow
インスタンスを使う理由は2つありあます。
最初に**「ゲームの進行とRubyコードの実行を一致させる」**そうすればRubyコードそのものがゲームのシナリオや演出のコードになります。
次に**「ゲーム内時間の進行を画面表示と同期させる」**これがないと勝手にゲームが進行してしまいます。
require 'fiber'
require 'singleton'
require 'dxruby'
class Flows < Array
include Singleton
def update
Sprite.clean self
Sprite.update self
end
end
class Flow < Fiber
def vanished?
!alive?
end
alias update resume
end
まずFlows
クラスから説明します。これはFlow
インスタンスのマネージャークラスです。プログラムのどこからでも呼び出せるようにシングルトン・パターンを使って定義しています。レシーバー呼び出しはFlows.instance
です。Array
クラスを継承していますので、Flows.instance
にFlow
インスタンスを追加したり削除したりする場合はArray
の操作と同じです。要は手早くマネージャー機能に必要なメソッドを手に入れているわけです。
注目点はupdate
メソッドです。ここで、DXRubyのSprite
クラス・メソッドであるSprite.clean
やSpritre.update
を使用しています。これは次のFlow
クラスの説明で意味を持ちます。Sprite.clean
やSprite.update
は入れ子になった配列も再帰的に走査してくれるので大変重宝します。
次はFlow
クラスです。これはFiber
を継承しています。目を引くのはvanished?
メソッドの定義です。メソッド内部でFiber#alive?
の結果を反転しています。ここで先ほどのSprite.clean
が意味を持ちます。Sprite.clean
はvanished?
メソッド呼び出しの結果がtrue
の時、配列からそのオブジェクトを取り除いてくれます。
こうすると次のupdate
呼び出し(Flow
インスタンスではresume
のエイリアスとして定義)の時に終了したFiber
を起こさないようにしています。終了したFiber
を起こすと例外になります。
あとはWindow.loop
からFlows.instance.update
を呼び出します。毎フレームごとにFlows.instance
に登録されたFlow
インスタンスが実行されます。Flow
インスタンスは自身のファイバーが終了すれば勝手にFlows.instance
から削除されます。
もちろんFlow
インスタンスに定義するブロック内ではフレームごとにFiber.yield
で離脱する必要があります。一方で、1フレーム中に実行すべき処理を確実に行うことができます。この確実に、というのが重要です。
# 利便性のためにヘルパーメソッドを用意する。
module Kernel
#
# Flow生成とFlowsへの登録を行う。
# ここではlambdaを受け取るように変更している。
# ブロックではなくlambdaとした理由はreturn, break, nextを呼び出しても例外が発生しないから。
#
def flow(lambda_obj)
Flows.instance << Flow.new(&lambda_obj)
end
#
# 現在のフローからメインファイバーへ一時的に離脱する。
#
def wrap
Flow.yield
end
end
#
# ゲーム進行の記述
#
flow -> {
#
# サンプルのため画像を表示せず文字列を標準出力へ出力する。
#
puts "Push SPACE Key"
until Input.key_push?(K_SPACE) do
# ポーリング処理、ユーザのキー入力を待つ。
# ループの終わりで現在のフローから抜け出しDXRubyへ制御を戻す。
wrap
end
#
# フロー中に新しいフローを作り出してよい。
# 新しいフローは、次のフレームから動作する。
#
# 4つの独立したフローを生成
#
new_flows = 4.times do |i|
#
# フローを生成。ここでは乱数で決められたフレーム数だけ、自身の番号を出力する動作を行う。
#
flow -> {
rand(60).times {
puts i; # 自身の番号を出力
wrap; # フレームあたりの処理を終えたら離脱する
}
}
end
#
# ゲーム本体のフローは60フレーム(1秒間)待機する。
# ゲームが進行した際に、過去の演出フローを取り除く仕組みがあれば待つ必要はない。
#
60.times { wrap }
#
# 最初に戻る。無限ループ
#
redo
}
#
# DXRubyの制御ループ。ユーザのキー入力を受け取るためには、このループを回す必要がある。
#
Window.loop do
Flows.instance.update
end
つまり、演出側から指示された順序通りにコードを記述すれば良いのです。これで、最初の目的は果たせました。
今回の実装では二番目の目的も果たすことができています。しかし、なぜプログラムの動作が画面表示(フレーム)と同期する必要があるのでしょうか。
本来、コンピューター・ゲームは画面表示のタイミングと密接に関連して本体プログラムが動作するフレーム同期方式でした。最近では3Dゲームの出現により画面表示と関係なくゲーム本体のプログラムが動作するフレーム非同期方式が出てきているようです。
そこで私は、Ruby & DXRubyでゲームを作る場合はフレーム同期方式とフレーム非同期方式どちらが良いか検討しました。 その結論はフレーム同期方式です。なぜならコンピューター、OS、ライブラリー、言語のいずれも実時間を厳密に守れないからです。フレーム同期方式でプログラムを組めば演出時間が確実に計算可能になります。演出において時間の保証は必要条件です。
以上のことからThread
ではダメな理由もわかります。Thread
はRubyが勝手にフローを離脱させてしまいます。そうさせない方法もありますが手間が掛かります。Fiber
があるならそっちを使おうということです。
今回掲載したコードは自由に利用して構いません。コピペなり改造するなり自由です、私の名前を記載する必要もありません。
それから、もしも記事を書くことができればRubyとDXRubyについての記事を書きたいと思っています。といっても、予定は未定ですが。
明日は土井ヴぃさんの「画像素材を用意せずにいきなり始めるゲーム開発PIZZA COOKER解説」です。
画像素材を用意するのは時間が掛かります。もし、画像素材がそろう前にゲーム開発ができるのならプロトタイプ開発も可能になります。そうすればゲーム製作のスピードも上がりますね!