ページ 1 / 1
セーブ、ロードのやり方の質問です。
Posted: 2014年1月30日(木) 22:57
by 弦馬
はじめまして。
C言語の構造化プログラミングでは簡単なゲーム物を作った事があり、ハイスコアやちょっとしたデータ量のセーブならできました。
今はC++でオブジェクト指向の勉強中ですが、まだ本や解説サイトを読んでなんとなくわかったかも……?程度です。
例としてですが、複数の国家が予算の範囲内で勇者を雇い
勇者はHPが尽きるまで、アイテムを装備して攻撃力と防御力を上げて敵国と戦う。
というゲームを作るとします。
コード:
#include <vector>
class cItem
{
private:
int m_atk;
int m_def;
};
class cCharacter
{
private:
int m_Hp;
std::vector<cItem*> m_pItem;
};
class cCountry
{
private:
int m_Gold;
std::vector<cCharacter*> m_pCharacter;
};
とても大雑把ですが、クラスとそのメンバ変数は大体こんな感じになりました。(勇者を雇う、アイテムを装備する、などの関数は省略しました)
これをセーブやロードしようと思ったらどうすればいいんでしょうか?
IDを割り振って
コード:
std::vector<int> m_CharacterIDlist;
とするのも考えましたが、cCharacterクラスにアクセスするにはポインタが必要です。
実体を持つ事にすると、2つの国が同じ勇者を雇う等の表現が難しくなります。
一応、IDとポインタの組み合わせでセーブとロードができるようなのは組んだのですが、
とんでもなく長くごちゃごちゃしていてバグが無い自信が全くありません。
アプローチとして正しいのかもわかりません。
ゲームを完成させるだけでなくオブジェクト指向の勉強も兼ねているので、
オブジェクト指向的なやり方で、スマートな方法があったら教えてください。
よろしくお願いします。
Re: セーブ、ロードのやり方の質問です。
Posted: 2014年1月31日(金) 08:42
by beatle
基本的に、プログラムの情報をファイルに保存するとき、ポインタ値をそのまま保存するのはダメです。
次にファイルを読み込んだとき、もうそのポインタ値の指す場所に元の情報が無いからです。
ポインタ値を保存する代わりに、対象物のリストとその添字を保存するといいと思います。
今回なら、キャラクタ実体の一覧と、その配列への添字。
で、ロード時はそのキャラクタ実体の一覧をmallocしたメモリ領域にでもロードしてあげて、その先頭アドレスと添字で計算したポインタ値を復元してあげればいいと思いますよ。
Re: セーブ、ロードのやり方の質問です。
Posted: 2014年1月31日(金) 19:21
by 弦馬
返信ありがとうございます。
そんな感じで作ってはみたんですが、今の自分の能力では全体を把握するのがギリギリできてる……のか……?ぐらいの物になっちゃいまして
もっとわかりやすいやり方があればと思ったんですが。
把握できないっていうのは上手に分割できてないって事かも知れないです。
IEnemyクラスを用意してenemy[t]->draw();で一気に描画できますよ、というのと同じような形になっていて
セーブは、CEnemyX::draw()に相当する部分でセーブする要素を判断して
クラスのリスト、インスタンスのリスト、セーブされるデータの塊、を整理してファイルに書き込む。
といった感じです。
ソースファイルは
1:インターフェースクラス
2:セーブ時はクラスやインスタンスを数値に置き換え、ロード時はそれを元にインスタンスを作り、実際に読み書きを行うクラス
3:「セーブする要素を判断」を行うクラス
の3つに別れてます。
2と3に問題がありそうな気がしてきました。
2はちょっと機能が多すぎるかもしれません。
それに「読み書きを行う」はwindows上でプログラミングするなら今後も使いまわせる部分ですが、
「クラスやインスタンスを数値に置き換え」「インスタンスを作り」はセーブされる要素持った他のクラスに依存してるので分割した方がいいでしょうか?
3は「セーブする要素を判断」って何を言ってるのかわかりません。
私が他人から突然言われてもさっぱりです。
CEnemyX::draw()に相当する部分で呼び出され
この要素はセーブします(m_Hp);
という使われ方をする関数をメンバに持つクラスなんですが……。
上手く何なのか言えないのはよくない事な気がします。
さらに3は「物」ではなく「動作」を発想のベースにして作られたクラスなのがオブジェクト指向的によくないと思ったんですが
よく考えたら2も「読み書き」という動作をベースにしてました。
「読み書き」というのは「ディスクに読み書き」という事なので「ディスク」をベースにして考えたディスククラスにするのが良かったんでしょうか?
それから、2の「セーブされる要素持った他のクラスに依存」というのも問題は無いのか怪しく思えます。
描画の場合は描画対象となるオブジェクトの種類を、描画を行うクラスが把握する必要は無いので問題無いと思えます。
しかしロード時にインスタンスを作る際には、対象となるクラスを把握してる必要があるので
かなりの量のクラスとベッタリくっつく事になるのでそこが不安です。
ファクトリパターンでitemファクトリやenemyファクトリ、bulletファクトリなんかはわかるんですが、
2は何でもファクトリと化しちゃってますよね。
何でも詰め込んでごちゃごちゃさせるのはオブジェクト指向の逆を行くような気がするんですが、やはり問題ありですか?
まとまりのない酷い質問ですみません。
Re: セーブ、ロードのやり方の質問です。
Posted: 2014年1月31日(金) 19:44
by beatle
私の読解能力が低くて返信を理解できてない部分がありますが、とりあえず回答書きます。
2について、クラスインスタンスの内容を数値などに置き換える処理を一般に「シリアライズ」といい、逆にファイルの内容からクラスインスタンスを作り出す処理を「デシリアライズ」といいます。
シリアライズは自分のクラスが直接保持するメンバだけに対し行い、他のクラスのインスタンスをメンバとして持つ場合、それぞれに対しシリアライズ処理を委譲すればすっきりしませんか?
試しにJSON形式でシリアライズするプログラムを書いてみました。
コード:
class A : public ISerializable {
int menber1_;
B member2_;
public:
virtual void serialize(std::ostream& os) const {
os << "{\"type\":\"A\",\"member1_\":\"" << member1_ << "\",\"member2_\":";
member2_.serialize(os);
os << "}";
}
};
member1_はクラスではないので、自前でシリアライズしてあげます。
member2_はクラスなので、serializeメソッドに処理を委譲します。
Re: セーブ、ロードのやり方の質問です。
Posted: 2014年2月01日(土) 12:24
by 弦馬
beatle さんが書きました:私の読解能力が低くて返信を理解できてない部分がありますが、とりあえず回答書きます。
いえ、こちらの書き方が悪いんだと思います。
オブジェクト指向が何かっていうのがまだ良く理解できていないので……。
1つ目の書き込みと2つ目の書き込みで主題がずれてしまっていたのもまずかったです。
すみませんでした。
beatle さんが書きました:2について、クラスインスタンスの内容を数値などに置き換える処理を一般に「シリアライズ」といい、逆にファイルの内容からクラスインスタンスを作り出す処理を「デシリアライズ」といいます。
シリアライズは自分のクラスが直接保持するメンバだけに対し行い、他のクラスのインスタンスをメンバとして持つ場合、それぞれに対しシリアライズ処理を委譲すればすっきりしませんか?
シリアライズという言葉は初めて知りました。
とてもすっきりしますね。
自分なりに実装するとなるとどういう形になるのかというのはまだイメージが沸きませんが試行錯誤してみます。
わかりにくい質問に答えてくださってありがとうございました。
ところで、この掲示板でオブジェクト指向とは何か?というようなトピックを立てても構いませんか?
C言語に限った話ではないのであまり歓迎されなかったりするんでしょうか。
beatleさんのようなベテランの方から回答を貰うだけでなく(もちろんそれは嬉しいしありがたい事です)
私と同じようなレベルで悩んでいる人とも意見交換してみたいと思いました。
Re: セーブ、ロードのやり方の質問です。
Posted: 2014年2月01日(土) 19:09
by beatle
オブジェクト指向とは、っていうのは抽象的過ぎて良い議論にならないので、議題としてはオススメしません。
トピックとしては違反ではないので、どうしても立てたいならいいと思います。(過去にもそんなトピックがあった気がします)
オブジェクト指向という用語は、実は僕もよくわかりません。一種のバズワードなので、誰も正確な定義は言えないのではないでしょうか?
とにかく、グローバル関数とデータ、というC言語のような設計から、賢いデータ(公開されたメソッドからしか内部のデータを弄れない。データは自分の操作され方をメソッドとして制限できる)への移行がポイントだと思います。すなわちクラスです。
クラスは内部状態(各種のメンバ変数)を持ち、それを公開されたメソッドから操作します。
つまり、メンバ変数の効力はクラス内に留まるので、どの変数がどこで使われどんな役目があるか、把握しやすいのです。あるクラスを実装するときは、他のクラスを気にしないでそのクラスだけに集中すればいいのです。
オブジェクト指向が何たるか、は考えても明確な答えは無いかもしれませんが、とにかくクラス間をきちんと分離して、あるクラスの実装に他のクラスがなるべく関与しないようなクラス分けを考えれば上手くいくんじゃないでしょうか。そういうのをクラス間が「疎結合」であると言います。
上手いオブジェクト指向設計をするために、疎結合とか凝集度とか、いくつか考慮すべき部分がありますので調べて勉強してください。