■ 新・ゲーム開発講座




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


■第46夜:バイナリSAVE/LOADの基礎

前回決めた方向性でさっそくシステムメニューのSAVE機能の実装を…と行きたいところですが、今回はまずバイナリSAVE/LOADの基礎について解説します。いままで「小物関数は BasicTips.cpp を参照してください」で済ませてきましたが、ファイルの入出力部分だけはきちんと押さえていきたいという次第です。なにしろ、基本中の基本ですし(・ω・)ノ

テキストモードを無視してバイナリモードで読み書きをするのは、実はテキストモードで読み書きするメリットがあまりないからです。テキストモードでテキストファイルを読むと改行コード CR-LF (キャリッジ リターンとラインフィード) がラインフィード文字 (LF) に置き換えられるのですが、本稿のシステムでは文字コードを直接処理(とゆーか改行コードは無視 ^^;)しているので実用上まったく無問題なのです。過去10年くらいこれで不自由していないのでたぶんこれからも大丈夫だろうと安直に考えています(爆)

※本来テキストモードは異なるプラットフォーム間でテキストをやりとりする場合に改行コードの相違を吸収したり、unicodeストリームでマルチバイト文字の変換を行ったり…というご利益があるのですが、OSをWindowsに限ってしまえばあまり気にしなくても大丈夫でしょう(^0^)

実は今回以降、BasicTips.cpp のバイナリSAVE、バイナリLOADの関数 HLS_bload()HLS_bsave() はC言語のストリーム入出力を使ったものからWindowsのAPIを使ったものに差し替えて使用します。K&R式の古典的手法より、Win32-APIの方がWindows上ではご利益が大きいからです。

■バイナリSAVE

汎用性を考えて、お手軽なSAVE用関数 HLS_bsave() を以下のリストのように定義します。引数は、ファイル名、データのアドレス、書き込みサイズです。データのアドレスを void型のポインタで定義したのは、あらゆるポインタで暗黙の変換が行われる(要するにどんな構造体のアドレスを渡してもOK)ためです。Windowsのように無数のポインタの型が存在する環境では、このvoid型のように何でもOKで通してくれる存在は貴重といえましょう(^0^)



bool HLS_bsave(char *f_name,void *buf,int size )
// f_name:ファイル名
// buf :セーブしたいデータの先頭アドレス
// size :セーブしたいサイズ(バイト単位)

{

HANDLE hFile;
DWORD write_size; //実際に書き込んだバイト数

//ファイルを開く
hFile = CreateFile(
f_name, //ファイル名
GENERIC_WRITE, //書き込みモードで開く
0, //ファイル共有はしない
NULL, //ファイルハンドルの子プロセスへの継承はしない
CREATE_ALWAYS, //ファイルが存在しない場合は新規作成。存在すれば上書き。
FILE_ATTRIBUTE_NORMAL, //ファイルに特別な属性はない
NULL //拡張属性なんて高尚なものは無用
);
if( hFile == INVALID_HANDLE_VALUE )return false; //失敗→falseを返す

//書き込む
SetFilePointer(hFile, 0, 0, FILE_BEGIN); //ファイルポインタを先頭に
WriteFile(
hFile, //ファイルハンドル
(LPCVOID)buf, //書き込むデータのアドレス
(DWORD)size, //読み込むバイト数はファイルサイズぶん
&write_size, //実際に読み込んだサイズは read_size に格納
NULL //余計なオプションは指定しない
);

//ファイルを閉じる
CloseHandle(hFile);

return true;

}


では関数の内容を解説します。
ファイルを読み書きするには、まずファイルハンドルを用意しなければなりません。ハンドルとは識別子(平たく言えば背番号みたいなもの)のことで、ファイルを開いたときにOSから値をもらい、以後の操作はこのハンドルを指定することでどのファイルを読み書きするのかを表します。

ファイルを開くにはWin32 APIの CreateFile() を使います。ここで言う「開く」とはファイルにアクセスするための準備をするということで、まだ実際にデータを書き込む訳ではありません。アクセスモードは GENERIC_WRITE(書き込み専用) を指定します。またファイル生成オプションとしては CREATE_ALWAYS を指定します。
CREATE_ALWAYS は ファイルが存在しない場合は新規作成、存在すれば上書きをするという指定です。CreateFile()は成功すればファイルのハンドルを返し、失敗しれば INVALID_HANDLE_VALUE という値を返します。(…そういえば、CD-Rに焼いたゲームをHDDに書き戻したような場合、CD上でセーブファイル属性が READ ONLY になってしまって上書きができない…というトラブル?事例が昔ありましたっけ… ^0^;)

書き込みは、SetFilePointer() でファイルポインタを FILE_BEGIN (=先頭)に設定してから WriteFile() で行います。 WriteFile() には実際に書き込んだデータサイズを引数のアドレスに格納するのですが、ここでは特に使用しないので受け皿の変数を用意したのみで活用はしていません(^^;)

書き込みが終わったら、CloseHandle() でファイルを閉じて終了です。


■バイナリLOAD

ついでなので、バイナリLOADのお手軽関数も整備してしまいましょう。内容はバイナリ
SAVEとかなり似ています。


bool HLS_bload(char *f_name,void *buf)
// f_name:ファイル名
// buf :読み込むアドレス
{

HANDLE hFile;
DWORD read_size; //実際に読み込んだバイト数

//ファイルを開く
hFile = CreateFile(
f_name, //ファイル名
GENERIC_READ, //読み取りモードで開く
0, //ファイル共有はしない
NULL, //ファイルハンドルの子プロセスへの継承はしない
OPEN_EXISTING, //ファイルが存在しない場合は新規作成せず失敗とする
FILE_ATTRIBUTE_NORMAL, //ファイルに特別な属性はない
NULL //拡張属性なんて高尚なものは無用
);
if( hFile == INVALID_HANDLE_VALUE )return false; //失敗→falseを返す

//読み込む
SetFilePointer(hFile, 0, 0, FILE_BEGIN); //頭から読む
ReadFile(
hFile, //ファイルハンドル
(LPVOID)buf, //読み込み先のアドレス
GetFileSize(hFile,NULL), //読み込むバイト数はファイルサイズぶん
&read_size, //実際に読み込んだサイズは read_size に格納
NULL //余計なオプションは指定しない
);

//ファイルを閉じる
CloseHandle(hFile);

return true;

}

では順次内容を解説します。
ファイルを開く操作はSAVEのときとほとんど一緒です。アクセスモードは読み込み専用ということで GENERIC_READ を指定します。また読み込もうとしたファイルが存在しない場合は、新規作成などせず漢らしく引き下がるよう OPEN_EXISTING (ファイルが存在しない場合は失敗とする)を指定します。失敗時は CreateFile()INVALID_HANDLE_VALUE を返すので、それを見て失敗だったら戻るようにします。

読み込みは、SetFilePointer() でファイルポインタを先頭にもってきてから ReadFile() にて行います。最後にファイルを CloseHandle() で閉じて完了です。


こんな程度のお手軽関数ですが、実は結構使い勝手があります。本稿のシステムメニューにおけるSAVE/LOAD処理も実際のデータの読み書きはこの関数を使っています。実際に動くシステムメニューのソースはもう少し先になりますが、明るい希望をもってずんどこ進んで参りましょう。