• 締切済み

画像をクラスにどうやって渡すべきかが解りません

こんにちは、今回もC++のプログラミングについて質問させて頂きます。 現在勉強がてら作っている簡単なゲームで、処理を軽くするたびに画像ファイルを読み込む回数を減らそうとしています。、 プレイヤーのように最初から生成されているオブジェクトの場合はどうにか出来たのですが、プログラムの途中で生成するオブジェクトの場合、画像ファイルをどう読み込めば良いのか解らず困っています。 現在、炎の弾を作り出すメンバ関数 void Object::Shoot_FireBall(int Angle)//ファイアボール { FireBall* FireBall1 = new  FireBall(true,5,Ref_x(),Ref_y(),charge,"FIREBALL",Angle,5,0,40,40,4,true,Image_FireBall,1.0,true,true); if(Objects_Numbers>=Actions_Limit) { Objects_Numbers = Actions_Lower; delete Objects[Objects_Numbers]; } Objects[Objects_Numbers++] = FireBall1; } 上記のコードの FireBall(true,5,Ref_x(),Ref_y(),charge,"FIREBALL",Angle,5,0,40,40,4,true,Image_FireBall,1.0,true,true); の中の Image_FireBall をどこで定義すれば良いかが解らず困っています 以前までは void Player::Shoot_FireBall()//ファイアボール { FireBall* FireBall1 = new FireBall(5,Ref_x(),Ref_y(),charge,"FIREBALL",Ref_angle(),5,0,40,40,4,true,"Action/FireBall1.png",6,3,2,1.0,true,true); if(FIRE_num>=FIRE_Limit) FIRE_num = 1; FIRE_Array[FIRE_num++] = FireBall1; } といったように画像ファイルの名前を渡し、FireBallのコンストラクター内で画像を取得していたのですが、それでは処理が重くなると聞き、修正しようとしています。 最初は画像ファイルをメインループに入る前に読みこめば良いのかと思ったのですが、それでは有効範囲が違うので定義されていない識別子とコンパイラに認識され使用することが出来ません。 グローバル宣言を使えばなんとかなるとは思うのですが、グローバル宣言はあまり多用しない方が良いようなので、他の方法を考え現在はObjectクラスの基底クラスGraphの中で class Graph { public: protected: int *Image_FireBall; }; Graph::Graph(bool Sub_bShow,int sub_x,int sub_y,int XSize , int YSize, double Sub_ExtRate, double sub_Angle,int Sub_Trans_Flag,int Sub_Turn_Flag,int* sub_Image) { Image_FireBall = new int[6]; LoadDivGraph("Action/FireBall1.png", 6, 3 , 2 ,40 , 40 , Image_FireBall); } /* 関係の無いメンバやその初期化については省いて載せています */ のようにしていますが、コレでは結局FireBallオブジェクトが生成される度に読み込まれているので処理は変わらないと思い、他の方法を探している所です。 一体どうやって定義するのが良いのでしょうか、良い方法があれば教えて頂けると助かります、宜しくお願いします。

みんなの回答

回答No.5

あ、もうひとつありましたね。パターン3 (これが最善かも) 実際には異なるクラス名、コードで書いてもなんら構いませんが ここでは便宜上FireBallがObjectクラスの派生クラス、として説明します。 3.FireBall等のクラスがObjectクラスの派生クラスであるなら、Objectクラスが持てばいい Objectクラスが複数の派生クラスのために staticメンバを持ってしまえばいい可能性があります しかも、ほんのちょっとだけメモリを多く使うだけで 「配列で確保しておきながら」 「毎回インデックスの指定は必要ない」 といったことも可能です。 例) ////Object.h//// class Graph; class Object { protected: enum CLASS_ID { FIREBALL_ = 0, その他, NUM_ }; private: static Graph* graphlist[NUM_]; //privateでOK protected: const Graph* const graph; //真骨頂 その他諸々のデータや関数 Object( CLASS_ID ); //基底クラスのコンストラクタに上のenumの、IDを指定 virtual ~Object(); //デストラクタはやっぱり仮想で public: static void Class_Initialize(); static void Class_Cleanup(); }; で、ソースはこんな感じに Graph* Object::graphlist[NUM_]; Object::Object( CLASS_ID id ) : graph(graphlist[id]) {} //これが狙い Object::~Object(){} void Object::Class_Initialize(){ //下でtry-catchを使いたい場合は前準備 for ( int i = NUM_; i--; ) graphlist[i] = NULL; graphlist[FIREBALL_] = new Graph(FireBall用の引数リスト); graphlist[その他] = new Graph(その他用の引数リスト); } void Object::Class_Cleanup(){ //一括解放 for ( int i = NUM_; i--; ) delete graphlist[i]; } 継承時は ////FireBall.h//// #include "Object.h" class FireBall : public Object { 最小限のメンバで済む } となり、FireBallのコンストラクタでは FireBall::FireBall() : Object(FIREBALL_), その他FireBallのメンバを初期化 { } このように、基底クラスのコンストラクタを引数付きで明示的に呼び出せばいいです。 これなら描画時に void FireBall::Draw() const { graph->Draw(なんちゃら); } だけで出来てしまいます。 あとはアプリの初期化で Object::Class_Initialize(); 後始末で Object::Class_Cleanup(); とするだけでいいです。 もちろん、この手法で行って、尚且つ 数が0の時が長く出現する場合があり ずっと確保し続けていると都合が悪い、というような場合 で 尚且つ「FireBall等のオブジェクトの数が可変長」 がいいのであれば、staticメンバに「現在の各クラスのインスタンスの数」 を追加し コンストラクタで0→1になったら ロード 1→0になったらアンロード という風に(ただしそれにコストがかかる場合は状況次第なのでそこは調整する。) すれば、実用レベルの、ほぼ最高に近い形になると思います。 その場合は static void Class_Initialize(); static void Class_Cleanup(); といったメンバが、かわりに不要になります。

回答No.4

>1.Graphクラスにstaticメンバを持たせる ↓ >2.Graphクラスにstaticメンバを持たせる でしたねw 本題ではないので大丈夫だと思いますが。

回答No.3

どうも とりあえず >Objectクラスの基底クラスGraph これが文字通りの意味ならば、ちょっとお勧めできない気がします。 Objectクラス(一般的なオブジェクトの動作やデータをつかさどる基底クラスっぽい)とGraphクラスって、関係的に直交してそう(独立した概念)に感じるからです。っていうか、通常基底クラスの方が抽象的な名前になります。 お勧めな方法は Graphクラスは別のクラスとして作っておいて class Graph { LPCTSTR filename; int width, height; その他諸々のデータ public: データの描画や確保のための、最小限の各メンバ関数 }; とかにしておいて FireBallクラスはObjectクラスとかから継承して作り Graph クラスには「アクセスするだけ」とか ObjectクラスにGraphクラスのポインタとかをメンバとして持たせる の方が、自然に思います。 もひとつ Shoot_FireBall関数が呼ばれるたびに new と deleteが呼ばれる仕様になっていますが new や deleteは単なるメンバの書き換えと比べれば、遥かに重い処理であることがほとんどです。 なので、こいつをそのタイミングで毎度行うのであれば (つまり前回質問でのforループのように書くならば) 最初から決まった分配列確保しておくのは適切とは言えないはずだし 最初から決まった分配列確保しておくなら 毎回newやdeleteをするべきではなく、やはり必要なメンバをその都度書き変えるだけ、というのが定説になります。 そしたら、本題です。 画像読み込みに関してですが そんな時は 「static」が超お役立ちの可能性があります。 内部処理とかはグローバルといっても遜色ないような形になりますが コードの上では アクセス制御が出来るので遥かに容易いことになります。 ぱっと思いつくだけでも大きく分けて2パターンのアプローチがあります。 1.FireBallクラスなどにstaticメンバを持たせる Graphクラスを上のように別途作っておくとした場合 一例としては Objectクラスを継承したFireBallクラスに class Graph; class FireBall : public Object { static Graph* graph; void Draw() const override; ・ ・ ・ public: static void Class_Initialize(); static void Class_Cleanup(); }; とかにしといて ソースの方で #include "Graph.h" Graph* FireBall::graph( NULL ); void FireBall::Class_Initialize(){ graph = new Graph(引数のリスト); //ここで画像読み込み } void FireBall::Class_Cleanup(){ delete graph; //ここで画像のアンロードなどの解放処理 } void FireBall::Draw() const { graphを使った処理~ } という風にして置いたうえで アプリケーション開始時に FireBall::Class_Initialize(); をよびだし、終了時に FireBall::Class_Cleanup(); を呼び出す。 といった手法が考えられます。 この場合 Graph* graph; を直接いじれるのはFireBallクラスだけなわけですから どこからでも自由にアクセスされる恐れがある グローバル変数とは大違いで、しかも目的を達することができます。 1.Graphクラスにstaticメンバを持たせる staticを使うもう一つの方法は class Graph { LPCTSTR filename; int width, height; その他諸々のデータ public: enum { FIREBALL_ = 0, PLAYER1_, PLAYER2_, NUM_ }; private: static Graph* graph[NUM_]; //ココにポインタ配列 public: static const Graph* GetGraph( int i ){ //描画の時などにとりだす。 return graph[i]; } その他データの描画や確保のための、最小限の各メンバ関数諸々 static void Class_Initialize(); //この中でNUM_分graphにロードする static void Class_Cleanup(); //この中でNUM_分全て解放する }; このようにして置き 例えば FireBall側から const Graph* graph = Graph::GetGraph( Graph::FIREBALL_ ); graph->Draw(なんちゃら); このように呼び出す、といった方法もあります。 この場合もやはり、初期化(画像の読み込み)と解放 の処理は、どこかで行う必要があります。 こっちの場合は、クラスごとに読み込み関数を用意してやる必要がないかわりに Graph::FIREBALL_などのインデックスを指定して取り出してやる必要はあります。

回答No.2

同一クラスのオブジェクトで共通の画像データだとして: 画像データを静的メンバ変数にして、コンストラクタでロード済みはどうか 判定し、ロードされていない場合のみロードするようにする。 (最初のオブジェクト生成は時間がかかるが、次からは速い) 同一クラスのオブジェクトで複数の画像データだとして: 上記と同様に静的メンバを使用する。 ロード済の画像の名称を静的メンバ変数で管理する。 クラスで共通のデータを処理する場合、静的メンバ変数を使用するのが一般 的です。 後、画像データを静的メンバ変数にすれば、そのロード処理は静的メンバ関数 で実行できるのでプログラムの初期化に実行することも可能です。 まあ、ある意味シングルトンパターンと言えるでしょう。 静的メンバ変数/関数はある意味ではグローバルと言えるでしょうが、グローバル にすべき理由があって行うのであれば、無条件に忌避すべきものでもありません。 静的メンバをわかった上でのことならごめんなさい。

  • Tacosan
  • ベストアンサー率23% (3656/15482)
回答No.1

static って知ってる?

関連するQ&A