■ 新・ゲーム開発講座




■ へっぽこプログラミング入門♪


■第45夜:SAVEするためのウォーミングアップ

システムメニューもいよいよ大詰めとなってきました。いよいよSAVE/LOAD関係のインプリメントです。しかしSAVEするという行為は意外と大変な内容ですので、今回はその準備に留めます。SAVEメニュー本体のコーディングをする前に、いくつか決め事をしておきましょう。文字ばっかりですみませんけど…(大汗 ^^;)

■SAVEとは何か

ところでSAVEというのはどういう処理でしょうか。平たくいえば、現在のゲームの進行状況を保存することです。言葉でいうととても簡単です。しかし「現在のゲーム状況」とは、プログラム的にどの程度正確に保存/再生できるものなのでしょうか。

最も単純に思いつくSAVE動作は、ありとあらゆる変数をそのままDISKに保存することでしょう。実は本HPの試作ノベル「長野旅行記」はそれに似たSAVE/LOAD方式をとっています。ほとんどすべての変数を本稿でいう Mode_stat に相当する構造体にパッケージにし、その構造体をそっくりファイルとしてHDDに書き出していたのです。今から見ると、よくもまあダサいことをやったものだと思いますが、いくつも平行して走るタスク処理をうまく保存するにはどうすればよいか…という苦悩のひとつの解だったわけで、当時はそれが一番確実だと思っていた訳です(うーむ ^^;)

この方式の欠点は、とにかく構造体が無意味に巨大化して意味不明のデータの塊になりやすいことです。本稿を書くにあたって、これをそのまま踏襲したら講座としては落第(笑)だろう、と思ったので現在の Mode_stat はループフラグとイベントフラグ程度の非常にシンプルなものに衣変えしていますこれは、平行して走るタスクをすべて「瞬間スナップショット」的に保存することを放棄した結果です。
とはいえ、画面切り替えのカーテン処理の最中にSAVE処理が行われるようなことは普通はないだろう、ということで無用のシチュエーションの保存の可否を整理したわけで、放棄といっても別に敗北ではありません(・ω・)ノ

それはともかく、ここでまずSAVE/LOADにまつわる問題点/留意点を列挙していきましょう。細かく挙げていけばキリがありませんが、くににんは主に3つくらいに集約できるのではないかと思っています。それは以下のようなものです。


SAVEにまつわる問題点とか留意点とか


【その1】あらゆるタスクを保存することはコストが高い

ここでいうタスクとは、本稿のプログラムの Mainloop() からちょこまかと呼ばれる各種エフェクト類の関数群を指します。Windowsのタスクマネージャで表示される個別プログラムのことではありませんので念のため…(^^;)

たとえばフェードイン/アウトとか、画面切り替えのカーテン処理とか画面がつぎつぎと切り替わっているような状況まで保存するのはプログラムのコストが高くつきます(平たく言えば面倒ということです)。ビデオの一時停止のように有る瞬間をピタ!と止めて保存し、ロード後に一時停止を解除するには、それらの処理に関わるあらゆる変数(それこそ、ローカルカウンタまで含めて)を保存しなければなりません。静止画1枚に単純なテキストが流れている程度ならともかく、オープニングやエンディングのイベントで画面がちょこまか書き換えられている最中のSAVEはちょっと怖いものがあります(^^;) そんな訳で、まずは状況を整理し、SAVE/LOADしやすい動作条件をつくってやらねばなりません。


【その2】あらゆる画面状況を正確に保存するのはコストが高い

たとえば特殊エフェクトがいろいろ使える素晴らしいノベルツールがあたっとします。スクリプター(ツールで走らせるスクリプトを書く人)は情熱的にあらゆるコマンドを駆使して超絶美麗な画面を構成しています。256種類のBMPを次々と読み込んでちまちまと転送し、芸術的なコラージュを画面に表示しました。…さて、その画面は正確に保存/再生できるでしょうか? (@▽@)

理論上は、保存/再生はおそらく可能です…とゆーか、可能でしょう、たぶん(^^;)。しかしプログラム的なコストは非常に高くなると思われます。最終的に出来上がった表画面に表示されている部分を1枚絵として保存する…という妥協案もあるかもしれませんが、ゲームの進行状況によってはそれだけでは不十分な場合があります。LOAD処理でその1枚絵を画面に復元できたとしても、その直後に走り出したスクリプト上で、別の画面 (SAVE前にメモリ上で操作していた) から画像の一部をコピーするような命令が書かれていたら、そこで画面はぐしゃぐしゃになるかもしれません。

それを防止するにはあらゆる仮想画面に読み込まれていたビットマップの情報を保存する必要がありますが、その仮想画面の内容が、今はメモリ上で上書きされてしまった別画面からの部分コピーの産物だったり、なにかのプログラム的なエフェクトで加工されたものだったとしたら、どうでしょう? (うーん、なんだか考えるのがイヤになりそうな局面ですけど…ありえないハナシではないですよね)

それらの操作の一切を記録し、STGのリプレイのように再現できれば解決の糸口になるかもしれませんが、どこまでさかのぼって記録すれば完全な状況再現ができるのかを把握するのは困難です。それを解決できるのは、グラフィックソフトのアンドゥバッファを記述できる「神」の領域に達したプログラマだけでしょう(爆) …といいますか、本当にこれが実装できたらゲーム本体よりアンドゥ機構のほうがプログラム的価値が高いとゆー本末転倒に陥るかもしれません(笑) いずれにしても、入門レベルの趣味のプログラマにとっては、かなり非現実的な内容です。


【その3】特にノベルの場合、表示スクリプトの位置の扱いに困る

単純にSAVEを行ったときのスクリプト位置を再現するようなLOAD/SAVEだと、LOAD直後に支障がでる場合があります。たとえば1ページに10行のテキストが表示されるスクリプトがあって、その5行目の途中のタイミングでSAVEが行われたとします。これをLOADして、そのまま続きを表示したらどうでしょう?本来画面に見えていてほしい最初の5行ぶんの文章が存在しない状態で画面が流れ始まってしまいます。これはいけません。ページ単位でテキストをめくっていくノベル形式では、ロードしたらそのページの先頭から表示が始まってほしいとくににんは思います。ところでこれをプログラム的に実現しようとすると、メニューを開いてSAVEを実行したタイミングと、保存すべきSAVE状態に時間差があるということになります。思惑通りの動きを実現するには、なんらかの対策が必要です。

…で、SVAE/LOAD処理を記述するにあたっては、まず事前にこれらをどのようにして解決するのかという方針を決めておかなければなりません。



現実的な解決方法

上記のような問題を解決するために、世のゲームデザイナさんやプログラマさんはいろいろな工夫を重ねています。現実との折り合いをつけ、プレイヤーに違和感を抱かせない程度にいろいろな制限を課して、プログラム的なコストを下げる努力をしてきたわけです。こうした先人の知恵に我々は敬意を払わなければなりません。現在よりも過酷で貧弱なマシン環境、SAVEメディアの制限・・・こうした歴史に鍛えられた暗黙のルールは貴重です。(ん〜なんだか、今日は風呂敷が大きいぞ:笑)

・・・ただし、純然たるプレイヤーの立場からすると、こうした制限はときに素朴な疑問として指摘されたりするのですけどね(爆死 ^0^;) 曰く、以下の如し…


・なぜ主人公の部屋にいるときしかSAVEが出来ないのか?
・戦闘中やイベント中にシステムメニューが開かないのは何故か?
・キャンプモードは何故存在するのか?
・LOADするとステージや章の最初に戻ってしまうのは何故か?
・脈絡なくダンジョンに存在するセーブポイントは、何故そこにあるのか?
・突然表示する「SAVEしますか?」の窓は何故そのタイミングで出るのか?


言うまでもなく、これらはSAVEしやすくLOADしやすいタイミングをつくるための制限です。いつも同じ背景の「主人公の部屋」や「キャンプモード」にSAVEポイントを限定すれば、少なくとも画面構成の再現について悩む必要はなくなります。
また脈絡なきダンジョン奥のSAVEポイント(笑)や突然ポップアップする「SAVEしますか?」は、その直後に長時間イベントの初期化処理を行っている可能性が濃厚と思われます。章の最初に戻ってしまうのも、本質は同じです。変なタイミングでLOAD/SAVEされては困るので、初期化が確実に行われるように制限をかけるのです。

・・・まあ、細かいことを言い出すとキリがないので、ここは
「処理が楽になる暗黙のルールがあるなら積極活用する」 のが吉ではないかとくににんは思います(・ω・)ノ
ところで暗黙のルールなんてものが思いつかなかったら?

・・・作ってしまえばいいのです。プログラマはゲーム世界を作り出す
の立場で君臨しています。いっつまいわーるどです。モーゼに十戒を与えたヤハウェのように、「俺がルールだ、わっはっは!」 で決めてしまって問題ありません。・・・ただし実装するのは自分ですから、なるべく穏便なルールにしたほうが無難といえますが(ぉ


■本稿におけるSAVE/LOADの取扱いについて

そのような次第で、カーソルブリンク中であればいつでもシステムメニューが開いてしまう我らがへっぽこノベルツールにおいても、予想される不具合を未然に防ぎ、かつそれでいてスクリプターの自由度をあまり損ねないような仕組みを考える必要があります。といっても、複雑怪奇な仕掛けを作る(=難しい方向へずんどこ走り出す)というのは本講座の趣旨に反しますので、なるべく平易なものを目指したいところです。そのために、次のような一種の割り切った前提を掲げます。


1)

画面の復帰については、読み込んだファイル名のみをキャッシュする
メモリ上で加工した内容までは保証しない。

2)

いわゆる「SAVEポイント」を任意の場所に設定できるようにする

3)

システムメニュー起動の可否をスクリプト上で制御できるようにする


…これで、ずいぶんすっきりしました。
要するに、SAVE/LOADに関しては最低限のケアに留め、SAVEするタイミングやLOAD後の復帰についてはある程度スクリプター側で配慮してもらおうという内容です。スクリプターの都合で「ここでSAVE/LOADされたらまずい!」というケースについては、システムメニューを一時的に強制OFFできる仕組みを提供することにします(フラグを1個用意すれば良いでしょう)。

さて、これでようやくSAVE処理のための方向性が整いました。では続いてもう少し具体的な動作のイメージを描いてみましょう。



■具体的なSAVEの動作イメージ

まず、スクリプト上で明示的にSAVEポイントを表すコマンドを定めます。ここでは単純に

#save_point
#sp


とでも致しましょう。
#sp は短縮表記版です(大量に設定する場合に長いと面倒なので…^^;)。#save_point コマンドは、メモリ内にSAVEデータイメージを作成します。具体的にはSAVEデータ用構造体に、必要な情報をパッケージします。ここでいう必要な情報とは、

・現在実行しているスクリプトファイル名
・テキストポインタが指し示しているスクリプトファイルの位置
・現在メモリ内に読み込んでいる画像ファイル名
・現在演奏しているBGM
・コンフィグ情報(フォント、テキスト表示エリア、テキスト表示座標など)
・フラグ構造体 Mode_stat


のことです。特に画像については、読み込んだ後でメモリ上でどのような加工をしたかetc…については関知しません。背景画像の上に #g_copy コマンドで登場人物の顔をぺたぺた貼りまくったような画面を構成していたとしても、そのような操作履歴は残さないのです。画面構成をLOAD時に再現したかったら、スクリプター側で #save_point コマンドを画面構成するスクリプト列の直前に挿入しておく等の工夫が必要です。その工夫をスクリプター側にある程度担ってもらうことで、プログラム側の負担を少なくしている訳です。(なんでも自動化しろとプログラマに押し付けるシナリオ屋さんと組むと、このあたりでプログラマの苦労が倍増しそう・・・ ^0^)

さて、SAVEデータのパッケージが出来たら、あとは何事もなかったようにゲームの処理を続けます。次の #save_point コマンドにぶつかったら、以前の内容は破棄してまたパッケージしなおしです。これを延々と繰り返すのがゲームの動作になります。

プレイヤーがシステムメニューを開き、SAVE命令を発したら、その時点でメモリ内にパックしてあるSAVE構造体をDISKに書き出します。改ページの直後に #save_point コマンドを記述しておけば、たとえページの終わり近くでSAVEを行ったとしても保存されるのはページ先頭になる訳です。いかがでしょう、イメージできましたでしょうか?

その他、SAVE/LOADを支援する機能として、以下の機能も実装することにしましょう。


・システムメニューの ON/OFF
・SAVEポイントの自動生成


システムメニューのON/OFFはいいとして、
SAVEポイントの自動生成って何?と思う方もいるかもしれません。これはあまり複雑なスクリプトを流さないことがあらかじめわかっている場合、改ページ処理を行った直後を自動的にSAVEポイントとして認識するという機能です。これならいちいち #save_point コマンドを記述しなくても、現在表示しているページの先頭が改ページ毎にパッケージ → SAVEされることになり非常に便利です。もちろん自動生成はフラグをON/OFFすることでスクリプト側から自由に指定できるようにします。これだけ機能が揃えば、ちょっとしたノベルのSAVE/LOADには十分でしょう。

さて、欲しい機能を列挙したらずいぶん盛りだくさんになりました(^^;)でも実用性を考えるとこれくらいの機能が揃っていないとどうしても使いにくいと思います。頑張って実装していきましょう。

ここで重要なのは、SAVE/LOADはコマンドひとつ作成すればOKというものではなく、常にシステムとして考えなければいけないということです。SAVEする必然性がなければ、プログラムはいかように奔放(適当?)に記述しても許されます。しかしSAVE/LOADを前提とするならば、いかに保存し、いかに再現するかを必ずどこかで考えておく必要があります。その準備を怠ると、ゲームは起動するけどSAVEはできない…という悲しい事態に陥りますので…(爆)

さて、そのような次第でたいそう偉そうなお話をしてしまいましたが、実はくににん自身がひやひやしていたりします(^^;) このあたりをどう実装するかは、システム設計者やプログラマの思想に関わる部分でもありますので、ここに示す例がスタンダードであるなどと言うつもりは全然ありません(…といいますか、そんな恐れ多い発言なんてできません:汗 ^^;)。あくまでも、無数に存在する解のなかの一つに過ぎませんので、そのつもりでお願いいたします・・・(汗汗汗)

今回は前振りだけでえらく長い話になってしまいました(…しかも最後が超弱気 ^^;)。
ともかく、気を取り直して次回以降順次実装を進めましょう(・ω・)ノ

Turn into the Next!