アクションゲームのリプレイ機能について語るのが流行らしいので、自分もちょっとだけ。
リプレイバグフリーは実現可能か (ABA の日誌)
リプレイ機能 (PlatineDispositif さんの雑記)
うちのサークルの前作の TWilight INSanity 開発中に報告された 144 個のバグのうち、11 個がリプレイに関するバグでした。それ以前の作品である CloverWing や委員ちょ(略)にもリプレイ機能があって、だいぶバグを潰したつもりでしたがそれでもまだバグが出てくるのです。大変ですね。それら過去に作ったゲームからのリプレイずれのバグ潰しのノウハウをまとめました。基本的に DirectX と C++ の話ですが、他の開発環境でも役に立つかもしれません。
■ No.1 : オブジェクトの初期化は徹底的に
コンストラクタや初期化メソッドで全てのメンバ変数を初期化するのは当然。placement new を使って、new 演算子に渡すメモリをあらかじめ ZeroMemory() などでゼロクリアしておくと、手動での初期化を忘れてもなんとかなるかもしれません。
■ No.2 : ゲームオブジェクトからグローバル変数を参照することは禁止しよう
ゲーム中のオブジェクトが参照する変数は一つの構造体・クラスにまとめておくのです。グローバル変数とかを直接読みに行ってはいけません。また、それらの変数の読み書きは必ず setter/getter 経由で行います。これにより、バグが発生したときの追跡が楽になります。要は GameInfo クラスを作り、ゲームオブジェクトが自分のメンバ変数以外を参照・変更するときは gameinfo->getHoge() やgameinfo->setHoge() のみを使うということです。
■ No.3 : 乱数なんか使わない
本当の意味での乱数なんか不要です。プレイヤーオブジェクトの座標、スコア、ゲーム開始からのフレーム数を足したり掛けたりして素数で割れば十分乱数っぽくなります。弾幕ゲーであればこれで十分安全地帯潰しになります。標準ライブラリの rand() の線形合同法から得られる乱数の精度も似たようなもんです。(ただし、カードゲームとかだと Mersenne-Twister 法などの手法を使わないとカ○ドセプトみたいな事態に陥るかもしれないですね。あくまで推測ですが)
■ No.4 : 描画処理で更新処理を行わない
Draw 処理でゲームオブジェクトの状態を変更しちゃだめです。そうじゃないとフレームスキップ時に挙動の違いが出てきます。もしそれが難しいなら、「表示スキップは Present() をスキップするだけ」というのが(裏技的な)最終手段としてはアリかもしれません。
■ No.5 : 浮動小数点演算の誤差を無くせ
通常の浮動小数点演算の丸め誤差については、誤差の蓄積をリセットするためにステージ開始時などにインラインアセンブラでFINIT 命令を呼んでおくことである程度対処できます。Direct3D の浮動小数点演算の誤差については MS 開発環境に詳しいことで有名な NyaRuRu さんが 01step さんの掲示板に書き込んだ内容が一番きれいにまとまってます。http://www.01step.net/tobitsuki/bbs/test/read.php/tobi_bbs/1116256631/18
■ No.6 : 非同期処理には気をつけて
バックグラウンドでのスクリプト読み込みなどが、意外とリプレイずれの温床になったりします。そもそも非同期処理しなければいいのですが、NOW LOADING な時間は減らしたいわけですから。このあたりは集中してチェックしましょうということで。
■ No.7 : 徹底したテストプレイを
最後の結論がこれかよ、という気がしないでもないですが。ゲームエンジン側でリプレイずれ対策をしても、ゲームインスタンスごとに微妙に処理は違うわけです。変数一つをとってしても、ゲーム起動時に初期化すればいい変数、ゲーム開始時に初期化すればいい変数、ステージごとに初期化が必要な変数、などなどあります。結局、ひたすらテストプレイを重ねるしかないです。

