SUPER MAOU LANDで使っているカスタムRTTI
SUPER MAOU LANDで使っているカスタムRTTIの自分用メモ。
(SUPER MAOU LAND作ってます)
もともとはUnityっぽくGetComponent
そのためにカスタムRTTIが必要になったので、その実装内容をメモしておく。
カスタムRTTIとかの前にC++でどうやってGetComponent
するとインスタンスに対してHoge型かどうかを尋ねる関数は必要だと思う。
なのでまずこんな感じの関数を作ってみる。
//キャスト可能ならtrue template<class T> bool IsA(Component *component){ return (component->getRTTI() == T::TYPE()); }
テンプレートを使うことによって、インスタンス(component)の型情報と、任意の型(T)の型情報の比較をしてるだけ。
すると、キャストする関数はこんな感じに書ける。
//キャスト(変換できないならnullptr) template<class T> T* Cast(Component *component){ if( IsA<T>(component) ){ return static_cast<T*>(component); } return nullptr; }
先ほど定義したIsA()を呼び出して、trueならstatic_castするだけ。
これをうまく使えばGetComponent
さて、IsA()の実装内容からして、オレオレComponentにはgetRTTI()とTYPE()の実装が必要になった。
つまり、こんな感じの実装にしとく必要がある
class ComponentBase{ public: using RTTI = const std::string *; //カスタムRTTIの実体は文字列のポインタ virtual RTTI getRTTI() const = 0; //実装は子供に任せます }; //コンポーネントその1 class HogeComponent : public ComponentBase{ public: RTTI getRTTI() const override{ return TYPE(); //実装内容はTYPE()に任せます } static RTTI TYPE(){ //s_typeが型情報になります。他の型情報とかぶらない様に注意してください static const std::string s_type = "HogeComponent"; return &s_type; } }; //コンポーネントその2 class FugaComponent : public ComponentBase{ public: RTTI getRTTI() const override{ return TYPE(); //実装内容はTYPE()に任せます } static RTTI TYPE(){ //s_typeが型情報になります。他の型情報とかぶらない様に注意してください static const std::string s_type = "FugaComponent"; return &s_type; } };
getRTTI()はインスタンスに対して呼び出されるので、仮想関数として定義しときます。
TYPE()は、T::TYPE()の様に呼び出されるので、staticメンバ関数として定義します。
さて、ここでもう一度IsA()の実装を振り返って、実装内容を確認します。
例えば、IsA
bool IsA(Component *component){ return (component->getRTTI() == HogeComponent::TYPE()); }
インスタンスが持つ型情報と、型が持つ型情報が比較されています。
componentがHogeComponentであれば、getRTTI()とTYPE()が同じものを返すので、trueが返ります。
また、componentがFugaComponentであれば、getRTTI()とTYPE()が違うものを返すので、falseが返ります。
実際には、getRTTI()とTYPE()の定義がメンドイので、マクロにして使っています。
#define IMPLEMENT_COMPONENT_RTTI(className) \ ComponentBase::RTTI getID() const override{ \ return TYPE(); \ } \ static ComponentBase::RTTI TYPE(){ \ static const std::string s_type = #className; \ return &s_type; \ } \
すると、実装はこんな感じになります。
class ComponentBase{ public: using RTTI = const std::string *; //カスタムRTTIの実体は文字列のポインタ virtual RTTI getRTTI() const = 0; //実装は子供に任せます }; //コンポーネントその1 class HogeComponent : public ComponentBase{ //ここに好きなように色々書く public: IMPLEMENT_COMPONENT_RTTI(HogeComponent); }; //コンポーネントその2 class FugaComponent : public ComponentBase{ //ここに好きなように色々書く public: IMPLEMENT_COMPONENT_RTTI(FugaComponent); };
わぁ、かんたん!
この仕組みはゲーム中のイベントとかにも持たせておくと、イベント毎の処理を捌きやすくなって色々と捗る。
マクロは好きくないけど他にいいやりかたが思いつかなかったのでこれでいいや、って感じ。
かしこいカスタムRTTIの実装だと継承関係とか辿ったりとかやるんだろうけど個人製作のゲームでそこまでいらんだろーって事で、ひとまずこれで。
つーかそもそもdynamic_cast使っちゃえばこんなもん実装する必要はないのだけれど・・・。
あとはこの仕組みを使ってGetComponent
後で書く。・・・といいなぁ。