ファイルの書き出しと読み込み

ゲームでは実行中にファイルにアクセスする必要があります。
一番利用するのがゲーム進行データのセーブとロードですね。
あとは、設定のセーブやロードなどです。

ファイルには、2種類あります。
バイナリファイルとテキストファイルです。
テキストファイルは、テキストだけが入っています。
テキスト以外には、リターンコードだけです。
通常のエディタで開けるファイルですね。
キャラクタの名前を保存するのによさそうです。

バイナリファイルはEXEファイルなど、数値がそのまま入っているファイルです。
char や、int などの数値が入っています。
ふつうのエディタで読み込もうとするとめちゃくちゃな文字が表示されます。
このファイルを開くにはバイナリエディタをいう特別なエディタが必要です。
バイナリエディタで開くと中味は

000000 10,22,32,55,00,00,00,00, 10,22,32,55,00,00,00,00,
000008 10,22,32,55,00,00,00,00, 10,22,32,55,00,00,00,00,
000010 10,22,32,55,00,00,00,00, 10,22,32,55,00,00,00,00,
000018 10,22,32,55,00,00,00,00, 10,22,32,55,00,00,00,00,
000020 10,22,32,55,00,00,00,00, 10,22,32,55,00,00,00,00,

という風になっています。
これはたとえばマップのデータや、ヒットポイントや経験値など数値を保存するのによいでしょう。

またファイルを操作するには、
 

#include <fstream.h>
 

が必要があります。

その他文字列などを扱うためにも以下のファイルをインクルードしておきます。
 

#include <fcntl.h>
#include <io.h>
#include <string.h>
#include <string>
#include <iostream>
 

バイナリファイルの書き込み

まず、ファイルを書込んでみましょう。
16×10データを書込みます。
テストのために書込むデータは、0から順番に値を入れておきます。
 

 void CFiletest0Dlg::OnButton1()
 {
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 //----- バイナリファイルの書き込み
 int i,j,z;
 int iarray[16][10];
 
 z = 0; // 配列を初期化する(TEST)
 for(i=0;i<=15;i++)
 for(j=0;j<=9;j++){
  iarray[i][j]=z;
  z++;
 }
 
 ofstream ofs( "test.dat", // ファイルを開く(ストリームを開く)
 _O_BINARY );
 
 ofs.setmode(filebuf::binary); // バイナリに指定変更する
 ofs.write( ( char* ) iarray, sizeof(iarray)); // iarrayのサイズ(バイト数)分書込む
 
 ofs.close(); // ファイルを閉じる(ストリームを閉じる)
 }
 

バイナリファイルの読み込み

マップデータを読み込んでみます。
ここでは、16*10の大きさのデータを読み込みます。
 

 void CFiletest0Dlg::OnButton5()
 {
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 //----- バイナリファイルの読み込み
 int iarray2[16][10];
 
 ifstream ifs( "test.dat", ios::binary | ios::nocreate );// ファイルを開く
 ifs.setmode(filebuf::binary); // バイナリに指定変更する
 ifs.read( ( char* ) iarray2, sizeof(iarray2)); // 1行分読み込む
 ifs.close(); // ファイルを閉じる
 
 }
 

テキストファイルの書き込み
 

 void CFiletest0Dlg::OnButton2()
 {
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 char *filename = "テキストのテストファイル";
 char *trib = "種族は人間です";
 char *prof = "火星出身です";
 ofstream out_file("testz.txt"); // デフォルト(テキストモード)でファイルを開く
 
 out_file << filename << endl;
 out_file << trib << endl;
 out_file << prof << endl;
 
 out_file.close();
 
 }
 

テキストファイルを読み込む

テキスト文字列の配列はちょっと面倒です。

ちょっと考えると、

char *name[20];

などとすればよさそうに見えますが、これではできません。

name[5]  が、配列の5個めの配列を差すのではなく、name という文字列の5個めの文字をさしてしまうからです。

テキストを1行づつ読み込んでこれをメモリに保持するには、ストリング配列というものを使います。

char *name;

ではなく、

CString name;

という風にします。
頭にCがついていますね、これもクラスです。
ストリングクラスというクラスなのでメンバ関数も持っています。
char * 型の文字列よりはずっと高機能です。
いうなれば、CBitmapのようなクラスともいえます。
ビットマップは画像を操作するクラスですが、CString は文字列を操作するクラスなのですね。

どのようなクラスか、ちょっと見てみましょう。

ヘルプには基本クラスは CObjList とかいてあります。
どうやらオブジェクトのポインタを配列として持つクラスのようです。
要素(この場合は文字列)を動的に追加したり、削除したりできるみたいです。

ちょっと実験をしてみます。

ダイアログベースのプロジェクトをつくり、ボタンをおいてください。
そしてボタンを押したときのハンドルを作ります。
OnButtonDownの中に以下のコードを書いて実行してみましょう。

 CStringList sl; //ストリングリストのオブジェクト
 sl.AddTail("1行目");      // ストリングリストに文字列を入れる
 sl.AddTail("2行目"); // 同じく追加する
 sl.AddTail("3行目");
 sl.AddTail("4行目");

 POSITION pos; // ストリングリストの位置を保持するオブジェクト
 pos = sl.GetHeadPosition(); // 配列の先頭にpos をセットする

 DC.TextOut(30,30,sl.GetAt(pos)); // ストリングリストの先頭の文字列を画面に表示する

 sl.GetNext(pos); // 次の文字列
 DC.TextOut(30,30+28*1,sl.GetAt(pos));

 sl.GetNext(pos);
 DC.TextOut(30,30+28*2,sl.GetAt(pos));

 sl.GetNext(pos);
 DC.TextOut(30,30+28*3,sl.GetAt(pos));

ウィンドウの中に

1行目
2行目
3行目
4行目

というように表示されればOKです。

このように、 CStringList クラスは文字列を配列として扱うことができます。
またストリング自体は char * 型の配列と同じように扱えるので特別な事情がない場合はString型 & StringList型を使うとなにかと便利です。

さて、このCStringList型を使ってテキストファイルを丸々読み込んでみましょう。

具体的には以下のようなコードになります。
 

  CStringList sl; // ストリングリストのオブジェクト
 CClientDC DC(this); // DCを得る
 
 char* pFileName = "readme.txt"; // 試しに開いてみるファイル。なければ作ってね
 CStdioFile f1; // ストリング読み込み用のファイルオブジェクト(これもクラス)
 
 if( !f1.Open( pFileName,CFile::typeText ) ) //ファイルをオープンして・・
 {
  #ifdef _DEBUG
  afxDump << "Unable to open file" << "\n"; // できなかったらメッセージを表示して帰る
  #endif
  exit( 1 );
 }
 
 CString linex; // 一時読み込み用のストリング
 
 for (int i=0; i<60; i++) // 試しに60行読み込む
 {
  f1.ReadString(linex); // 1行読み込み
  sl.AddTail(linex); // ストリングリストに追加する
 }
 

以上のコードで60行読み込みました。

テキストファイルを丸ごとメモリに保持するという場面はなかなかないような気がしますけど・・・。

このコードを実行するとリストに60個の文字列が追加されます。
テキストファイルが60行に満たない場合は空の文字列が追加されます。

実際にある行数を最後まで読み込む場合は

while(f1.ReadString(linex))
sl.AddTail(linex);

とします。
読み込みが失敗すると(最後の行の次を読み込もうとすると)FALSEが返されるので終わりになります。

そのた、指定した個所の文字列を読み込んだり、文字列を全部クリアしたりするメンバ関数が用意されています。
詳しくはヘルプをみてください。


構造体をそのままファイルに出力する

パーティのデータなどは、構造体になっていますね。
そのままファイルに書込みましょう。

 

 void CFiletest0Dlg::OnButton4()
 {
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 struct mons {
 char *name;
 int type;
 int hp;
 };
 mons monster[3]; //mons型構造体のオブジェクト
 
 monster[0].name = "ドラゴン";
 monster[3].name = "みずしまどらごん";
 
 monster[0].type = 5;
 monster[0].hp = 2000;
 //------------ 構造体をファイルに書込む
 ofstream ofile( "mons.dat" , ios::binary );
 ofile.setmode(filebuf::binary); // バイナリに指定変更する
 ofile.write( (char *) &monster, sizeof(monster) );
 ofile.close();
 
 }
 

ファイルを構造体に読み込む

この場合はファイル内容が構造体と合っていないとエラーをおこします。
 

 void CFiletest0Dlg::OnButton6()
 { 
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 // TODO: この位置にコントロール通知ハンドラ用のコードを追加してください
 struct mons {
 char *name;
 int type;
 int hp;
 };
 mons monsin; //mons型構造体のオブジェクト
 
 //---------- 構造体データをファイルから読み込む
 ifstream ifile( "mons.dat", ios::binary | ios::nocreate );
 ifile.setmode(filebuf::binary); // バイナリに指定変更する
 if( ifile ) { // ios::operator void*()
  ifile.read( (char *) &monsin, sizeof( monsin ) );
 }
  ifile.close();
 }
 

以上のコードはダイアログベースでの例ですが、中味をみるためにテストする場合はコンソールで
行い、中味を確認するために出力してみましょう。