データを固める


まず初めに、何の為にゲーム制作を行うのか。
これを問いたいと思います。


単純ですが、私的には誰かに遊んで貰いたいから。これに尽きると思うんですよ。
そして、自分が作ったゲームを楽しんでもらえたら最高じゃないですか。

さて、何でこんな話をしたかと言いますと、ゲームを遊んでくれるプレイヤーにとって
より楽しんでもらうためのUI(ユーザーインターフェイス)を意識してゲームを作っているのか?
という事を書きたかったからです。
UIとは広い範囲ですが、プレイヤーにとって一番のUIはロード時間の軽減だと私は思います。
早くゲームをやりたいのに、ロードが頻繁に長時間行われるとそれだけで萎えちゃいますよね。

ではロード時間を速くするにはどうすればいいのか?
色々な方法があると思います。

その方法の一つとしてデータを固めると言った方法をご紹介していこうと思います。


まず準備として、データを固めるツールの作成を行います。
固めると言ってもzipやgzでの圧縮ではないですよ?
あくまでも、複数のファイルを一つにまとめる事が目的です。

今回使うのは『C#』です。

何故C#かと言うと、単に便利だからです。
余計な事考えずにデータとかの操作が行えるので……別にc++でもよかったんですが、
ツールでc++を使う利点とかそんなにないし。。。


固めるツールの仕様としては
・exeの置いてあるディレクトリと同じところに有るフォルダーまたはファイルを全て固める。
・固めたファイルの情報をまとめたテキストを作る
・テキストには、ファイル名,始まりのバイト,終わりのバイトをかきこむ事

このくらいでいいのでは?

以下サンプル、判り易さ重視です。


using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Collections;


namespace dat_making
{
class Program
{
//エラー部
enum ErrorMessage
{
eSUCCESS,
eACCESS_FAILED,
};

//ディレクトリ内のファイルをリストへ登録します
static ErrorMessage FileCheck(string sz, ArrayList fileList)
{
//指定したディレクトリのファイルを全て保存
string files = Directory.GetFiles(sz);

//ディレクトリにフォルダーが有った場合
string dirs = Directory.GetDirectories(sz, "*", SearchOption.AllDirectories);
foreach (string s in dirs)
{
//再起呼び出しでフォルダー内も同様の処理
FileCheck(s, fileList);
}

int cnt = fileList.Count;
foreach (string s in files)
{
//.exe(このexe等)は含まない
if (Path.GetExtension(s) != ".exe")
{
//リストへ登録
fileList.Add(s);
cnt++;
}
}
if (cnt == 0)
{
//ファイルがないのでエラー
return ErrorMessage.eACCESS_FAILED;
}
//リスト登録成功
return ErrorMessage.eSUCCESS;

}

//メイン処理
static void Main(string args)
{
string myPath = Directory.GetCurrentDirectory(); //現在のカレントパスを取得
ArrayList fileList = new ArrayList();
ErrorMessage myError;

Console.WriteLine(myPath);

//カレント内のファイルを全て探し出し、パスを取得する
myError = FileCheck(myPath, fileList);

//エラー処理
if (myError == ErrorMessage.eACCESS_FAILED)
{
Console.WriteLine("結合ファイルが有りません");
Console.WriteLine("続行するには何かキーを押してください...");
Console.ReadKey();
return;
}

Console.WriteLine("ファイル数:" + fileList.Count);

byte oneFile;
UInt64 allByte = 0;
foreach (string s in fileList)
{
//ファイル名を全てフルパスで書出し。
Console.WriteLine(s);
//ファイル全てのバイト数を調べる
oneFile = File.ReadAllBytes(s);
allByte += (ulong)oneFile.Length;

}
Console.WriteLine("全てのバイト数:" + allByte);
byte[] allBuf = new byte[allByte];
int count = 0;

//個々のファイルのバイト数を記録
ArrayList map = new ArrayList();
foreach (string s in fileList)
{
oneFile = File.ReadAllBytes(s);
Array.Copy(allBuf, count, oneFile, 0, oneFile.Length);
map.Add(count + oneFile.Length);
}

File.WriteAllBytes*1;
UInt64 iii = 0;
for (int i = 0; i < fileList.Count; ++i )
{
//sz = ファイル名, ファイルの開始位置, ファイルサイズ
string sz = fileList[i] + "," + iii + ","+ map[i];
sw.WriteLine(sz);
//ファイルの開始位置のために
iii += Convert.ToUInt64(map[i]);
}
sw.Close();

Console.WriteLine("全て成功");
Console.WriteLine("続行するには何かキーを押してください...");
Console.ReadKey();
return;

}
}
}

何が何だかわかんねって方はとりあえずC#のコンソール作ってコードをコピペで動かしてみて下さい。

先ほど書きましたが、exeと同じディレクトリにフォルダもしくはファイルは全て固められます。
つまり、余計なファイルやフォルダ等を入れておいたらそれらも固められるので注意が必要です。



では、実際に固めたデータのアクセスと、個々のデータのアクセスで差が出るのか検証してみます。
次はC++を使いますね。

#include
#include
#include

int main()
{
using namespace std;

clock_t now;
clock_t out;

char sz[255];
FILE* fp;
now = clock();

for(int cnt = 0 ; cnt < 100 ; ++cnt)
{
for(int i = 0 ; i < 10 ; ++i)
{
for(int j = 0 ; j < 10 ; ++j)
{
sprintf(sz, "file\\HOGEHOGE%d\\test%d.txt", i, j);
fopen_s(&fp, sz, "rb");
fclose(fp);
}

}
}
out = clock();



cout << "個別ファイルオープン(ミリ秒)" << endl;
cout << now << endl;
cout << out << endl;
cout << out - now << endl;



now = clock();
for(int cnt = 0 ; cnt < 100 ; ++cnt)
{

fopen_s(&fp, "file\\srcAll.dat", "rb");
fclose(fp);
}
out = clock();


cout << "固めたデータ(ミリ秒)" << endl;
cout << now << endl;
cout << out << endl;
cout << out - now << endl;


return 0;
}


出力:
個別ファイルオープン(ミリ秒)
1
790
789
固めたデータ(ミリ秒)
802
809
7


最初ファイル100個でやってもクロックだと結果が判り難かったので、同じ処理を100回繰り返すようにしました。
およそ100倍の差が出ていますね。まぁ実際はこんなに差は出ませんが、アクセスする数が減ればそれだけ
効果を生む事が出来る訳です。

今回のテストでは、ファイル名がめんどくさかったのでsprintfなんてものを使ってしまいました、sprintfだけの計算で
およそ38フレームだったので、やっぱり今回の実験では100倍のアクセス時の差があるって言っても間違えではないかも…。


しかし、実際データとして使うために一つの固めたファイル自体をわけなければいけないので、実際やってみると
100倍の差も出ないと思います。


ちなみに、この固めたデータを使う場合ファイルオープンは常にしっぱなしになります。ファイルを開くのだけでこれほど時間がかかるんですから当然です。
しかし、思いもよらないところでゲームが落ちたりすると、ファイルが開きっぱなしとかになってしまうので十分注意して下さい。


さて、長編ものの終了となります。

どうもお疲れ様でした。

*1:myPath + "\\srcAll.dat"), allBuf); //datファイルを読むための情報 StreamWriter sw = new StreamWriter((myPath + "\\srcAll.txt"