第8回:セーブ・ロード



基礎編を通して、アドベンチャーゲームの一通りの流れが完成しました。
しかしながら、ゲームとして遊ぶためには足りない機能がまだ幾つかあります。
ここからはそういった拡張機能を一つずつ実装していくことにします。
第8回では、セーブとロードの機能を実装しましょう。

ところで、セーブデータって、中には何が入っているの?

一般的には、「そのデータをロードした時に、セーブした箇所から
ゲームを続けるための変数の値」が入っています。
例えば、現在表示している背景の番号や、演奏しているCDトラックなど。

実際、どの変数をセーブデータに入れれば良いの?

グローバル変数宣言部をご覧下さい。
セーブデータに入れられそうな変数がたくさんありますね。
これらのグローバル変数の中から、ゲームの進行に重要そうな変数を選んで
セーブデータとしてファイルに保存してやれば良いでしょう。

ファイルに保存って…どういうフォーマットで保存すれば良いの?

ファイルは、テキストとバイナリの2つのタイプに大別されます。
テキストエディタでファイルを開いてみて、中身を人間が読めるものならテキストファイル、
そうでなければバイナリファイルであると考えても、そう間違いではないでしょう。

こういったゲームのセーブデータは、バイナリで保存することをお勧めします。
なぜなら、テキストで保存すると、ユーザは中身を読めてしまい、
簡単に改ざんできてしまうからです。
バイナリで保存しても、バイナリエディタを使えば改ざんすることは可能ですが、
テキストで保存した場合に比べて解析に手間が掛かることに違いありません。

保存するデータは、グローバル宣言されている変数の中にある
動的に生成しないもの、即ちIntegerやBooleanなどの変数です。

まず、これらの変数を要素としたレコード型(C言語では構造体と呼ばれます)を作成します。
type節にあるTPhase型の定義の後に、次のように定義しましょう。
全てグローバル宣言部にある変数ですから、そのままコピー&ペーストすれば良いですね。

            
type
  TMainForm = class(TForm)
  (中略)
  //通常フェーズと選択フェーズ、シナリオストップ
  TPhase = (normal, choice, stop);
            
            
  TSaveData = record
    CurBack : Integer; //表示中の背景の番号
    CurChara : Integer; //表示中の立ちキャラの番号
    SetChara : Boolean; //立ちキャラを表示しているか
    TextPoint : Integer; //表示するメッセージ番号を指す
    MTo : Integer; //最後に表示した行の番号
    CurTrack : Integer; //現在演奏中のCDのトラック
    phase : TPhase; //フェーズ
    SkipTo : array[0..3] of Integer; //選択肢による跳び先
    ChoiceNum : Integer; //選択している選択肢番号
  end;
            

これで、セーブデータの型TSaveDataができました。
次に、セーブする手続きを書きますが、その手続きを何処から呼び出すか、覚えていますか?
外部設計の時に、マウス右クリックで出現するポップアップメニューから
セーブを行えるようにしようということでしたね。

MainFormにPopupMenuを一つ置き(Standardタブにあります)、ダブルクリックしてください。
メニューエディタが出現しますので、図8-1の状態になるようにメニュー項目(TMenuItem)を追加します。

図8-1:メニューエディタ

オブジェクトインスペクタでポップアップメニューと各メニュー項目のプロパティを変更します。

ポップアップメニューのプロパティ
プロパティ名 設定値 備考
Name PopupMenu TPopupMenuの名前
AutoHotKeys maManual メニューのアクセラレーターキーを手動にする(設定しない)

「セーブ」メニューのプロパティ
プロパティ名 設定値
Caption セーブ
Name MnuSave
「ロード」メニューのプロパティ
プロパティ名 設定値
Caption ロード
Name MnuLoad

セパレータのプロパティ
プロパティ名 設定値
Caption -
Name MnuMinus1
「終了」メニューのプロパティ
プロパティ名 設定値
Caption 終了
Name MnuExit

セパレータというのは、「ロード」と「終了」の間にある横線のメニューのことです。
"-"(マイナス)をCaptionに設定することによって自動的にセパレータとなります。

PrimImage上で右クリックしたときにこのポップアップメニューが出現するように、
PrimImageのプロパティをオブジェクトインスペクタで変更します。

PrimImageのプロパティ
プロパティ名 設定値
PopupMenu PopupMenu

また、セーブデータのファイルが無いときに誤って「ロード」を選択することの無いよう、
FormCreateイベントの最初のあたりで

            
procedure TMainForm.FormCreate(Sender: TObject);
var
   i : Integer;
begin
            
            
   //セーブデータのファイルが無ければロード不可に
   if not FileExists('savedata.sav') then
      MnuLoad.Enabled := False;
            

を追加します。

メニューアイテム「セーブ」をダブルクリックするとMnuSaveClickイベントの雛形が生成されます。
この中にセーブするための処理を書いていきましょう(リスト20)。

(リスト20)
            
procedure TMainForm.MnuSaveClick(Sender: TObject);
            
            
var
   f : file;
   savedata : TSaveData;
   i : Integer;
            
            
begin
            
            
   //セーブするデータをsavedataに代入  --------------------------(24)
   savedata.CurBack := CurBack;                           |
   savedata.CurChara := CurChara;                         |
   savedata.SetChara := SetChara;                         |
   savedata.TextPoint := TextPoint;                       |
   savedata.MTo := MTo;                                   |
   savedata.CurTrack := CurTrack;                         |
   savedata.phase := phase;                               |
   for i := 0 to 3 do                                     |
      savedata.SkipTo[i] := SkipTo[i];                    |
   savedata.ChoiceNum := ChoiceNum;  ----------------------

   //savedataをファイル'savedata.sav'に保存  --------------------(25)
   AssignFile(f, 'savedata.sav');                         |
   Rewrite(f,1);                                          |
   BlockWrite(f, savedata, sizeof(TSaveData));            |
   CloseFile(f);  -----------------------------------------

   //ロード可能にする
   MnuLoad.Enabled := True;  ------------------------------------(26)
            
            
end;
            

(24)でTSaveData型の変数savedataに保存する変数を代入した後
(25)でsavedata.savという名前のファイルで保存しています。
保存が終わったら、そのロードできるようにするため、
(26)で「ロード」のメニューアイテムをクリック可能にします。

次に、「ロード」をクリックしたときのロード処理です(リスト21)。
ファイルをロードしてから各変数をデータから復元し、
セーブしたときの状態に戻しています。

(リスト21)
            
procedure TMainForm.MnuLoadClick(Sender: TObject);
            
            
var
   f : file;
   loaddata : TSaveData;
   i : Integer;
            
            
begin
            
            
   //ファイル'savedata.sav'からloaddataに読み込み  --------------(27)
   AssignFile(f, 'savedata.sav');                         |
   Reset(f,1);                                            |
   BlockRead(f, loaddata, sizeof(TSaveData));             |
   CloseFile(f);  -----------------------------------------

   //選択フェーズだった場合のことを考えて、メッセージの反転表示を戻す
   PrimImage.Canvas.CopyMode := cmSrcCopy;
   TextBmp.Transparent := True;

   //変数をロードしたデータから戻す  ----------------------------(28)
   CurBack := loaddata.CurBack;                           |
   CurChara := loaddata.CurChara;                         |
   TextPoint := loaddata.TextPoint;                       |
   MTo := loaddata.MTo;                                   |
   CurTrack := loaddata.CurTrack;                         |
   phase := loaddata.phase;                               |
   for i := 0 to 3 do                                     |
      SkipTo[i] := loaddata.SkipTo[i];                    |
   ChoiceNum := loaddata.ChoiceNum;  ----------------------

   //セーブした状態を復元する  ----------------------------------(29)
   BackBmp.LoadFromFile(Format('..\\..\\data\\bg%.2d.bmp',[CurBack]));
   PrimImage.Picture.Assign(BackBmp);                     |
   SetChara := loaddata.SetChara;                         |
   if SetChara then DrawChara;                            |
   DrawMessageWindow;                                     |
   PutText(TextPoint);                                    |
   CDPlay(CurTrack);                                      |
   if phase = choice then ChoiceInit;  --------------------
            
            
end;
            

セーブと逆の手順を踏みます。
(27)でファイルから読み込み、(28)でセーブデータをグローバル変数に代入した後、
(29)でその変数を元にセーブ時の状況を復元しています。

最後に「終了」メニューを選択した際に発生するイベントを実装します。
ゲームを終了させるだけですから、Close手続きを呼び出せば良いでしょう。

            
procedure TMainForm.MnuExitClick(Sender: TObject);
begin
            
            
   Close;
            
            
end;
            

では実行してみましょう。
適当な箇所で右クリックしてポップアップメニューの「セーブ」を選択し、
少しページを進めてから「ロード」を選択してください。
セーブしたときのページに戻ります。
「終了」メニューを選択した時は、ウィンドウの閉じるボタンを押した場合と同様、
ゲームが終了します。


第9回へ
アドベンチャーゲーム設計論トップへ戻る
開発分室へ戻る
トップページへ戻る