ダウンキャストはやっていいの?
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
ダウンキャストはやっていいの?
データフローの中でどうしてもダウンキャストをしてしまう箇所があるのですが、どうしたらいいでしょうか?
Objectクラス
↓
継承
↓
Entityクラス→当たり判定を委託→ContactAreaクラス
↓
継承
↓
Characterクラス
というクラス関係で、「ContactAreaクラス」のポインタを「ContactAreaListクラス」に登録し、
当たり判定リストクラス::Run()という関数が呼び出されると一括ですべてを判定して、当たっていたらフィードバックをEntityクラスに返すというものです。
(ContactAreaListに登録されたContactAreaクラス同士で判定を行い、持ち主(Entity)に結果を伝える)
このような相関図で攻撃判定を行うのですが、ContactAreaクラスは他のContactAreaクラスと衝突した時、
無条件に持ち主(Entity)に「この相手(ContactAreaクラス)と衝突した」と結果を持ってきます。
この時、そのEntity以外がすべて敵なら持ってきたすべてのContactAreaクラスに対しダメージ処理をすれば楽チンなのですが、
Characterクラスが保持している勢力情報(int型ですが)によって分別してダメージ処理したいので必然的にEntity→Characterクラスへダウンキャストして勢力情報を取得する必要があります。
こういった場合ダウンキャストは適切なのでしょうか?
文章だけでわかりにくいかもしれませんがよろしくお願いします。
Objectクラス
↓
継承
↓
Entityクラス→当たり判定を委託→ContactAreaクラス
↓
継承
↓
Characterクラス
というクラス関係で、「ContactAreaクラス」のポインタを「ContactAreaListクラス」に登録し、
当たり判定リストクラス::Run()という関数が呼び出されると一括ですべてを判定して、当たっていたらフィードバックをEntityクラスに返すというものです。
(ContactAreaListに登録されたContactAreaクラス同士で判定を行い、持ち主(Entity)に結果を伝える)
このような相関図で攻撃判定を行うのですが、ContactAreaクラスは他のContactAreaクラスと衝突した時、
無条件に持ち主(Entity)に「この相手(ContactAreaクラス)と衝突した」と結果を持ってきます。
この時、そのEntity以外がすべて敵なら持ってきたすべてのContactAreaクラスに対しダメージ処理をすれば楽チンなのですが、
Characterクラスが保持している勢力情報(int型ですが)によって分別してダメージ処理したいので必然的にEntity→Characterクラスへダウンキャストして勢力情報を取得する必要があります。
こういった場合ダウンキャストは適切なのでしょうか?
文章だけでわかりにくいかもしれませんがよろしくお願いします。
Re: ダウンキャストはやっていいの?
文章だけなのと継承関係の矢印の向きがはっきりしない(想像はつきますが)のでよくわかりませんが、
当たり判定を出した時情報を欲しいから、Character型にキャストするのは良いのか、という話だと解釈します。
結論から言うと、良いとは思えません。
インターフェイスを使ってはどうでしょう。
Entityクラス・・“実体クラス”?そのあたりの関係性がよくわからないのですが。
例を挙げるなら、
まずEnergyInformationクラスとCollisionInformationクラスを用意します。
EnergyInformationクラスに勢力情報を取得する純仮想関数の宣言し、CollisionInformationが更にそれを継承(勢力情報を取得する関数はまだ定義しない)、ここには当たり判定に必要な関数を純仮想関数で宣言します。そしてCharacterクラスより下の具体クラスで全て定義します。
Entityには自分(Character)のthisポインタをCollisionInformationとして渡し(暗黙的なアップキャスト)、当たり判定で当たっていた場合、接触元へ返す際にCollisionInformationのポインタをEnergyInformationとして返し(暗黙的なアップキャスト)、Entityではそれを介して得た情報を元に計算すれば良いのでは?
当たり判定を出した時情報を欲しいから、Character型にキャストするのは良いのか、という話だと解釈します。
結論から言うと、良いとは思えません。
インターフェイスを使ってはどうでしょう。
Entityクラス・・“実体クラス”?そのあたりの関係性がよくわからないのですが。
例を挙げるなら、
まずEnergyInformationクラスとCollisionInformationクラスを用意します。
EnergyInformationクラスに勢力情報を取得する純仮想関数の宣言し、CollisionInformationが更にそれを継承(勢力情報を取得する関数はまだ定義しない)、ここには当たり判定に必要な関数を純仮想関数で宣言します。そしてCharacterクラスより下の具体クラスで全て定義します。
Entityには自分(Character)のthisポインタをCollisionInformationとして渡し(暗黙的なアップキャスト)、当たり判定で当たっていた場合、接触元へ返す際にCollisionInformationのポインタをEnergyInformationとして返し(暗黙的なアップキャスト)、Entityではそれを介して得た情報を元に計算すれば良いのでは?
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
糸冬
――――――――
制作・著作 NHK
――――――――
制作・著作 NHK
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
Re: ダウンキャストはやっていいの?
回答ありがとうございます。
Entityというのはゲーム中に表現できるすべてのもののベースとして作りました。画像・当たり判定のどちらかを持つものはすべてこれから派生しますが、画像・当たり判定を持つクラスを保持するだけでその機能をもっているわけじゃないです。(壁やキャラクターや武器)
EntityとEntityから派生するクラス諸々は当たり判定に必要な情報をまったくもっておらず、Entityのメンバ変数であるContactAreaクラスが持ってます。(部位破壊や部位別ダメージみたいな表現をしたかった)
わかりづらくてすいません。
役に立たないかもしれませんが、攻撃からダメージ処理までの流れはこんなかんじです。
例では前提として攻撃したいEntityと攻撃を受けたいEntityそれぞれContactAreaを1つだけ持ってます。
①攻撃したいEntityが攻撃属性を持ったContactAreaをContactAreaListに登録する。
②攻撃を受けたいEntityが被害属性を持ったCotnactAreaをContactAreaListに登録する。
③ContactAreaList::Runを実行し、接触していたら【攻撃したいEntityのContactArea】に【攻撃を受けたいEntityのContactArea】のポインタを渡します。
④【攻撃したいEntityのContactArea】が受け取った【攻撃を受けたいEntityのContactArea】のポインタと、攻撃したいEntityのどのContactAreaが接触したかを自分の親である攻撃したいEntityに渡します。
⑤④で攻撃したいEntityが受け取った【攻撃を受けたいEntityのContactArea】のポインタと、【攻撃したいEntityのContactArea】という情報からダメージ量を計算して【攻撃を受けたいEntityのContactArea】に、ダメージ量と、接触した【攻撃したいEntityのContactArea】のポインタを渡します。
⑥【攻撃を受けたいEntityのContactArea】が受け取ったダメージ量とダメージ量と、接触した【攻撃したいEntityのContactArea】のポインタと、どのContactAreaが接触したかを自分の親である攻撃を受けたいEntityに渡します。
⑦攻撃を受けたいEntityが受け取った【攻撃したいEntityのContactArea】のポインタとダメージ量と【攻撃を受けたいEntityのContactArea】から、最終的に自分が受けるダメージを計算し、その場で適用します。
③ ④ ⑤ ⑥ ⑦
ContactArea::Run → AttackEntity::ContactArea → AttackEntity → DefenseEntity::ContactArea → DefenseEntity
上記の部位別ダメージのせいもあって、ダメージ処理自体は継承元から受け継いだ仮想関数内でやりたいためEntityクラス内での処理はできません。(純粋仮想関数ではないので継承先で何も定義しなければダメージ処理はできそうですけど)
クラスを活用したゲームの1作目ということもあってゲームの設計自体掻い摘んで学んできたことなので変でしたら設計の方も、もしよろしかったらご指摘お願いします。
えーと、位置的にはEntityクラスとCharacterクラスの間にEnergyInformationクラスとCollisionInformationクラスが入るような形でしょうか。新月の獅子 さんが書きました: まずEnergyInformationクラスとCollisionInformationクラスを用意します。
EnergyInformationクラスに勢力情報を取得する純仮想関数の宣言し、CollisionInformationが更にそれを継承(勢力情報を取得する関数はまだ定義しない)、ここには当たり判定に必要な関数を純仮想関数で宣言します。そしてCharacterクラスより下の具体クラスで全て定義します。
Entityには自分(Character)のthisポインタをCollisionInformationとして渡し(暗黙的なアップキャスト)、当たり判定で当たっていた場合、接触元へ返す際にCollisionInformationのポインタをEnergyInformationとして返し(暗黙的なアップキャスト)、Entityではそれを介して得た情報を元に計算すれば良いのでは?
Entityというのはゲーム中に表現できるすべてのもののベースとして作りました。画像・当たり判定のどちらかを持つものはすべてこれから派生しますが、画像・当たり判定を持つクラスを保持するだけでその機能をもっているわけじゃないです。(壁やキャラクターや武器)
EntityとEntityから派生するクラス諸々は当たり判定に必要な情報をまったくもっておらず、Entityのメンバ変数であるContactAreaクラスが持ってます。(部位破壊や部位別ダメージみたいな表現をしたかった)
わかりづらくてすいません。
class ContactArea : public Object{};
class Entity : public Object{
CContactArea ContactArea;
};
例では前提として攻撃したいEntityと攻撃を受けたいEntityそれぞれContactAreaを1つだけ持ってます。
①攻撃したいEntityが攻撃属性を持ったContactAreaをContactAreaListに登録する。
②攻撃を受けたいEntityが被害属性を持ったCotnactAreaをContactAreaListに登録する。
③ContactAreaList::Runを実行し、接触していたら【攻撃したいEntityのContactArea】に【攻撃を受けたいEntityのContactArea】のポインタを渡します。
④【攻撃したいEntityのContactArea】が受け取った【攻撃を受けたいEntityのContactArea】のポインタと、攻撃したいEntityのどのContactAreaが接触したかを自分の親である攻撃したいEntityに渡します。
⑤④で攻撃したいEntityが受け取った【攻撃を受けたいEntityのContactArea】のポインタと、【攻撃したいEntityのContactArea】という情報からダメージ量を計算して【攻撃を受けたいEntityのContactArea】に、ダメージ量と、接触した【攻撃したいEntityのContactArea】のポインタを渡します。
⑥【攻撃を受けたいEntityのContactArea】が受け取ったダメージ量とダメージ量と、接触した【攻撃したいEntityのContactArea】のポインタと、どのContactAreaが接触したかを自分の親である攻撃を受けたいEntityに渡します。
⑦攻撃を受けたいEntityが受け取った【攻撃したいEntityのContactArea】のポインタとダメージ量と【攻撃を受けたいEntityのContactArea】から、最終的に自分が受けるダメージを計算し、その場で適用します。
③ ④ ⑤ ⑥ ⑦
ContactArea::Run → AttackEntity::ContactArea → AttackEntity → DefenseEntity::ContactArea → DefenseEntity
上記の部位別ダメージのせいもあって、ダメージ処理自体は継承元から受け継いだ仮想関数内でやりたいためEntityクラス内での処理はできません。(純粋仮想関数ではないので継承先で何も定義しなければダメージ処理はできそうですけど)
クラスを活用したゲームの1作目ということもあってゲームの設計自体掻い摘んで学んできたことなので変でしたら設計の方も、もしよろしかったらご指摘お願いします。
Re: ダウンキャストはやっていいの?
HUM...LisetteLander さんが書きました:えーと、位置的にはEntityクラスとCharacterクラスの間にEnergyInformationクラスとCollisionInformationクラスが入るような形でしょうか。新月の獅子 さんが書きました: まずEnergyInformationクラスとCollisionInformationクラスを用意します。
EnergyInformationクラスに勢力情報を取得する純仮想関数の宣言し、CollisionInformationが更にそれを継承(勢力情報を取得する関数はまだ定義しない)、ここには当たり判定に必要な関数を純仮想関数で宣言します。そしてCharacterクラスより下の具体クラスで全て定義します。
Entityには自分(Character)のthisポインタをCollisionInformationとして渡し(暗黙的なアップキャスト)、当たり判定で当たっていた場合、接触元へ返す際にCollisionInformationのポインタをEnergyInformationとして返し(暗黙的なアップキャスト)、Entityではそれを介して得た情報を元に計算すれば良いのでは?
Entityというのはゲーム中に表現できるすべてのもののベースとして作りました。画像・当たり判定のどちらかを持つものはすべてこれから派生しますが、画像・当たり判定を持つクラスを保持するだけでその機能をもっているわけじゃないです。(壁やキャラクターや武器)
EntityとEntityから派生するクラス諸々は当たり判定に必要な情報をまったくもっておらず、Entityのメンバ変数であるContactAreaクラスが持ってます。(部位破壊や部位別ダメージみたいな表現をしたかった)
わかりづらくてすいません。
>えーと、位置的にはEntityクラスとCharacterクラスの間にEnergyInformationクラスとCollisionInformationクラスが入るような形でしょうか。
というのは違いますかね。C++では多重継承が許されていますので、間に入れるというより、・・・実際にコードで見せたほうが早いですかね
/// とりあえず勢力情報が構造体だとして、
struct CharactorEnergy
{
};
class Entity
{
};
class EnergyInformation
{
public:
virtual const CharactorEnergy& getCharactorEnergy() const = 0; //< 勢力情報を取得するインターフェイス
};
class CollisionInformation
:public EnergyInformation
{
public:
virtual int getCollision() = 0; //< 当たり判定に必要な関数(てきとう)
};
class Character
:public Entity
,public CollisionInformation
{
private:
CharactorEnergy infomationEnergy;
public:
const CharactorEnergy& getCharactorEnergy() const { return this->infomationEnergy; }
int getCollision() { return 0; }
};
/// とりあえず勢力情報が構造体だとして、
struct CharactorEnergy
{
};
/// 勢力情報を取得するインターフェイス
class EnergyInformation
{
public:
virtual const CharactorEnergy& getCharactorEnergy() const = 0; //< 勢力情報を取得するインターフェイスメソッド
};
class ContactArea
{
public:
/// Runでは勢力情報を受け取るインターフェイスを引数にしてみる
/// 勢力情報がどこで使用されるのか説明がないのですがおそらく攻撃を受けたいEntityの内部だと思うのです、そこへContactAreaを介してEnergyInformationを攻撃を受けたいEntityに渡せば良いのでは?
void Run( EnergyInformation* energyInformation )
{
energyInformation->getCharactorEnergy(); //< これを渡す
}
};
class Entity
{
private:
ContactArea contactArea; //< 当たり判定に関する情報や処理はこちらで行う?
protected:
ContactArea& getContactArea(){ return this->contactArea; }
public:
virtual void test() = 0;
};
class Character
:public Entity
,public EnergyInformation
{
private:
CharactorEnergy infomationEnergy; //< 勢力情報
public:
const CharactorEnergy& getCharactorEnergy() const { return this->infomationEnergy; } //< インターフェイスの実装
void test()
{
this->getContactArea().Run( this );
}
};
ダウンキャストの危険性はご存知でしょうか?
Character型にしてしまうと、関係ないもの、存在しないものまでアクセスできてしまうのが非常に危険なんです。危険であると同時に、オブジェクト指向の基礎的な概念であるカプセル化は崩壊します。
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
糸冬
――――――――
制作・著作 NHK
――――――――
制作・著作 NHK
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
Re: ダウンキャストはやっていいの?
Contact::RunはCharacterの処理順によってできるだけ不公平が無いよう1ループ1回呼び出されます。
説明不足でした、すいません。
Characterそれぞれの処理(Update)をしたあとにContactAreaList::Runを実行しContactAreaを経由してEntityの純粋仮想関数にFeedbackしてそれぞれのCharacterが振る舞いを決めます。
mainループ
↑一括
Entity
Character
ContactAreaList
ContactArea
ひょっとすると、もうEntityはインターフェースクラスということを割りきってすべての継承先で必要になる純粋仮想関数を宣言しといたほうがいいのでしょうか?
つまり、壁だろうが人だろうが全部同じ関数を使って処理して、プレイヤーキャラクターだけアドレスをもらって操作する・・・みたいな・・・
説明不足でした、すいません。
Characterそれぞれの処理(Update)をしたあとにContactAreaList::Runを実行しContactAreaを経由してEntityの純粋仮想関数にFeedbackしてそれぞれのCharacterが振る舞いを決めます。
mainループ
int main(){
CContactAreaList ContactAreaList;
CCharacter CharacterFriend(&ContactAreaList);
CCharacter CharacterEnemy(&ContactAreaList);
while(/* */){
CharacterFriend.Update();
CharacterEnemy.Update();
ContactAreaList::Run();
}
}
Entity
class CEntity : public CObject{//CObjectは座標情報があるだけ
protected:
CContactArea ContactArea;
CContactAreaList* ContactAreaList;
public:
virtual void Feedback(ContactArea& ,int)=0;
virtual void Update()=0;
}
class CCharacter : public CEntity{
public:
CCharacter(*CContactAreaList tmp){
ContactAreaList=tmp;
//他ContactAreaの設定
}
virtual void Update(){
ContactAreaList->Entry(ContactArea);
}
virtual void Feedback(ContactArea* ContactArea){
/*ダメージ処理・勢力判定*/
}
};
class CContactAreaList{
private:
vector <CContactArea*> List;
public:
void Entry(){}//省略
void Run(){
if(/*色々やって、当たっていたら*/){
ContactArea[攻撃]->NotifiAgency(ContactArea[被害]);//攻撃したContactAreaに被害を与えたContactAreaのポインタを渡す。
}
}
};
class ContactArea : public Object{
CCharacter* Owner;
public:
void NotifiAgency(CContactArea* tmp){Owner->Feedback(tmp);}
};
つまり、壁だろうが人だろうが全部同じ関数を使って処理して、プレイヤーキャラクターだけアドレスをもらって操作する・・・みたいな・・・
Re: ダウンキャストはやっていいの?
あーそういうことでしたか。
んー・・・
インターフェイスは見せるモノを絞るのに使ったほうが良いでしょうし、全部まとめてしまっては意味が無いのでは。
えっとー・・・具体的にどこでダウンキャストしちゃってるんですか?
一向に肝心のダウンキャストの部分が見えないのですが。
んー・・・
細かい仕様分かりませんがあまりイイ事である気はしません。ひょっとすると、もうEntityはインターフェースクラスということを割りきってすべての継承先で必要になる純粋仮想関数を宣言しといたほうがいいのでしょうか?
インターフェイスは見せるモノを絞るのに使ったほうが良いでしょうし、全部まとめてしまっては意味が無いのでは。
えっとー・・・具体的にどこでダウンキャストしちゃってるんですか?
一向に肝心のダウンキャストの部分が見えないのですが。
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
糸冬
――――――――
制作・著作 NHK
――――――――
制作・著作 NHK
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
Re: ダウンキャストはやっていいの?
あ、すいません、コードを間違えてました。
ではなく
です。
例えばCharacterが他のEntityのContactAreaの勢力情報を調べたい時に
そのContactAreaの持ち主であるEntityがCharacterなのか?それともWallなのか、Weaponなのかというサブクラスに落とさないと判断できないということです。
class ContactArea : public Object{
CCharacter* Owner;
public:
void NotifiAgency(CContactArea* tmp){Owner->Feedback(tmp);}
};
class ContactArea : public Object{
CEntity* Owner;
public:
void NotifiAgency(CContactArea* tmp){Owner->Feedback(tmp);}
};
例えばCharacterが他のEntityのContactAreaの勢力情報を調べたい時に
そのContactAreaの持ち主であるEntityがCharacterなのか?それともWallなのか、Weaponなのかというサブクラスに落とさないと判断できないということです。
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
Re: ダウンキャストはやっていいの?
勢力によってダメージを受ける・受けないという判断をするのはCharacter同士だけなのですから、逆に勢力を全く考えないってゲームの仕様を変更したほうが楽ですけど・・・
Re: ダウンキャストはやっていいの?
あーーなるほど
うーんなかなか複雑ですねぇ・・・
そうですね・・・
勢力を考慮するのがキャラどうしだけで、今後仕様が変わらないのなら、ダブルディスパッチはどうでしょう。
こちらを参照してみてください。
これを応用して、ContactAreaのオーナ毎に振る舞いを変えるのも手です。
うーんなかなか複雑ですねぇ・・・
そうですね・・・
勢力を考慮するのがキャラどうしだけで、今後仕様が変わらないのなら、ダブルディスパッチはどうでしょう。
こちらを参照してみてください。
これを応用して、ContactAreaのオーナ毎に振る舞いを変えるのも手です。
✜ で C ご ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
: す + 注 :
¦ か + 文 ¦
: ? Is the は :
✜ order C++? ✜
糸冬
――――――――
制作・著作 NHK
――――――――
制作・著作 NHK
- LisetteLander
- 記事: 147
- 登録日時: 14年前
- 住所: 東京
Re: ダウンキャストはやっていいの?
ありがとうございます、ダブルディスパッチ試してみます。
とりあえずこの質問は解決とさせていただきます。
とりあえずこの質問は解決とさせていただきます。