トップページ > 過去ログ > 記事閲覧
データのセーブや文字列の扱い等基礎質問
名前:dal 日時: 2008/07/03 20:14

DxLibを使ってDQ風な形式のRPGを作っているのですが、 適当に基礎を学んだ後、試行錯誤を繰り返しながら 使えなくはない方法をごり押しで使ってきてかなり作りこんでしまい、 試行錯誤の名残など色々と汚い部分が多いため、一度作り直そうと考えています。 このタイミングで、曖昧なままだった基礎的な部分の大量の疑問を解決して おきたいので、色々と質問させてください。 長いですが基本的なことが並んでいますので、どうかご指導よろしくお願いします。 ファイルにデータをセーブしたり、ファイルからロードする方法についてです。 数値や文字列などが混在した情報を保存しようとしています。 エクセルのような表形式にまとまっているデータを読み出すような 使い方をします。 1、一般に、データを保存するファイルの形式はどの形式を使われているのでしょうか。  現在はtxt形式のままやっていますが、できるだけデータを見たり書き換えたり  できないようにというのもそろそろ考慮に入れたいと考えています。 2、読み出し、書き出しのアルゴリズムの基本形みたいなものが知りたいです。  基礎を適当に学んだ後、使えなくはないのでそのままごり押しで使ってますが  よりスマートな一般的な書き方などあればそちらを使いたく思っています。  現在は以下のような、1文字分ずつずらしていって、\tがきたらデータの切れ目なので  そこまでの文字列を読んでintに変換したりしてます。  データ自体は、id,名前,para[0],para[1],…,para[40] みたいな感じで並んでます。  para(パラメーター)はHPやすばやさといったステータス値です。  また、HPやspeedといったわかりやすい変数名にしたかったのですが、 if(i==3)data[j].hp = atoi(&buff[kaisi]); if(i==4)data[j].mp = atoi(&buff[kaisi]);  といった行が何十行も続いてしまっています。何か良い書き方があれば  アドバイスお願いします。   ///////ロード/////// char buff[256]; FileHandle = FileRead_open( "data\\charadata.txt" ) ; for (j=0;j<KYARASU;j++) //1行に1人分のデータなので人数分の行繰り返し { FileRead_gets( buff, 256, FileHandle ); kaisi = 0; i=0; for (k=0;k<256;k++) { if (buff[k] == NULL) break; if (buff[k] == '\t') { buff[k] = '\0'; if (i==0){ data[j].id = atoi(&buff[kaisi]); }else if (i==1){ sprintf(data[j].name , "%s" ,&buff[kaisi]); //data[j].name = &buff[kaisi]; }else{ data[j].para[i-2] = atoi(&buff[kaisi]); } kaisi = k+1; i++; } } } FileRead_close( FileHandle ) ;   //////セーブ/////// fp = fopen( filename, "w" ); for (j=0;j<KYARASU;j++){ sprintf (saveyou,"%d\t%s\t" , data[j].id , data[j].name); fprintf (fp,saveyou); for (i=0;i<40;i++){ sprintf (saveyou,"%d\t" , data[j].para[i]); fprintf (fp,saveyou); } fprintf (fp,"\n"); } fclose (fp); 3、文字列は、どの型で扱うのが良いのでしょうか。  最初にcharの配列を使う方法を学んでそれで事足りてるので、そのまま 現在はcharの配列を使っていますが、字数が少ないものは無駄ができたりするので、  DxLibの仕様との兼ね合いも考えて、String型あるいはその他の方が  良いといったことがもしあれば教えてください(String型はまだ何もわかってないです)。 4、たとえば敵のデータなど(画像ではなく数値や文字のデータのみ)を、  ファイルからLoadするタイミングはいつが良いか困っています。  起動時に読み出しておく場合はメモリ占有量が気になり、  必要に応じてファイルにアクセスして読む場合は速度が気になっています。そこで、 メモリ使用量が、どの程度だと多すぎてやばいのかという目安が  検討がつきません。256×256の2次元マップのデータには、65536×4バイト(int型)  になるわけですが、これぐらいだと全然大丈夫ですが、その他に音楽や  画像もLoadすることになることを考えると、増えすぎると困るなら  節約可能なところ限り節約する仕様にしたいと思っているため、  どの程度なら余裕、どの程度なら危険、といった目安を教えてください。  物理メモリは256MBでもサクサク動く程度にしたいと思っています。 5、1)背景を描いて、2)ウィンドウを描いて、3)文字列を5つほど描いて、  4)文字列の中のどれを選ぶかのカーソルを描き  5)キー入力に応じてカーソルを動かす、というよくある動作をやるとき、  キーレスポンスが気になったので、  3)の文字列まで描いたものをいったんGetDrawScreenGraphして、  キー入力待ちループでは、そのGraphを描いて、カーソル描いて  という風にしているのですが、PCによってはむしろGetDrawScreeGraphで  時間がかかってるようなので速度重視には逆効果に思えているのですが、  このような方法はあまり良くないのでしょうか?  キー入力待ちループで毎回、背景とウィンドウと文字とカーソルを  描く方が良いのでしょうか?

Page: 1 |

Re: データのセーブや文字列の扱い等基礎質問 ( No.1 )
名前:憂煉 日時:2008/07/03 21:10

期末考査中で時間が無いのでさらっといきます。 1番に関して 私は同じ引数と戻り値を持つ関数を二つ使っています。片方がテキストデータ用、もう片方がバイナリデータ用です。 製作やデバッグなどの時は前者、最終的な段階で後者に切り替えています。 2番 これくらい普通(と私は思います。) sprintf(data[j].name , "%s" ,&buff[kaisi]); この部分はstrcpyでいい気がします。 //data[j].name = &buff[kaisi]; あー、私も昔これで嵌りました。懐かしい^^ 3番4番 String型は意外と無駄が大きいです。 プログラマに優しく作ってあるので使いやすいですが、メモリ消費を気にしたり実行速度を考えたりするとchar型で注意深く組んだ方が圧倒的に高速です。 先頭一致とかの関数は自分用ライブラリとして作っておくといいですよ。便利です。 しかし、こんなとこを削ってもほとんど無意味です。画像や音楽の占有メモリを削る方が得策です。文字列を削るのは正直、時間と労力の無駄です。 あー、あとマップデータならintじゃなくでcharやshortで大丈夫だと思います。 5番 なんともいえないです。どの程度の量を描画するかで違いますし、環境によって良し悪しが変わってくると思います。 言い換えると「お好みでどうぞ」 しいて言うならば文字列はなるべく画像で表示するといいです。フォントハンドルとかを使うとその時点で2メガのメモリを喰います。日本語がきつくなりますけどね… セーブ、ロード、文字列関連は自分用の自分で納得のいくライブラリを作っておくとよいでしょう。
Re: データのセーブや文字列の扱い等基礎質問 ( No.2 )
名前: 日時:2008/07/04 11:10

# 個人的な意見が結構入っているので参考程度に。 1.データを保存するファイルの形式はどの形式を使われているのか 殆どが中身の改ざんを防ぐため、配布時はバイナリ形式で テスト時はテキスト形式で保存するのが一般的です。 #オンラインゲームなどユーザが直接データを見る機会が #無い場合はテキスト形式で出力することがあります。 #ファイルの構造は様々にありますが。。。 2.読み出し、書き出しのアルゴリズムの基本形みたいなものが知りたい 上に少し書きましたがファイル構造によりけりです。 セーブするデータが増えても大丈夫なように作る必要があるとは思いますが 基本的にはdalさんが書かれているような構造が基本形みたいなものです。 3.文字列は、どの型で扱うのが良いのか CならCHAR配列、C++ならStringでしょう。 C++でのクラスのオーバーヘッドが気になるようなら、 CHAR配列にします。。。が はっきり言って好みの問題程度です 4.敵のデータ読み出しのタイミング 数値や文字のデータのみなら、はっきり言って必要なところで 読み込むようにパフォーマンスを削ったところで、 それほどメモリの使用率に差が出るとは思えません。 数万件使ってもせいぜい十数MB程度でしょう。 数がそれほどなくて、配布後に敵などを追加する必要が無いなら、 Global変数に収めておく程度で良いでしょう。 これはちょっと弊害もありますしC言語をやる人には、 あまり好まれるやり方ではありませんが普遍のデータを 逐一ファイルから読み出す必要はありません。 >節約可能なところ限り節約する仕様にしたいと 節約はいいことですが、どこがボトルネックになっているのか 分からないままに闇雲にいぢくりまわすのは愚の骨頂です。 全体の1%しか使っていないところを削ってもたかがしれてますし まずはどの部分でメモリが一番使用されているのかを 突き止めるべきでは? 5.キー入力待ちループで毎回、背景とウィンドウと文字とカーソルを  描く方が良いのか? パターンを内部的に画像で用意しているということでしょうか? 違ってたらごめんなさい。 #もし上記なら、これも好みの問題ですが、 #私ならカーソルの描画位置だけ変えます。
Re: データのセーブや文字列の扱い等基礎質問 ( No.3 )
名前:かたぱると 日時:2008/07/04 14:45

他人や一般的にどうなっているかって気になる&不安になりますよね。 極論すると「不正なく動けば正しい」になりますが スマートに動くほうがいいですよね。 サクっと答えますと自分は以下な感じです。 1、キャラクターの種類とかが多くなってきて全体管理とか調整で便利なので最近はCSVファイルで作成しています。   (中身はカンマ区切りのテキストファイルみたいなもんです)   これでいいや、となったらバイナリ形式にして他人から読み込めないようにしています。   本体のアプリとは別にこのCSVファイルを読み込んで修正したりするツールとか作って編集してます。   データ形式の設計をちゃんとしてからでないとこの手法はオススメできませんが…。   (MicroSoftのOfficeでも見れますしね。ソートとか楽チン) 2、結局のところ構造体等に値を叩き込むので拡張性はもたせつつも1つ1つ読み込んでいますね。   atoiとかで叩き込むのでほとんど変わりません。   CSVファイルなのでカンマを区切り(トークン)にしてstrtokとかで楽してます。 3、私はもっぱらCHAR型で扱っています。 4、ステージやMAPの区切り等でファイルを分けており、必要になった最初のタイミングで   ファイル毎にロードをかけています。(面クリアして次の面へ行ったとき、MAPを切り替えた時等)   規模がそれほど大きくないのであれば全体を起動時に一括で読み込んでしまいます。 5、自分は全て書き直す派ですね。大体どこかしらがアニメーションしているのと、   「このときだけこの処理」みたいな特殊実装を繰り返すと後々手直しする際に面倒だからです。   よほどオブジェクトを多数出したり処理が重い事をしない限りはキーレスポンスは問題ないと思います。
Re: データのセーブや文字列の扱い等基礎質問 ( No.4 )
名前:管理人 日時:2008/07/11 21:10

遅くなりましたが私も一応・・・ 1、一般に、データを保存するファイルの形式はどの形式を使われているのでしょうか。  デバッグ中は私もテキスト形式で、完成が近くなったらバイナリ形式です。  バイナリ形式でも生データだと改造される可能性があるので、自前の簡単な暗号化+圧縮をしています。  (その手の方の手にかかれば簡単に解析されてしまう程度ですが・・・) 2、読み出し、書き出しのアルゴリズムの基本形みたいなものが知りたいです。  皆さんも仰られていますがデータ形式に寄りけりだと思います。  セーブデータに限るのでしたら1の続きで、ファイルの内容をメモリに丸々読み込んで  展開+暗号化解除、データの解析。といった感じです。 3、文字列は、どの型で扱うのが良いのでしょうか。  私は char 型で扱っていますが、別に String でも良いと思います。 4、たとえば敵のデータなど(画像ではなく数値や文字のデータのみ)を、  ファイルからLoadするタイミングはいつが良いか困っています。  マップデータを除けばデータ容量的には起動時に全部読み込んでしまっても  問題ないと思いますが、RPGという性質上数はそれなりになると思います。  DQ1程度でしたら良いと思いますが、最終的にDQ2以降程度の規模になると  解析にそれなりの時間を要すると思います。   データフォーマットを読み込み時間短縮に特化すればゲームで使用する  画像・音楽以外のデータの総量が数十メガ程度までは起動時に読み込んで  しまっても良いと思いますが、利便性を保ったままそのフォーマットを  扱う環境を揃える労力を考えると、拘らない限りは必要に応じて読み込む、  で良いと思います。 5、1)背景を描いて、2)ウィンドウを描いて、3)文字列を5つほど描いて、  普通は毎回全部描き直してもキーレスポンスに支障を来すことは無い筈ですが  全部を描くのにそんなに時間がかかるのでしょうか?  ScreenFlip を実行してから次の ScreenFlip を実行し終わるまでに  どれくらい時間が経過しているかを GetNowCount を使って調べる  事が出来ますので、よろしければ調べてみてください。 例: int keika_jikan; int count; // 初期化 keika_jikan = 0; ScreenFlip(); count = GetNowCount(); // メインループ while( ProcessMessage() == 0 ) { // 画面の初期化 ClearDrawScreen(); // いろいろな処理 // 経過時間の描画 DrawFormatString( 0, 0, "jikan:%d", keika_jikan ); // 裏画面の内容を表画面に ScreenFlip(); // 経過時間を算出 keika_jikan = GetNowCount() - count; // 現在のカウントを取得 count = GetNowCount(); }  秒間60コマのゲームの場合この値が 17ms 前後が正常となりますので、  もしこの値よりも極端に大きくなってしまっている場合は何か問題が潜んでいる  可能性があります。
Re: データのセーブや文字列の扱い等基礎質問 ( No.5 )
名前:dal 日時:2008/07/17 20:40

非常に参考になりました。これですっきりと続けていけそうです。 問題だった箇所も改善していくことができそうです。 みなさんありがとうございました。

Page: 1 |