ページ 1 / 1
クラスの継承について
Posted: 2014年2月05日(水) 03:57
by coc
管理人様のhttp://dixq.net/g/sp_06.htmlを参考に現在プログラムしています。
このサイト内で
Game.h
コード:
#pragma once
#include "BaseScene.h"
#include "ISceneChanger.h"
//ゲーム画面クラス
class Game : public BaseScene {
public :
Game::Game(ISceneChanger* changer);
void Initialize() override; //初期化処理をオーバーライド。
//void Finalize() override; //終了処理をオーバーライド。
void Update() override; //更新処理をオーバーライド。
void Draw() override; //描画処理をオーバーライド。
};
のGameクラスを継承してPlayerクラスを作ろうとしています
Player.h
コード:
#pragma once
#include "Game.h"
//ゲーム画面クラス
class Player : public Game {
public :
//Player::Player(ISceneChanger* changer);
void Initialize() override; //初期化処理をオーバーライド。
void Finalize() override; //終了処理をオーバーライド。
void Update() override; //更新処理をオーバーライド。
void Draw() override; //描画処理をオーバーライド。
};
この時Game.cpp内のInitで
コード:
//初期化
void Game::Initialize(){
mImageHandle = LoadGraph("images/Scene_Game.png"); //画像のロード
//プレーヤー情報の初期化
Player::Initialize();
//他にも関連した初期化関数を呼び出していく
}
という形で実装していきたいのですが、クラス継承先のメンバ関数は継承元からは参照出来ないためどの様に組めば良いのでしょうか?
↑の形ではなくとも、一般的な構造を教えて頂けると助かります。
Re: クラスの継承について
Posted: 2014年2月05日(水) 07:58
by beatle
うーん。継承の意味を勘違いしているのではないでしょうか?
オブジェクト指向で継承というと、普通は「is-a関係」が成り立つ場合に使います。
この場合で言うと「Player is a Game」、日本語で言えば、「プレイヤーはゲームである」です。
多分違いますよね?
教科書的な例では「人間は哺乳類である」とか「三角形は図形である」とかでしょうか。
is-a関係の重要なことは、子クラスは親クラスとして見なせる、ということです。
人間は哺乳類ですので、哺乳類に成り立つことは人間にも成り立ちます。
三角形は図形ですので、図形にできる操作(色を付ける、移動する)は三角形にも適用できます。
そして、継承では親クラスは子クラス特有の情報を使わないのが、良いプログラムにするときの原則です。
親クラスは子クラスを使ってもいいですが、あくまで親クラスとして扱います。
図形は他の図形を扱ってもよいですが、それはあくまで「図形」の範囲で。三角形に特有な性質(3つの角度)などを図形クラスで使ってはいけません。
ということで、PlayerがGameを継承することは間違いじゃないでしょうか?
親(継承元)から子(継承先)を使わないことが原則ですから、ご質問の方向性は修正した方がいいと思います。
Re: クラスの継承について
Posted: 2014年2月05日(水) 12:54
by coc
ご返信有難うございます!
今一まだ上手く理解出来てなく申し訳ないのですが
Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる
これは問題無いでしょうか
Player is a BaseDataと言われると違和感があるのでまだ改善の余地がありそうですが…。
またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
オブジェクト指向から離れてしまっている気がして心配です
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:17
by みけCAT
名前をBaseDataクラスではなく、例えばCharacterクラスにする方がいい気がします。
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:29
by usao
やりたいことは単にこういうことなのだろうか…?
コード:
class Player
{
public:
void Init(); //何かしらPlayerに必要な初期化処理を行う
};
//
class Game
{
public:
void Init()
{
//何かしらGameに必要な初期化処理を行う
...
//その一環として,Playerの初期化処理を行う(Playerに初期化処理を行わせる)
m_PlayerInst.Init();
}
private:
Player m_PlayerInst; //Playerインスタンス.とりあえずこの例ではGameがメンバとして保持.
};
オフトピック
>オブジェクト指向から離れてしまっている気がして心配です
個人的には,"オブジェクト指向がどうこう" とかいう言葉に囚われすぎずに,もっと気を楽に持ったほうが良い気がします.
どうしてもそういう心配をしたいのだとしても,もっと後からしても遅くないかもしれませんし.
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:36
by softya(ソフト屋)
オーバーライド前提なら、純粋仮想関数で良いんじゃないかと言う気もします。
その場合は、共通処理は別のメンバ関数で行う必要ありますですね。
>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
とにかく、親から子のクラスやメンバ関数は見えないし見ては行けないです。
そもそもこういう仕様の必要があるなら継承を使うより委譲を使うべきところなのでは?
【補足】 usaoさんが委譲の例を書いてましたね。
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:41
by みけCAT
横入り(?)失礼します。
softya(ソフト屋) さんが書きました:>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
とにかく、親から子のクラスやメンバ関数は見えないし見ては行けないです。
そもそもこういう仕様の必要があるなら継承を使うより委譲を使うべきところなのでは?
【補足】 usaoさんが委譲の例を書いてましたね。
ここでsoftya(ソフト屋)さんが言っている「親」「子」とは、どういう性質をもつものですか?
cocさんの投稿(No: 3)の自分の解釈(Javaで書くと)
コード:
class Game {
// hoge
}
class BaseData {
// foo
}
class Player extends BaseData {
// fuga
}
class mob extends BaseData {
// piyo
}
は間違っているでしょうか?
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:43
by softya(ソフト屋)
あれ? 最初のコードはPlayer はGameを継承しているんで、そのつもりで書いたんですが話の流れとズレてますか?
編集を間違ったので修正。
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:46
by みけCAT
softya(ソフト屋) さんが書きました:あれ? 最初のコードはPlayer はGameを継承しているんで、そのつもりで書いたんですが話の流れとズレてますか?
coc さんが書きました:またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
この発言の「この場合」は、最初のコードではなく
coc さんが書きました:Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる
という場合だと解釈しましたが、話の流れとズレているでしょうか?
Re: クラスの継承について
Posted: 2014年2月05日(水) 13:51
by softya(ソフト屋)
失礼しました。
Gameには何も継承させない
新たにBaseDataクラスを作成(これは座標やモデルハンドルなどをメンバー変数に持たせる
BaseDataの下にPlayerやmobなどのクラスを継承させる
これが前提ですね。
とすると、
>またこの場合Game::InitからBaseData::InitやPlayer::Initを呼び出して良いものなのでしょうか?
委譲しないと呼び出せないですね。
つまり、GameがPlayerのインスタンスを持つって事です。これはusaoさんの書かれたとおりです。
Initをstaticにしても良いですが、こはもうオブジェクト指向的に書きたという意図から外れていくばかりだと思います。
Re: クラスの継承について
Posted: 2014年2月05日(水) 16:13
by coc
現在皆様のご意見を元にBaseDataをBaseCharacterとして
コード:
#pragma once
//キャラのベースとなるクラス
class CBaseCharacter{
private:
public :
CBaseCharacter();
void Initialize(); //初期化処理
//void Finalize(); //終了処理
void Update(); //更新処理
void Draw(); //描画処理
};
このようにヘッダを作り、Gameからusaoさんの提示して下さったコードを参考に
Game.h
コード:
#pragma once
#include "BaseScene.h"
#include "ISceneChanger.h"
#include "BaseCharacter.h"
//ゲーム画面クラス
class Game : public BaseScene {
private:
CBaseCharacter m_BaseCharacter;
public :
/*Game::*/Game(ISceneChanger* changer);
void Initialize(); //初期化処理をオーバーライド。
//void Finalize() override; //終了処理をオーバーライド。
void Update() override; //更新処理をオーバーライド。
void Draw() override; //描画処理をオーバーライド。
};
としてGame::Init内でCBaseのInitを呼び出す事に成功しました。
またPlayer.hは
コード:
#pragma once
#include "BaseCharacter.h"
//プレーヤーのクラス
class CPlayer : public CBaseCharacter {
public :
//Player::Player();
void Initialize(); //初期化処理。
void Finalize(); //終了処理。
void Update(); //更新処理。
void Draw(); //描画処理。
};
としてBaseCharacterを継承しています。
この状態でCPlayerのInitを呼び出す一般的な方法を教えて頂けると助かります。
最初CBaseCharacterをインタフェースクラスにして初期化などを純粋仮想関数に、ヘッダにenumで{Player,Mob・・・}などを記載しておきfor文などでじゅんぐりに呼び出す方法を考えましたが、CBaseCharacterをインタフェースクラスにするとメンバ変数が置けなくなるためこれでは駄目だなと。
次にCPlayerもCBaseCharacterからの継承でなく委譲関係にしたら問題無いと考えましたが、これまで委譲にするともう全部委譲になってしまうような気がして。
何か単純な方法に気付けていないだけな気がしてなりませんが、一般的に継承先から継承元を参照するのでそもそもGameとCBaseCharacterを委譲関係にするのではなく、GameとCPlayerやMobなどを委譲関係にすべきなのでしょうか?
Re: クラスの継承について
Posted: 2014年2月05日(水) 16:24
by softya(ソフト屋)
この形だとCBaseCharacterのインスタンスとCPlayerのインスタンスは一切の関わりがないのでCPlayer::Initは呼び出す事は不可能ですね。
>何か単純な方法に気付けていないだけな気がしてなりませんが、一般的に継承先から継承元を参照するのでそもそもGameとCBaseCharacterを委譲関係にするのではなく、GameとCPlayerやMobなどを委譲関係にすべきなのでしょうか?
やりたいことを説明出来ていないので、みなさんの回答がcocさんの望むものとズレるんだと思います。
現状で出来なくて困ることはなんでしょうか?
例えば、GameクラスでCBaseCharacter ではなくCPlayer のインスタンスを持つと出来ないことは?
Re: クラスの継承について
Posted: 2014年2月05日(水) 16:31
by usao
ひょっとして 仮想関数 についてちゃんと調べられると良いのかも.
>CBaseCharacterをインタフェースクラスにするとメンバ変数が置けなくなるためこれでは駄目だなと
別に基底クラス側を 「純粋仮想関数しか持たないクラス」 にすることに
こだわらなくてもよいのではないかと思いますが,どうなんでしょう.
コード:
//やや謎な例ですが,別に仮想クラスでもメンバ変数を持ちたければ持てばいい.
//武器を用いてターゲットを殴るクラス用の基底.
//実際に殴りかかる人を表すクラスはこれから派生し,
//オリジナリティあふれる殴り方を仮想関数Attack()に実装する.
class CAttackerBase
{
public:
virtual ~CAttackerBase() {}
public:
//現在の武器を用いてでターゲットを殴る.
//実際にどのように殴るのか{横殴り?打ち下ろし?etc}は派生先で実装する.
//[戻り値]:無事に殴れたらtrueを返すこと.
virtual bool Attack( Tgt & ) = 0;
//Attackerは武器を装備できる
void EquipWeapon( const Weapon &rWeapon ){ m_CurrentWeapon = rWeapon; }
const Weapon &GetCurrentWeapon() const { return m_CurrentWeapon; }
private:
Weapon m_CurrentWeapon; //現在装備中の武器
};
一応継承してるコードを追記.
► スポイラーを表示
コード:
//殴り屋1号.フルスイングで殴るので威力がでかいがミスが多い.
class CAttackerType1 : public CAttackerBase
{
public:
virtual bool Attack( Tgt &rTgt )
{
const int Damage = GetCurrentWeapon().GetPower() * 3; //なんと武器本来の3倍のダメージをたたき出す
if( 乱数で50% ) //…んだけど,ミスが多いんだなぁ
{
rTgt.Damage( Damage ); //ターゲットにダメージ!
return true; //攻撃成功
}
else { return false; } //ミス
}
};
//殴り屋2号.確実に殴る仕事人だが,威力は弱め.
class CAttackerType2 : public CAttackerBase
{
public:
virtual bool Attack( Tgt &rTgt )
{ //威力は弱いのだが確実に相手の体力を削る
const int Damage = GetCurrentWeapon().GetPower() / 2;
rTgt.Damage( Damage ); //ターゲットにダメージ!
return true; //攻撃成功
}
};
//
int main()
{
Tgt TgtInst;
CAttacker1 at1;
CAttacker2 at2;
CAttackerBase *Attackers[2] = { &at1, &at2 };
//二人でタコ殴り
for( int i=0; i<2; i++ )
{ Attackers[i]->Attack( TgtInst ); }
...
}
Re: クラスの継承について
Posted: 2014年2月05日(水) 16:37
by coc
現状はまだ全然中身が無いために、CPlayerのインスタンスを持たせれば問題無いです。
ただこれからどんどん継承先のクラスが多岐に渡り増えていくと思われるため、クラス継承の枝先を悉くGameに集めてしまう事は避けたいと思いました。
頭の中の考えでは
Game → 3つほど大まかなオブジェクトで分類し、それぞれの初期化など関数を呼び出し → 3つのオブジェクト関数の中から更に枝分かれした関数の呼び出し → ・・・・
という形です。
恐らくこの考えがおかしいから皆様と食い違っているのだと思います。
一般的にこのように枝分けされた場合、枝先を呼び出していくにはどのように記述していけば宜しいのでしょうか
Re: クラスの継承について
Posted: 2014年2月05日(水) 16:49
by softya(ソフト屋)
えーと、やっぱり良くわからないです。
こういうのが出来たらOKなんですかね?
コード:
#include<iostream>
class CBaseCharctor {
public :
CBaseCharctor::CBaseCharctor() {
std::cout << "CBaseCharctor()\n";
}
virtual void Initialize() {
std::cout << "CBaseCharctor::Initialize()\n";
}
};
class CPlayer : public CBaseCharctor {
public :
CPlayer::CPlayer() {
std::cout << "CPlayer()\n";
}
void Initialize() {
CBaseCharctor::Initialize();
std::cout << "CPlayer::Initialize()\n";
}
};
int main() {
CPlayer player;
player.Initialize();
return 0;
}
それともこっち?
コード:
#include<iostream>
class CBaseCharctor {
public :
CBaseCharctor::CBaseCharctor() {
std::cout << "CBaseCharctor()\n";
}
virtual void Initialize() {
std::cout << "CBaseCharctor::Initialize()\n";
}
};
class CPlayer : public CBaseCharctor {
public :
CPlayer::CPlayer() {
std::cout << "CPlayer()\n";
}
void Initialize() {
CBaseCharctor::Initialize();
std::cout << "CPlayer::Initialize()\n";
}
};
int main() {
CBaseCharctor *baseChar;
baseChar = new CPlayer();
baseChar->Initialize();
return 0;
}
Re: クラスの継承について
Posted: 2014年2月05日(水) 17:42
by ISLe
質問者さんは、実行時にどのタイミングでオブジェクトを生成するのかという点についても曖昧なのではないでしょうかね。
各オブジェクトのライフサイクルをガントチャートふうに図に描いてみたら分かりやすくなるかもしれません。
Gameクラスが継承しているのはBaseSceneクラスなので、Gameクラスはシーンを表しているのでしょう。
わたしなら、プレイヤークラスの生成・管理はさらに上位のクラスで行うようにすると思います。
新・ゲームプログラミングの館で言うとSceneMgrになってしまいますが、シーンの変化を検知してオブジェクトの生成・破棄を行うオブジェクトマネージャを新設するのが理想です。
Initialize,Finalizeはわざわざ呼び出さなくてもそれぞれコンストラクタ,デストラクタを使えば良いと思います。
Re: クラスの継承について
Posted: 2014年2月05日(水) 18:51
by ISLe
館のコードに合わせるなら、Gameクラスの中でさらにシーンを細分化する方向がありますね。
そうしたら、プレイヤークラスは単純にGameクラスのメンバで良いですね。
コード:
class Game : public BaseScene {
private:
CPlayer m_Player;
public :
Game(ISceneChanger* changer);
void Initialize() override;
void Finalize() override;
void Update() override;
void Draw() override;
};
Game::Game(ISceneChanger* changer) : m_Player()
{
}
void Game::Initialize()
{
m_Player.Initialize();
}
No.5でusaoさんが書いたのと同じことですけど。
メンバが増えていくことを懸念されているんでしたっけ。
Re: クラスの継承について
Posted: 2014年2月05日(水) 20:44
by coc
ISLeさんのおっしゃる様にGameクラスを更に細分化していく方向に決めました。
それとは別ながらクラスの継承についての問題なのですが、
GameクラスでPlayerとMob[2]のインスタンスを生成
BaseCharaから継承したPlayerクラスに座標などの情報を保持
BaseCharaから継承したMob[2]クラスに座標などのを保持(for文でGameから複数回呼び出す事で初期化)
この時BaseCharaに当たり判定を行うPrivateな関数を用意する予定でした。(引数はVector型の座標を二セット)
しかし実際組んでみると今更ながらPlayerとMobクラスは互いに関係性を持たないため、自身の座標は送れても相手の座標を見る事が出来ないという現実に気付きました。
ISLeさんのおっしゃられた様に紙に関係図を書いてみたのですが、CameraやStageクラスを何処に配置するかについても困っております。
(今はどちらもPlayerと委譲関係にしてありますが)
このような状態
BaseChara
↓
Game → Player ← Camera,Stage
→ Mob
クラスの組み方がさっぱりです・・・
Re: クラスの継承について
Posted: 2014年2月05日(水) 21:22
by softya(ソフト屋)
GameがPlayerとMobを知っているので、当たり判定もGameが呼び出して行えばよいでしょう。
result = collision(Player.GetPoint(),Mob.getPoint());
Camera,StageもGameが管理して良いと思います。Gameってゲームシーンの全体を統括するシーンマネージャなんですよね?
Re: クラスの継承について
Posted: 2014年2月06日(木) 09:52
by usao
>CameraやStageクラスを何処に配置するか
とりあえず 現状では,
「Gameがアクティブなシーンになっている間だけ必要なもの」
は全て Gameがメンバ変数として持つ という形を考えたら楽になりませんか?(気持ち的な意味で)
>当たり判定
CBaseCharactor(の派生)同士であれば,当たり判定を行う方法が共通 とかいう状況であれば
コード:
class CBaseCharactor
{
...
public:
//CollisionCheckData は実際は何になるのかわからないけど
//(例えば 矩形だとか,中心座標だとか?) とにかく当たり判定計算に必要なデータ.
virtual CollisionCheckData GetCollisionCheckData() const = 0;
};
//当たり判定関数
//※戻り値の型は何がいいのかよくわからんけど
int CollisionCheck( const CBaseCharactor &rObj1, const CBaseCharactor &rObj2 )
{
CollisionCheckData D1 = rObj1.GetCollisionCheckData();
CollisionCheckData D2 = rObj2.GetCollisionCheckData();
D1とD2を使って判定を行い,その結果を返す
}
みたいなことでよいのではないかなぁ.
オフトピック
>CameraやStageクラスを何処に配置するか
とかは,「とりあえず」「どこか(自分がここかな?と思う場所あたり)」に配置してやってみて
その結果,「ここにあると不便」とか「ここじゃどうしようもない」だとか「ここにあればすんなりいって良い予感」とかいう
手応え(?)が得られたならば,それに沿って修正するなりそのままいくなりを決めていければ良いと思うのです.
というのは,経験のある方々からの 「良い(ように見える)」回答 を参考にするにしても,
それが「何故良いのか」という理由の部分を掴むことが重要だと思うので.
そのためには,「これこれこういう考えのもとで,実際こうしてみたんだけど,それだと××だった」という試行錯誤というか
段階があった方がいいんじゃないかな…とか.(余計なお世話かもですが.)
Re: クラスの継承について
Posted: 2014年2月06日(木) 18:06
by ISLe
クラスの組み方ではなくて、オブジェクトの関係を整理するほうが先決なのでは。
同じクラスを継承すれば勝手に連携できるようになる、と考えているならば間違いです。
上位にマネージメントするクラスがあって、そのクラスとやり取りするクラスがある。
そのやり取りのインターフェースを共通化する、というのが基底クラスの役割りです。
やり取りを共通化する見通しが立ってないのに形だけ基底クラスを用意しても意味がありません。
参考になるか分かりませんが、この掲示板でも過去にこの手の設計の話題が何度か出ています。
『天の声』とか『神の視点』で過去ログを検索してみてください。
Re: クラスの継承について
Posted: 2014年2月10日(月) 17:55
by coc
皆さんご返事有り難う御座います。
おっしゃって頂いた様に、取り敢えず組んでみて駄目だったら改善していく手法で今回はチャレンジしてみます。