virtualに関して

フォーラム(掲示板)ルール
フォーラム(掲示板)ルールはこちら  ※コードを貼り付ける場合は [code][/code] で囲って下さい。詳しくはこちら
EKISUKE
記事: 108
登録日時: 13年前

virtualに関して

#1

投稿記事 by EKISUKE » 13年前

今作っているゲームで敵の処理(何種類か)をゲームメインのcppファイルで何回も呼び出したりしてごちゃごちゃしていたので、
それを一括してやりたいと思っていたとき学校でvirtualというものを学びました。
それを使って以下のようなプログラムをくみました
GameMain.h

コード:

class GameMain : public ofBaseApp{

	EnemyBase enemybase[16];
	
	public:
		void setup();
		void update();
		void draw();
		
};
ここでプログラムの全体の流れを書いています
GameMain.cpp

コード:

void*		gpImage = NULL;

static const int	ENEMY_COUNT_MAX = 200;

EnemyBase*			gpEnemy[ENEMY_COUNT_MAX];		//	敵配列

//---------------------------------------------------------------------------
//! 敵配列に追加する
//!@param	[in]	p	追加する敵クラス
//!@param	true	成功
//!@param	false	失敗(満杯)
//---------------------------------------------------------------------------
bool	addEnemy(EnemyBase* p)
{
	for( int i = 0; i < ENEMY_COUNT_MAX; i++){
		if( gpEnemy[i] != NULL ) continue;

		//---- find
		gpEnemy[i] = p;
		return true;
	}
	return	false;
}


//--------------------------------------------------------------
void GameMain::setup(){

	//==========================================================
	// 敵初期化
	//==========================================================
		
	for( int i=0; i<10; i++){

		EnemyBase*	p = new Crow;
		if( addEnemy(p) == false){
			delete p;
		}
	}
	
}

//--------------------------------------------------------------
void GameMain::update(){
	

	//==========================================================
	// 敵更新処理
	//==========================================================
	for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
		if( gpEnemy[i] == NULL ) continue;
		gpEnemy[i]->update();
		gpEnemy[i]->enemysetMove(bouei.mov);
	}
	for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
		if(CheckHitCircle( bouei.pos._x, bouei.pos._y, gpEnemy[i]->pos._x, gpEnemy[i]->pos._y, bouei.radius, gpEnemy[i]->radius) != true)
	}
}
//--------------------------------------------------------------
void GameMain::draw(){

	for( int i=0; i<ENEMY_COUNT_MAX; i++)
	{
		if( gpEnemy[i] == NULL) continue;
		
		gpEnemy[i]->draw();
	}

}
↑の56行目のCheckHitCircleはHit.h等で宣言しています。
このChecHitCircleを50~54行目のように一回だけの呼び出しでgpEnemyすべてのあたり判定はできませんか?
virtualのように変数を仮想化して子クラスが上書きできるようなシステムがあれば教えてほしいです。
Hit.h

コード:

 bool CheckHitCircle(float posx, float posy, float tposx, float tposy, float radius, float tradius);
Hit.cpp

コード:

//	二つのオブジェクトのあたり判定
bool CheckHitCircle(float posx, float posy, float tposx, float tposy, float radius, float tradius){
	
	float x = posx - tposx;;
	float y = posy - tposy;;
	float r = radius + tradius;
	float length = sqrtf(x * x + y * y);

		if( length > r)
		{
			return true;
		}else if( length < r )
		{
			return false;
		}

}
足りないところがありましたら返信お願いします。
あと、プログラムの書き方でアドバイスがあればお願いします。
回答よろしくお願いします。
最後に編集したユーザー EKISUKE on 2012年9月17日(月) 11:30 [ 編集 3 回目 ]

beatle
記事: 1281
登録日時: 13年前
住所: 埼玉
連絡を取る:

Re: virtualに関して

#2

投稿記事 by beatle » 13年前

ソースコードが長くて理解し難いので、何がやりたいのかを簡潔に表した単純なソースコードを示していただけますか?
特に今回のvirtualに関する質問なら、ゲーム的な処理はソースコードに含める必要は無いでしょう。
質問を説明するのに最低限必要な簡潔な、説明用に作りなおしたソースコードが欲しいです。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#3

投稿記事 by Dixq (管理人) » 13年前

私の理解力が低いだけかもしれませんが、イマイチ質問内容が理解できません。
「virtualに関して」というタイトルから、virtualに関連した質問だと思うのですが、
聞きたいことは
[ このChecHitCircleを50~54行目のように一回だけの呼び出しでgpEnemyすべてのあたり判定はできませんか? ]
でしょうか。

まず、もし継承について知りたいなら、ofBaseAppの中も知りたいです。

コード:

class ofBaseApp {
    public:
        void setup() = 0;
        void update() = 0;
        void draw() = 0;
};
こんな感じでしょうか?
以下項目に分けて書きます。

① 「一回だけの呼び出しでgpEnemyすべてのあたり判定」について

1行で計算したいなら、あたり判定を計算するメソッドを作ってそれを呼び出したらよいのではないでしょうか。

コード:

    for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
        if( gpEnemy[i] == NULL ) continue;
        gpEnemy[i]->update();
        gpEnemy[i]->enemysetMove(bouei.mov);
        updateHitStatus();
    }
それか、やりたいことは以下のようなことだと思うので、Enemyにあたっているかどうかを問う「isHit」メソッドを用意して以下のように書いてもいいかもしれません。

コード:

    for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
        if( gpEnemy[i] == NULL ) continue;
        gpEnemy[i]->update();
        gpEnemy[i]->enemysetMove(bouei.mov);
        if( gpEnemy[i]->isHit(bouei) ){
            delete gpEnemy[i];
            gpEnemy[i] = NULL;
        }
    }
もし「enemyの呼び出し一回で」すべてのあたり判定を計算したいと思っているのであれば、それはプログラム設計上おかしいと思います。
Enemyは同じレベルで複数並んでいるので、同じレベルで呼んであげる必要があるかと思います。
ただいまいちvirtualとの関連が分かりません。

② メンバー変数を使っていない

せっかくクラスを使っているのにメンバー変数を使わずグローバル変数を使っています。
EnemyBase* gpEnemy[ENEMY_COUNT_MAX];
などはクラス宣言の中に書き、メンバー変数として扱うべきでしょう。

③ クラス名は大文字からに統一

ofBaseApp という名前のつけ方はあまり見ません。大文字から初めてそれに統一する方がよいでしょう。
また、「setup」は「initialize」の方が普通かもしれません。
それから、「initialize」「update」「draw」の組み合わせのBaseクラスはofBaseAppというより、沢山ある仕事の内一つを示しているため
クラス名を「Task」とでもすると分かりやすいかもしれません。

④ 敵の管理を区別したいならマネージャークラスの導入を

今回GameMainというクラスが数多くある敵の管理をしていますが、この先プレイヤー、背景、SEや音楽・・
色んな物を管理するようになってくると、仕事が増えるでしょう。
そこで敵を管理するクラスである「EnemyManager」とでも名付けたクラスを用意し、敵の生成や状態の更新、破棄といったことはこのEnemyManagerにやらせるとよいと思います。
GameMainからはinitialize、update、drawを呼ぶだけです。
後のことは全部EnemyManagerがやります。


あまり回答になっていない気がしますが、不明な点があれば
「何が聞きたいか」を少し明確にしたうえでお聞き頂けると幸いです。

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#4

投稿記事 by EKISUKE » 13年前

すみません。僕の説明不足です。
聞きたいこととしましては、

virtualのように親クラスの変数を子クラスで上書きできるものはあるのか
あたり判定は別のファイルで作っておきその呼び出しを一回で敵すべて(newした)のあたり判定処理を行えるのか
です。

なので↓のような親クラスを作り
enemy.h

コード:

class EnemyBase 
{

public:
	//----------------------------------------------------------------------
	//! @name	初期化
	//----------------------------------------------------------------------
	//@
	//! コンストラクタ
	EnemyBase();
	//@}

	//----------------------------------------------------------------------
	//! @name	敵タスク専用関数
	//! 継承したオブジェクトはこの関数を上書きできる
	//----------------------------------------------------------------------
	//@{
	//! 座標更新
	virtual	void update();
	//! 描画
	virtual      void draw();
	//@}
	Vector2 pos;		//!< 座標
	Vector2 mov;		//!< 移動量
protected:
	ofImage	image[4];			//!< 画像用
	int radius;					//!< 半径
};
↓のように子クラスに継承してvirtualのついたdraw,updateを上書きできるようにしています。

Crow.h

コード:

class Crow : public EnemyBase 
{

public:
	//----------------------------------------------------------------------
	//! @name	初期化
	//----------------------------------------------------------------------
	//@{

	//! コンストラクタ
	Crow();

	//@}
	//----------------------------------------------------------------------
	//! @name	敵タスク専用関数
	//! 継承したオブジェクトはこの関数を上書きできる
	//----------------------------------------------------------------------
	//@{

	//! 座標更新
	virtual void update();
	//! 描画
	virtual void draw();

	//@}
protected:
	int radius;
};
これでGameMain.cppで

GameMain.cpp

コード:

for( int i=0; i<10; i++){
        EnemyBase*  p = new Crow;
        if( addEnemy(p) == false){
            delete p;
        }
    }
を行うとgpEnemy[]にポインタが入れられ、更新処理は今まで敵の種類ごとにupdateを呼び出していたのが、

GameMain.cpp

コード:

 for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
        if( gpEnemy[i] == NULL ) continue;
        gpEnemy[i]->update();
    }
このように一括でできるので、あたり判定も↑のようにfor文一つだけでできないかと思ったのです。
そこで、別のファイルにHit.hとHit.cppというのを作りその中にあたり判定の関数をつくり、
それを呼び出して↓のように使おうとおもったところ

GameMain.cpp

コード:

for( int i=0; i<ENEMY_COUNT_MAX; i++ ){
		if(CheckHitCircle( bouei.pos._x, bouei.pos._y, gpEnemy[i]->pos._x, gpEnemy[i]->pos._y, bouei.radius, gpEnemy[i]->radius) != true)
}
こういう風に呼び出してしまうと、pos._x,pos._yはEnemyBaseクラスのものが呼ばれ処理が正しく行われないと思いました。
なので、virtual関数のように、↑のように変数を呼び出しても、子クラスであるCrowのpos._x,pos._yが呼ばれるようなものはないのかと思ったのです。
これが無理なのであれば、EnemyBaseにvirtualでHit関数を作るしかないのでしょうか?
Dixq (管理人) さんが書きました:ofBaseApp という名前のつけ方はあまり見ません。大文字から初めてそれに統一する方がよいでしょう。
今作っているのはOpenframeworksというのを使っていまして、そこのサンプルコードのようなものをコピーしてひっぱってきたので、
ofBaseAppというのは僕が作ったのではなく、元々あったのものですが、名前は変えたほうがやはりいいでしょうか?

ofBaseAppはこのようなものでした。

コード:

class ofBaseApp : public ofBaseSoundInput, public ofBaseSoundOutput{

	public:
        ofBaseApp() {
            mouseX = mouseY = 0;
        }

		virtual ~ofBaseApp(){}

		virtual void setup(){}
		virtual void update(){}
		virtual void draw(){}
		virtual void exit(){}

		virtual void windowResized(int w, int h){}

		virtual void keyPressed( int key ){}
		virtual void keyReleased( int key ){}

		virtual void mouseMoved( int x, int y ){}
		virtual void mouseDragged( int x, int y, int button ){}
		virtual void mousePressed( int x, int y, int button ){}
		virtual void mouseReleased(){}
		virtual void mouseReleased(int x, int y, int button ){}
		
		virtual void dragEvent(ofDragInfo dragInfo) { }
		virtual void gotMessage(ofMessage msg){ }		
		
		int mouseX, mouseY;			// for processing heads
};
これもまだ継承しているようです。

長文でわかりずらくすみません。
色々まだわからないところがあるので↑のようなアドバイスはありがたいです。
回答お願いします。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#5

投稿記事 by Dixq (管理人) » 13年前

> ①virtualのように親クラスの変数を子クラスで上書きできるものはあるのか

あぁなるほど、「virtualで仮想関数を作れるように、「仮想変数」のようなことが出来ないか」ということですか。
それであれば変数のオーバーライドは出来ませんが、
その変数をクラスにして、インターフェイスを親クラスが持ってはどうでしょう。

(例)
変数をクラスにしておき、親クラスは変数の基底クラスのポインタを持つようにします。
今回の場合、あたり判定をこのような造りにするのは良いかどうか疑問ですが、方法として紹介します。

BaseEnemyを継承してEnemyを作りますが、
BaseEnemyはあたり判定計算機の基底クラスのポインタBaseHitCalculator*を持ちます。
EnemyはBaseHitCalculatorを継承したEnemy用のあたり判定計算機であるEnemyHitCalculatorを作って変数に代入します。
コードをご覧下さい。

コード:

#include <stdio.h>

//あたり判定計算機の基底
class BaseHitCalculator {
public:
	virtual ~BaseHitCalculator(){}
	virtual bool isHit(int val) = 0;
};

//敵のあたり判定計算機
class EnemyHitCalculator : public BaseHitCalculator {
public:
	bool isHit(int val) override {
		printf("%dを元に敵のあたり判定を計算", val);
		return true;
	}
};

//敵の基底
class BaseEnemy {
protected:
	BaseHitCalculator* mHitCalculator;
public:
	virtual ~BaseEnemy(){}
	virtual void initialize() = 0;
	virtual void update() = 0;
};

//敵クラス
class Enemy : public BaseEnemy {
public:
	void initialize() override {
		mHitCalculator = new EnemyHitCalculator();
	}
	void update() override {
		mHitCalculator->isHit(0);
	}
};

int main(){
	Enemy enemy;
	enemy.initialize();
	enemy.update();
	return 0;
}
これでどうでしょう。

また、これとは別に、親クラスが派生クラスの特定の値を取得したいのなら親クラスに

int getXXX() = 0;

のような純粋仮想関数を用意しておき、親クラスはgetXXX()を通して派生クラス特有の値を取得します。
派生クラスではそのクラスに特化した値を本メソッドで返すようにすればよいでしょう。


> ②あたり判定は別のファイルで作っておきその呼び出しを一回で敵すべて(newした)のあたり判定処理を行えるのか

new した人があたり判定の指示をしたりdeleteしたりするべきだと思うので、別のクラスが一括処理しない方が良いと思います。
従ってEnemyのあたり判定メソッドはEnemyが持った方が良いと思いますが、以下のように特定の計算をUtil系クラスに任せることもできるでしょう。
下の例の場合、HitCalculatorがインスタンス化しない計算機クラス(Util系クラス)になっています。

コード:

#include <stdio.h>

//敵クラス
class Enemy {
public:
	int getPos(){	//位置を返す
		return 1;
	}
};

//プレイヤークラス
class Player {
public:
	int getPos(){	//位置を返す
		return 1;
	}
};

//あたり判定計算機の基底
class HitCalculator {
	HitCalculator(){}
public:
	static bool isHit(int enemyPos, int playerPos){
		printf("敵の位置%dとプレイヤーの位置%dで計算", enemyPos, playerPos);
		return true;
	}
};

int main(){
	Enemy  enemy;
	Player player;
	HitCalculator::isHit(enemy.getPos(), player.getPos());
	return 0;
}

> ofBaseAppというのは僕が作ったのではなく、元々あったのものですが、名前は変えたほうがやはりいいでしょうか?

そうでしたか、そのフレームワークのコーディング規約がそうなっているなら、それに従った方が良いと思います。
また、GameMainの親クラスの造りが私が想像していたのと違いました。
これであればTaskではない方がよいでしょう。

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#6

投稿記事 by EKISUKE » 13年前

返信遅くなってすみません。

ソースコードを見てもすこしまだ理解できないところ(純粋仮想関数やデストラクタなど)があったので、学校の先生にきき教えてもらいました。
Dixq (管理人) さんが書きました:

コード:

#include <stdio.h>
 
//あたり判定計算機の基底
class BaseHitCalculator {
public:
    virtual ~BaseHitCalculator(){}
    virtual bool isHit(int val) = 0;
};
 
//敵のあたり判定計算機
class EnemyHitCalculator : public BaseHitCalculator {
public:
    bool isHit(int val) override {
        printf("%dを元に敵のあたり判定を計算", val);
        return true;
    }
};
 
//敵の基底
class BaseEnemy {
protected:
    BaseHitCalculator* mHitCalculator;
public:
    virtual ~BaseEnemy(){}
    virtual void initialize() = 0;
    virtual void update() = 0;
};
 
//敵クラス
class Enemy : public BaseEnemy {
public:
    void initialize() override {
        mHitCalculator = new EnemyHitCalculator();
    }
    void update() override {
        mHitCalculator->isHit(0);
    }
};
 
int main(){
    Enemy enemy;
    enemy.initialize();
    enemy.update();
    return 0;
}
このコードはあたり判定の元となるBaseHitCalculatorクラスをつくりそこでisHitを純粋仮想関数にして、
Enemyなどで敵用のあたり判定計算に中身を記述することによって、メイン関数ではinitialize()とupdate()だけを呼び出すだけであたり判定が可能という認識であっているでしょうか?
Dixq (管理人) さんが書きました:

コード:

#include <stdio.h>
 
//敵クラス
class Enemy {
public:
    int getPos(){   //位置を返す
        return 1;
    }
};
 
//プレイヤークラス
class Player {
public:
    int getPos(){   //位置を返す
        return 1;
    }
};
 
//あたり判定計算機の基底
class HitCalculator {
    HitCalculator(){}
public:
    static bool isHit(int enemyPos, int playerPos){
        printf("敵の位置%dとプレイヤーの位置%dで計算", enemyPos, playerPos);
        return true;
    }
};
 
int main(){
    Enemy  enemy;
    Player player;
    HitCalculator::isHit(enemy.getPos(), player.getPos());
    return 0;
}
こっちはそれぞれのあたり判定のとりたいクラスに座標を取得するgetPos()をつくりHitクラスを作っておいて、
そのあたり判定計算機にgetPos()でとったものを当てはめて計算するという認識であっているでしょうか?

今つくっているHit.hとHit.cppにあるあたり判定関数をうまく生かすには後者のほうがそのままで使えると思ったのですが、
前者のやり方のメリットというのは、メインでのソースコードが初期化、更新、描画などシンプルにできるというものでしょうか?
後者のほうのメリットはわかりやすいのと、あたり判定をどのタイミングでやっているかがわかりやすいというところでしょか?

上でも言ったように今つくってあるHit関数をそのまま生かすには後者のほうを参考にしてやるほうが導入がしやすいでしょうか?
それとも前者でやっていくほうが後々楽なのでしょうか?
あと、コードの内容の認識は↑に書いたものであっているでしょうか?
この3つを聞きたいです。

長文ですみません。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#7

投稿記事 by Dixq (管理人) » 13年前

今携帯しかないので簡単に書きます。

規模によりますがあたり判定の計算はそのクラスごとに閉じる方が良いと思います。
その方が汎用性があがります。
あたり判定の計算が仮想変数のようなもの無しに作りにくいと思ったら設計者手法が間違っている可能性があります。
ただ、計算部を委譲するのはありでしょう。

それから、間違った方法で実装してはならないことは無いわけですから
思い当たるすべての方法を試してみるのが一番です。
自分で何がよいか見極めるのも勉強になるでしょう。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#8

投稿記事 by Dixq (管理人) » 13年前

> Enemyなどで敵用のあたり判定計算に中身を記述することによって、メイン関数ではinitialize()とupdate()だけを呼び出すだけであたり判定が可能という認識であっているでしょうか?

Enemyなどで敵用のあたり判定計算に中身を「別途」記述することによって実現している例で、
実際のあたり判定の計算は特にしていないので、
本来はEnemy内部の
void update() override {
mHitCalculator->isHit(0);
}
のisHitの返り値を見て特定の処理をする必要がありますが、それをご理解の上であれば認識あっていると思います。

>こっちはそれぞれのあたり判定のとりたいクラスに座標を取得するgetPos()をつくりHitクラスを作っておいて、
>そのあたり判定計算機にgetPos()でとったものを当てはめて計算するという認識であっているでしょうか?

そうですが、あれはあくまで例であり、本来は座標だけでなくあたり判定範囲や判定に必要な情報を全て渡す必要があるでしょう。
本当は
HitCalculator::isHit(enemy.getPos(), player.getPos())
の返り値を見てプレイヤーにダメージを与えるなり殺すなりする必要があります。

> 今つくっているHit.hとHit.cppにあるあたり判定関数をうまく生かすには後者のほうがそのままで使えると思ったのですが、

そうですね、今の考えのまま活かすのであれば後者でしょうが、私は前者でも後者でもない物を勧めます。
BaseEnemyにisHitを用意しておき

コード:

class BaseEnemy {
public:
    virtual ~BaseEnemy(){}
    virtual bool isHit(対象物体のあたり判定情報) = 0;
};
継承先でisHitの中身を実装して、その敵ごとに個別のあたり判定計算をする方が良いでしょう。
どの敵もパラメータさえ渡せば同じように計算してよいのであれば、純粋仮想関数にせずに、親クラスであたり判定の実装を書けばよいと思います。
親クラスのあたり判定計算を使ってよい継承先では、省略できますし、ボス等特殊な敵で、カスタマイズしたい場合はoverrideしてisHitの計算をカスタマイズすればよいかと思います。

(いつもわざわざデストラクタを書いている理由はこの辺を参考にどうぞ)
http://www.google.co.jp/search?q=%E3%83 ... e&ie=UTF-8

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#9

投稿記事 by EKISUKE » 13年前

なるほど。
回答有難うございます。
Dixqさんの答えを参考にサンプルとしていろいろとあたり判定のプログラムを組んでみようと思います。

デストラクタですが、URLの先のページを少しと呼んだのですが、デストラクタはメモリーリークを防ぐためで、
virtualをデストラクタにつけるのは子クラスのデストラクタが呼ばれないのを防ぐためということでしょうか?

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#10

投稿記事 by Dixq (管理人) » 13年前

そうですが、デストラクタがメモリリークを防いでくれるわけではないです。
オブジェクトがdeleteされると自動的に呼ばれるデストラクタで終了処理をすれば
例えば自分で実装した終了処理関数であるfinalize関数などを呼び忘れたりしても漏れなく終了処理が出来るのでメモリリークが起きにくいということです。
逆に自分で実装したinitialize関数などで最初の処理をするのではなく、コンストラクタでしてしまえば初期化漏れを防ぐことが出来るでしょう。

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#11

投稿記事 by EKISUKE » 13年前

コンストラクタとinitialize関数は両方持っていて、両方同じ処理の場合コンストラクタのみでもいいのでしょうか?
それとも両方持っていたほうがいいのでしょうか?

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#12

投稿記事 by Dixq (管理人) » 13年前

コンストラクタだけで良い場合もありますし、別の関数で初期化したいときもあります。
インスタンスはそのままで内部のパラメータだけ元に戻したいことや、別のモジュールがロードを完了した後で初期化したい場合など、
ゲームではよくあるので時と場合によるかと思います。

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#13

投稿記事 by EKISUKE » 13年前

なんども聞いて申し訳ないのですが、
Dixq (管理人) さんが書きました:

コード:

class BaseEnemy {
public:
    virtual ~BaseEnemy(){}
    virtual bool isHit(対象物体のあたり判定情報) = 0;
};
isHitを純粋仮想関数にした場合
BaseEnemy baseenemy;とインスタンス化はできないのは純粋仮想関数があるからですか?
その場合、子クラスのisHitを親クラスを介して呼び出せないのでしょうか?
BaseEnemy* p = new 子クラス;
p->isHitと呼べば子クラスのisHitが呼ばれるのでしょうか?
インスタンス化はしないでいいのでしょうか?
それとデストラクタはvirtual ~BaseEnemy()をp->~BaseEnemy();と呼べば、子クラスのデストラクタが呼ばれ、
その中でdelete p;とすればいいのでしょうか?deleteはどのタイミングかはプログラムしだいですか?
質問が多くてすみません。

アバター
Dixq (管理人)
管理人
記事: 1662
登録日時: 15年前
住所: 北海道札幌市
連絡を取る:

Re: virtualに関して

#14

投稿記事 by Dixq (管理人) » 13年前

では、番号を付けて一つずつ回答します。


> isHitを純粋仮想関数にした場合BaseEnemy baseenemy;とインスタンス化はできないのは純粋仮想関数があるからですか?

はい、Baseとつけているからには、抽象クラスです。
何か特定のクラスに継承して、具体化して初めて使用できます。
例えば「生物」という抽象的な定義が入っているクラスがあったとして、それを使うには
「犬」とか「人間」とか具体的なクラスに継承して定義して初めて使えるようになります。
従って「純粋仮想関数があるクラスは抽象クラスであり、そのままではインスタンス化できない」です。


> その場合、子クラスのisHitを親クラスを介して呼び出せないのでしょうか?
> BaseEnemy* p = new 子クラス;
> p->isHitと呼べば子クラスのisHitが呼ばれるのでしょうか?

呼ばれます。
子クラスから、親クラスのメソッドを呼びたければ以下のような形で、

コード:

class EnemyHitCalculator : public BaseHitCalculator {
public:
    void isHit(int val) override {
        BaseHitCalculator::isHit(val);
    }
};
親クラスの型を付けてあげれば上のクラスが呼び出せます。(ただし今回の場合は親クラスに定義が無いのでダメです)


> インスタンス化はしないでいいのでしょうか?

インスタンス化しなくてよいかという質問の意味がよく分かりません。
new 子クラス; でインスタンスを作っていますよね。
それとも子クラスも親クラスもインスタンス化しないといけないと思っています?


> デストラクタはvirtual ~BaseEnemy()をp->~BaseEnemy();と呼べば、子クラスのデストラクタが呼ばれ、
> その中でdelete p;とすればいいのでしょうか?deleteはどのタイミングかはプログラムしだいですか?

デストラクタは自分でコールするものではないです。
コンストラクタはnewした時に自動で呼ばれるもの
デストラクタはdeleteした時に自動でよばれるもの
だと思って下さい。
デストラクタの中で、クラス内でnewした物があればdeleteして下さい。


最後に、聞いて頂いても良いですが、とにかく気になることがあったらドンドンテストプログラムを書いて行って下さい。
分からないことがあった時、自力で解決できるかできないかは今後の成長に大きく影響しますので。
例えば今回の「②」等は試しにテストプログラムを書いて実行して見れば分かるはずですよね。
プログラムは1~10まで紹介してある参考書があるわけではないので、今後試行錯誤で解を見つけていく機会が非常に多いと思います。
(質問サイトですから、質問することに対して否定しているわけではありません。よりステップアップできますという意味合いです)

EKISUKE
記事: 108
登録日時: 13年前

Re: virtualに関して

#15

投稿記事 by EKISUKE » 13年前

回答有難うございます。
Dixq (管理人) さんが書きました:最後に、聞いて頂いても良いですが、とにかく気になることがあったらドンドンテストプログラムを書いて行って下さい。
分からないことがあった時、自力で解決できるかできないかは今後の成長に大きく影響しますので。
例えば今回の「②」等は試しにテストプログラムを書いて実行して見れば分かるはずですよね。
プログラムは1~10まで紹介してある参考書があるわけではないので、今後試行錯誤で解を見つけていく機会が非常に多いと思います。
すみません。そうですね。聞くよりやらないとわからないこともありますよね。
Dixq (管理人) さんが書きました:
> インスタンス化はしないでいいのでしょうか?

インスタンス化しなくてよいかという質問の意味がよく分かりません。
new 子クラス; でインスタンスを作っていますよね。
それとも子クラスも親クラスもインスタンス化しないといけないと思っています?
親もインスタンス化しないとだめかと思っていました。
でないと、子クラスにたどり着けないのかと思っていました。
親の関数を呼び出すときは "親クラス.関数"でしか呼べないと勘違いしていました。
"親クラスにキャストしたポインタ->関数"で呼べるのを忘れていました。

追記
EnemyManagerをつくりそこでisHit(Vector2 pos, float radius)の関数をバーチャルでつくり、それぞれの敵のファイルの中に処理をいれEnemyManagerで敵と、当たり判定のある
キャラの引数をもらってあたり判定をするという風にすることにしました。
プログラムはまだできていないので、載せれませんが、そこのプログラムができれば、載せようと思います。
回答有難うございました。

閉鎖

“C言語何でも質問掲示板” へ戻る