トップページ > 過去ログ > 記事閲覧
クラスtoka
名前:獅子 日時: 2011/09/07 21:35

DXライブラリを利用してただいまゲームを作っているのですが、ちょっとわからないことがあるので質問させて下さい。 あるていどゲームの根底が出来たんですが、ソースを見て、私がクラスを使っていないことに気付きました。別に使う必要は無いかもしれませんが、オブジェクト指向とやらを覚えるためにソースを全て書き直すことにしました。 そしてちょっと困ったことが起きました。 今関連する2つのクラスがあります。 「主人公モデルクラス」 主に主人公のデータを保持しています。メンバ変数はprivateで主人公の体力などです。メンバ関数はそれらへのアクセス、更新などのみです。 「主人公ビュークラス」 主人公のデータを出力するためのクラスです。体力をゲージで表示したりする関数を保持しています。 まず一つ目の疑問として、 「これらは別のクラスに分けておくべきか否か」。 もちろんどっちでも問題ないのかも知れませんが…ちょっと判断が付かないのでなれている方の考え方を伺いたいです。 二つ目の疑問は、 「分けるとしたらどう分ければいいか」 分かりにくくてすみません。 えっと、いまこの二つのクラスはそれぞれ二つのh、cppファイルに記述しています。 そしてMain.cppでインスタンスを実体化させて使おうとしているのですが、みての通りビュークラスはモデルクラスに依存しているのでモデルクラスのインスタンスの持つ情報をビュークラスで読み取りたいわけなんです。 ですが、このモデルクラスのインスタンスは一つでは無いので(パーティの人数分)、いちいちポインタとかだと面倒くさいんです(ややこしいですねすみません) つまり要約すると、モデルクラスのメンバ変数を簡単にビュークラスから読み取る方法はないでしょうか。 //ModelClass.h class ModelClass { int HP;//体力 これをビュークラスから読み取りたい public: int GetHP(); }; //ModelClass.cpp #include "ModelClass.h" int ModelClass::GetHP() { return HP; } //ViewClass.h class ViewClass { public: void ViewHP();//この関数を使用するとき、モデルクラスのインスタンスの情報が欲しい }; //ViewClass.cpp #include "ViewClass.h" void ViewClass::ViewHP() { // } //main.cpp #include "ModelClass.h" #include "ViewClass.h" //省略 ModelClass mc; ViewClass vc; vc.GetHP();//このとき、mcのメンバ「HP」をどう渡せばいいのですか //省略 …と、つたわりましたでしょうか? そもそもこんなのオブジェクト指向に触れてもいないんじゃないかとか思うんですが、何卒よろしくお願いします。

Page: 1 |

Re: クラスtoka ( No.1 )
名前:アリ 日時:2011/09/08 03:12

friendクラスにしてビュークラスにモデルクラスごと渡してしまうとか オブジェクト指向的にはあんまりよろしくないですけど・・・
Re: クラスtoka ( No.2 )
名前:tare 日時:2011/09/08 10:56

モデルインスタンスが複数あるなら、モデルクラスにビュークラスを統合してしまったほうがややこしく無いと思われます。 どうしても分離したいならば、ビュークラスにモデルクラスへの参照ModelClass*を持たせましょう。 void ViewClass::RegistModelClass (ModelClass *_model) { m_Model = _model; // m_ModelはViewClassのメンバ変数 } あとはViewClassの描画関数を呼び出した際に関数内でm_Model->GetHP()とすればいちいち書く必要はなくなります。 ViewClassインスタンスはモデル毎に用意していてもいいですし、描画配列vector<ModelClass*>などにModelClassを逐一登録して、入れ替わり描画しても良いと思います。
Re: クラスtoka ( No.3 )
名前:クラスクラス 日時:2011/09/08 16:36

私なら、表示のたびにHPを問い合わせるのではなく、 HP表示オブジェクトを作って、モデル側からHPが変化したら、HPを渡すメッセージを出す (ただのメソッドコールかもね) とすれば、毎フレームごとにHP表示のたびの問い合わせが無くなる。 表示が軽い、HP表示非表示はモデルに関係なく出来る。HP変化のアニメも表示側で出来る。 等々の利点があり、分離性もよくなる。 実装は、モデル側にHPのオブジェクトポインターを持つか。(あまりお勧めしない、が、簡単なものなら何でもいい) 本当のメッセージキューを作って、完全に分離も出来る。 私は後者を実装している。
Re: クラスtoka ( No.4 )
名前:獅子 日時:2011/09/08 18:17

アリさん、tareさん、クラスクラスさん、返信ありがとうございます。 どれも興味深いのですが、クラスクラスさんの言うものにちょっと驚きました。 初心者なのでびっくりしました。それって、具体的にどう実装したらよいのでしょうか?難しくてわかりません。observerパターンのようなものでしょうか? よろしければ教えて頂けないでしょうか
Re: クラスtoka ( No.5 )
名前:獅子 日時:2011/09/08 19:13

すいません具体的に言うとメッセージキューとはどう使えばいいのでしょうか?
Re: クラスtoka ( No.6 )
名前:クラスクラス 日時:2011/09/09 12:55

広義の意味でobserverパターンと言ってもいいかもしれません・・・が、 誤解を受けそうな気もするので、私はそうは言いたくは無いですね 単純に言うと、表示ループとゲーム処理ループを別に持った場合の 実装方法の一つです。 ゲーム処理ループ               表示処理ループ   |                      |   |                 何かのキューが有るかチェック  HP変化した  → キューを出す →  キュー有り対応する処理を行う   |                      |  次の処理を行う               OnUpdateする   |                    OnDrowする  ゲーム処理ループに戻る         表示処理ループに戻る このキュー処理に独自の処理を作ってもいいし。List を活用してもいい。 私は高速化のために、独自のキュー処理を実装している。 (new delete 遅いし管理大変だから、独自のキューバッファー処理込みで  高速なキュー処理を)   この方法の利点は、HP表示方法がどんなものでも、モデルに関係が無い点です。 HPが変化した時、表示部分でグニューとHPバーが移動してもいい、位置が変 わってもいい。独立性がとても高い。 今日プロバイダ変わるので今日の13時に撤去。 次にネットにつなげられるのが、1週間以上先かも。 なので、何か有りましたら、お返事できるのが遅いです。 切断前の追記。 表示ループとゲーム処理ループが同一スレッド(同一ループ内)でも、別スレッドでもかまわない。
Re: クラスtoka ( No.7 )
名前:獅子 日時:2011/09/09 13:06

クラスクラスさん返信ありがとうございます。 すみません、根本的な点なのですが、二つのループ処理を別に持つとはどういう風に実装したらよいのでしょうか…(並列処理?ってどうするんでしょうか) あと、new演算子だと遅いから独自のキューバッファを実装とのことですが、こちらはどう実装したらいいか想像もつきません(´・ω・)スミマセン よろしければもうすこし具体的に教えて頂けないでしょうか?暇な時で全然いいのでお返事よろしくお願いします
Re: クラスtoka ( No.8 )
名前:クラスクラス 日時:2011/09/15 18:18

今日新しいプロバイダにつながり。 言葉で説明するより、動くソースをのせます。 ただし!!!!!! 最小限の機能のみなので、固定数バッファーだったり、デバッグ機能はない。 本当ならいろいろな管理機能が有るが、一切排除して、最小のプログラムに。 なので、参考としてみて欲しい。 以下は、2つのスレッドで、ゲーム処理ループでは1秒ごとにHPがランダムに決まり、表示ループにキューで送信している。 #include <windows.h> #include <process.h> #include "DxLib.h" //== キュー data ===================================================== class que_data { private: int m_qid; // キューの種類 int m_p1; // キューのパラメータ1 この場合はHP que_data * m_next; // next public: que_data() { this->init(); } ~que_data() { } void init() { this->m_qid = 0; this->m_p1 = 0; this->m_next = NULL; } int get_qid() { return this->m_qid; } int get_p1() { return this->m_p1; } void set_used() { this->m_qid = 999; } void set_data(int qid,int no) { this->m_qid = qid; this->m_p1 = no; } que_data * get_next() { return this->m_next; } void set_next(que_data * qdat) { this->m_next = qdat; } }; //== キュー 管理 ===================================================== #define QMAX (100) class que_manager { private: CRITICAL_SECTION m_cs; // クリティカルセクションオブジェクト que_data m_qbuf[QMAX]; // キューバッファー que_data * m_qstart; // キューの開始 que_data * m_qend; // キューの終了 public: que_manager() { InitializeCriticalSection(&this->m_cs); this->m_qstart = NULL; // キューの開始 this->m_qend = NULL; // キューの終了 } ~que_manager() { } // キューデータ確保------------------------ que_data * alloc_que() { que_data * qdp = NULL; EnterCriticalSection(&this->m_cs); // クリティカルセクションに入る。 for(int i=0;i<QMAX;i++) { if(this->m_qbuf[i].get_qid() == 0) { // キューIDが0の時は空きデータ this->m_qbuf[i].set_used(); // キューデータが使用された qdp = &this->m_qbuf[i]; } } LeaveCriticalSection(&this->m_cs); // クリティカルセクションを出る。 return qdp; } // キューデータ解放------------------------ void free_que(que_data * qdat) { EnterCriticalSection(&this->m_cs); // クリティカルセクションに入る。 qdat->init(); LeaveCriticalSection(&this->m_cs); // クリティカルセクションを出る。 } // キューを入れる (FIFO) ------------------ void push_que(que_data * qdat) { EnterCriticalSection(&this->m_cs); // クリティカルセクションに入る。 if(this->m_qstart == NULL) { this->m_qstart = qdat; this->m_qend = qdat; } else { this->m_qend->set_next(qdat); this->m_qend = qdat; } LeaveCriticalSection(&this->m_cs); // クリティカルセクションを出る。 } // キューを取る (FIFO) ------------------- que_data * pop_que() { que_data * qdp = NULL; EnterCriticalSection(&this->m_cs); // クリティカルセクションに入る。 if(this->m_qstart != NULL) { qdp = this->m_qstart; this->m_qstart = qdp->get_next(); } LeaveCriticalSection(&this->m_cs); // クリティカルセクションを出る。 return qdp; } }; //======================================================================== que_manager g_que_dat; //== ゲーム処理ループ ==================================================== unsigned __stdcall game_loop(LPVOID lpParameter) { int hp; que_data * qdp; while(1) { hp = GetRand(100); // 1秒ごとに、ランダムにHPを変化させる // HPが変化したことを表示部にキューで送信する---------- qdp = g_que_dat.alloc_que(); // キューデータ確保 qdp->set_data(1,hp); // キューデータ設定 g_que_dat.push_que(qdp); // キュー送信 Sleep(1000); } } //== main (表示部ループ)================================================ int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { // Dxlibの初期設定---------------- ChangeWindowMode( true ); // ウインドウモードで起動 if( DxLib_Init() == -1 ) return -1; // DXライブラリの初期化 SetDrawScreen( DX_SCREEN_BACK ) ; // 裏画面を描画対象にする que_data * qdp; char wstr[200]; // データの初期設定---------------- int new_hp = 100; int hp = 0; int hp_color = GetColor( 0 , 0 , 255 ) ; // 青色の値を取得 int FontHandle1 = CreateFontToHandle( NULL , 20 , 3 ,DX_FONTTYPE_NORMAL); // : ノーマルフォント // ゲーム処理ループのスレッド起動---------------- HANDLE thread_h; // スレッドハンドル thread_h = (HANDLE)_beginthreadex(NULL, 100000, game_loop , NULL, 0, 0); if(thread_h == 0) { return -1; // スレッドが起動できない } // 表示部ループ-------------------------------- while( ProcessMessage() == 0 ) { // キュー処理------------------------------------- while((qdp = g_que_dat.pop_que()) != NULL) { // キューデータ取出し if(qdp->get_qid() == 1) { new_hp= qdp->get_p1(); // HP変化データの受信 } g_que_dat.free_que(qdp); // キューデータ解放 } // OnUpdate処理 ----------------------------------- if(hp < new_hp) { hp += 2; if(hp > new_hp) { hp = new_hp; } } else if(hp > new_hp) { hp -= 2; if(hp < new_hp) { hp = new_hp; } } // OnDrow処理 ------------------------------------- ClearDrawScreen() ; // 画面を初期化 sprintf_s(wstr,200," HP = %3d / 100",hp); DrawStringToHandle( 100 , 100 , wstr, GetColor( 255 , 255 , 255 ) , FontHandle1 ) ; DrawBox( 100 ,120 , hp * 4 + 100 , 150, hp_color , TRUE) ; // 四角形を描画 ScreenFlip() ; // 裏画面の内容を表画面に反映する Sleep(5); } DxLib_End() ; // DXライブラリの後始末 return 0 ; } //========================================================================
Re: クラスtoka ( No.9 )
名前:クラスクラス 日時:2011/09/15 18:20

次のプログラムは、1つのスレッドでゲーム処理と表示処理を行っている。 #include <windows.h> #include <process.h> #include "DxLib.h" //== キュー data ===================================================== class que_data { private: int m_qid; // キューの種類 int m_p1; // キューのパラメータ1 この場合はHP que_data * m_next; // next public: que_data() { this->init(); } ~que_data() { } void init() { this->m_qid = 0; this->m_p1 = 0; this->m_next = NULL; } int get_qid() { return this->m_qid; } int get_p1() { return this->m_p1; } void set_used() { this->m_qid = 999; } void set_data(int qid,int no) { this->m_qid = qid; this->m_p1 = no; } que_data * get_next() { return this->m_next; } void set_next(que_data * qdat) { this->m_next = qdat; } }; //== キュー 管理 ===================================================== #define QMAX (100) class que_manager { private: que_data m_qbuf[QMAX]; // キューバッファー que_data * m_qstart; // キューの開始 que_data * m_qend; // キューの終了 public: que_manager() { this->m_qstart = NULL; // キューの開始 this->m_qend = NULL; // キューの終了 } ~que_manager() { } // キューデータ確保------------------------ que_data * alloc_que() { que_data * qdp = NULL; for(int i=0;i<QMAX;i++) { if(this->m_qbuf[i].get_qid() == 0) { this->m_qbuf[i].set_used(); qdp = &this->m_qbuf[i]; } } return qdp; } // キューデータ解放------------------------ void free_que(que_data * qdat) { qdat->init(); } // キューを入れる (FIFO) ------------------ void push_que(que_data * qdat) { if(this->m_qstart == NULL) { this->m_qstart = qdat; this->m_qend = qdat; } else { this->m_qend->set_next(qdat); this->m_qend = qdat; } } // キューを取る (FIFO) ------------------- que_data * pop_que() { que_data * qdp = NULL; if(this->m_qstart != NULL) { qdp = this->m_qstart; this->m_qstart = qdp->get_next(); } return qdp; } }; //======================================================================== que_manager g_que_dat; int time_old; //== ゲーム処理 ==================================================== void game処理() { int hp; que_data * qdp; int time_new = GetNowCount(); if((time_new - time_old) > 1000) { hp = GetRand(100); // 1秒ごとに、ランダムにHPを変化させる // HPが変化したことを表示部にキューで連絡する---------- qdp = g_que_dat.alloc_que(); // キューデータ取出し qdp->set_data(1,hp); // キューデータ設定 g_que_dat.push_que(qdp); // キューの送信 time_old = time_new; } } //== main (表示部ループ)================================================ int WINAPI WinMain( HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nCmdShow ) { // Dxlibの初期設定---------------- ChangeWindowMode( true ); // ウインドウモードで起動 if( DxLib_Init() == -1 ) return -1; // DXライブラリの初期化 SetDrawScreen( DX_SCREEN_BACK ) ; // 裏画面を描画対象にする que_data * qdp; char wstr[200]; // データの初期設定---------------- int new_hp = 100; int hp = 0; int hp_color = GetColor( 0 , 0 , 255 ) ; // 青色の値を取得 int FontHandle1 = CreateFontToHandle( NULL , 20 , 3 ,DX_FONTTYPE_NORMAL); // : ノーマルフォント time_old = GetNowCount(); // 表示部ループ-------------------------------- while( ProcessMessage() == 0 ) { // ゲーム処理呼び出し----------------------- game処理(); // キュー処理------------------------------------- while((qdp = g_que_dat.pop_que()) != NULL) { if(qdp->get_qid() == 1) { new_hp= qdp->get_p1(); } g_que_dat.free_que(qdp); } // OnUpdate処理 ----------------------------------- if(hp < new_hp) { hp += 2; if(hp > new_hp) { hp = new_hp; } } else if(hp > new_hp) { hp -= 2; if(hp < new_hp) { hp = new_hp; } } // OnDrow処理 ------------------------------------- ClearDrawScreen() ; // 画面を初期化 sprintf_s(wstr,200," HP = %3d / 100",hp); DrawStringToHandle( 100 , 100 , wstr, GetColor( 255 , 255 , 255 ) , FontHandle1 ) ; DrawBox( 100 ,120 , hp * 4 + 100 , 150, hp_color , TRUE) ; // 四角形を描画 ScreenFlip() ; // 裏画面の内容を表画面に反映する Sleep(5); } DxLib_End() ; // DXライブラリの後始末 return 0 ; } //========================================================================
Re: クラスtoka ( No.10 )
名前:クラスクラス 日時:2011/09/15 20:52

この2つの内、前者は2つのスレッドで実際に並列に動いている。 この利点は、表示のFPSにまったく影響されずにゲーム処理部分が書ける。 その他にも多くの利点があるが、ここでは割愛する。 私は、常に前者の設計をしている、その方が楽だし簡単だから。 しかし、技術ノウハウや設計ノウハウは多少高いかもしれない。 並列処理は慣れるまで大変だが、慣れたら楽です。 追記。 ちなみに、何故new deleteを使わないかというと。 遅い理由もあるが、主に、シーンが変わったり、終了や、何かの突然の状態遷移の場合。 表示部に対すキュー処理を一時中止やキャンセルしたい場合。 キューを使うところでnewすると、そのdeleteが大変。 一括管理していれば、キューマネージャに、「これから一時中止ね」とか、「今までのキュークリアして」とか 「シーン変わるから、キュー処理中断」とか簡単にできるのですよ。 キューに対する管理を一括で管理できる。 これの利点は計り知れない。
Re: クラスtoka ( No.11 )
名前:クラスクラス 日時:2011/09/15 23:54

おっと、キューバッファーのオバーフロー処理チェックがないから。 窓が後ろになるとエラーおこります orz その他にもエラーチェックしていないのできおつけてください。 このエラー起こさないようには下記2行追加。 追加−> if(qdp != NULL) { qdp->set_data(1,hp); // キューデータ設定 g_que_dat.push_que(qdp); // キュー送信 追加−> }
Re: クラスtoka ( No.12 )
名前:獅子(解決) 日時:2011/09/17 21:38

おお、凄い!返信ありがとうございます! ナカナカ難しそうですが使えたら便利そうです。頑張って勉強してみます。わざわざソースコードまでありがとうございました!

Page: 1 |