[開発] <警告!>まだあなたのゲームにリプレイ機能を実装してはいけません!なぜなら……(後編)
この記事を読まずに、あなたのゲームにリプレイ機能を実装するのはあまりに危険です……。
なぜなら……。
前回のゲームにおけるリプレイの実装方法の続き、後編です。
お読みでない方はまずは前回からどうぞ。
■乱数の処理
乱数は再現可能なものを使います。
最初に使っていたのは線形合同法でした。
GuNMeNのときは何を思ったか、
あらかじめ線形合同法で生成しておいた65536個の乱数テーブルを使ってました。
まあ、これはあまり意味ないです。
(たぶん、家庭用ゲームみたいなことがやりたかったのかと)
今は、乱数生成ルーチンには Mersenne Twister を使っています。
Mersenne Twisterも再現性があります。
ちなみに乱数種(シード)の自分で初期化をする場合は、
そのシード値も記録する必要があります。
さらっと書きましたが、最初にまずハマるところですので注意を。
面倒くさいので最近は、乱数種の変更すらしてませんw
つまり、乱数種固定で初期化wwww
また、これは重要な点なのですが、
自分の場合は乱数オブジェクトを複数持っています。
今のプロジェクトだと主にメインの更新処理で使うもの用と、エフェクト用と、描画用を持っています。
乱数オブジェクトを複数持つ利点は、
ちょっとした変更に強いことです。
つまり、ちょっとした変更をしてもリプレイをずらさないようにすることができるのです。
エフェクト弄りたいなーってときは、
エフェクト用の乱数オブジェクトのインスタンスを別に持っていて、
ゲーム進行に影響を与えないようにしておけば、
エフェクトを弄っても、リプレイに影響がでません!!
(逆に、あくまでエフェクトがメインのゲーム進行に影響を与えない前提です。エフェクトの上にキャラクターが乗れる、とかいう仕様では無理です。まあ、そうなったとしても、それをエフェクト以外に割り当てるだけですが)
■描画を飛ばす場合は、飛ばしていいものだけを書いておく
更新処理が固定フレームレートベースで、
描画をスキップするような実装をする場合、
前提として、描画処理と更新処理をわけます。
そうなると、絶対に実行される更新処理の方(キャラクターの移動とか)は、描画の方に入れないようにする必要があります。
逆に言うと、描画の方には飛ばしちゃいけないものを入れないようにしないといけません。
例えば、こんな感じ。
自分の場合、描画処理でも乱数を使いたくなるので、
描画処理用にも乱数オブジェクトを 1つもってます。
そうすると描画が飛ばされたりしても、
更新処理用の乱数には影響がでません。
■ハードウェアは変えないこと
ハードウェアが再生環境と同じアーキテクチャでないとダメだと思われます。
ようするに、リプレイを記録した環境と同じような計算をするハードウェア環境で再生しないとダメです。
これに関しては今は大丈夫ですが、将来どうなるかわかりません。
10年後は再生できる環境が残っているものでしょうか?
極端な例ですが、Intel社のCPUとモトローラー社(以前のMacの)のCPUとでは、
エンディアンが異なりますよね?
考慮していない場合は、同じように再生できません。
(もちろん、浮動小数などそれ以外にも考慮しないといけないものがありますが……。)
■リプレイ時にキー操作
リプレイを再生中に、早送りとか、ポーズとか、終了したりとか。
あれ?キー判定どうすんの?
簡単ですよ!
メインで使っている(再生で使っている)キー入力オブジェクトとは別にリプレイ中のキー操作オブジェクトを生成して使うだけです。
だけです、さらっと書きましたが、キー入力周りをそういう風に組んどけってことですw
↓こんな感じ。
記録中は以下のような感じだとすると、
- Player Aのキー入力オブジェクト(記録中)
- Player Bのキー入力オブジェクト(記録中)
再生中は以下のような感じ。
- Player Aのキー入力オブジェクト(記録から再生してるだけ)
- Player Bのキー入力オブジェクト(記録から再生してるだけ)
- リプレイ画面のキー入力オブジェクト(記録はもちろんしてない)
再生中のフラグが立ってるときだけ、「リプレイ画面のキー入力オブジェクト」を処理する。
(記録中は生成すらしない)
リプレイ画面のキー入力オブジェクトは、Player A(B)のキー入力オブジェクトのクラスを使いまわしてます(インスタンスでなくてあくまでクラス)。
あらかじめキー入力をクラス化しておけば、こんなのはサックリできますよ。
■バージョンアップしても大丈夫なように場合わけする
バグ報告をもらってバグ修正したり、
バージョンアップしたりして、
前のリプレイが再生できなくなったりすると、
かなり萎えますよね?
そこでこの方法をとります。
ひそかに、前述の WireBattler や GuNMeN は、
(リリースした範囲の)昔のバージョンのリプレイも再生できます。
これは、リプレイのデータファイルにバージョン番号を埋め込んでおいて、再生時に古いバージョン番号の場合には、古いルーチンを動かしているのです。
ですので、昔のリプレイも同じように再生できます。
例えば、GuNMeNは最初にコミケでリリースしたバージョンより、
現在のリリースバージョンの方がバランス調整がしてあり、
キーボードでもプレイしやすくなっています。
なんと、その新しいバージョンでも、
昔のコミケの時のリプレイデータが普通に再生できるのです!
(現に、タイトル画面を放っておく事で見られるデモプレイは、昔のバージョンのリプレイをそのままつかっているのです)
その代わり、バグって落ちるリプレイとかは、
バグ報告をもらって直しても、依然、落ちるままですwww
(今はそうそうないですが、未熟な時代に(WireBattlerを作っていたくらいの時)頻発しました)
ただ、パラメータなどを外部に出している時はちょっと処理が増えるので面倒ですが、やる価値はありますよ。
ていうか、このこと初めてかいたよ!
ユーザーの視点から立つとリプレイデータが使えなくなる、ということは避けるべきは当たり前なのですが、やっている人どれだけいるのかなあ。
大体、リプレイ実装の際の注意点はこんな感じですね。
■コラム:洋ゲー関係のリプレイについて
個人的には、市販のFPS、RTS類のリプレイが気になっているところです。
昔から FPS、RTSなどは大概可変フレームレートですし、
どう同期をとっているものやら、と。
自分が一時期プレイしてた Tribes 2 なんかは途中から記録して、そこから再生も可能でした。
ネットワークゲームですが、記録するマシンのネットワークが遅くてもばっちり対応できてました。
(Tribes2のネットコードの優秀さは定評があったのをさしおいても)
もちろん、可変フレームレートでも、他機種でも再生可能でした。
あとは、RTSもきちんとできますよね。
自分がよくリプレイを見ていた Rise of Nations や Command & Conquer Generals あたりもかなりリプレイはきちんと動いていました。
ただ、ほとんどのRTSはバージョンアップしたら、
前のバージョンのものが再生できなかったり、
再生できても途中からずれるという、けっこう適当なんだけれども……。
(パラメータやプログラムなどのバージョンわけの仕組みはずさんな模様)
まあ、この辺の洋ゲーマジックは一度暴いておきたいところです。
参考になるのは Quake3 のソースとか、
Torque Engine(Tribes2用ゲームエンジン)とかかな?
■まとめ
以上のような形でリプレイ実装の記事を書いてみましたが、参考になったでしょうか?
ちなみに、今回の記事で紹介した実際にリプレイが動いている自作ゲーム「WireBattler」、「GuNMeN」、「GanGanGan 体験版」の全てのソースファイル(Delphi用)は「GuNMeNパッケージ版」に収録されています。
実際にリプレイが実装されて動いているゲームとそのソースファイルが見てみたい方は、お手に取ってみて下さい。参考になるのではないかと思います。
(各ゲームのソースコードは CDの /omake/source ディレクトリに .zipファイルで入っています )
ぜひ、あたなも自作ゲームにリプレイを実装してみてください。
ゲームの提供の幅が広がるはずです。
では、今回はこの辺で終わりにします。
ありがとうございました。
[2008/07/20] 誤字脱字修正。
■参考リンク
[ 開発 ]

