16239


cocos2d-objCで書いたtiledMapを使ったゲームを、cocos2d-x(v3.7.1)で書き直しています。

objC版では、Tiledのオブジェクトグループからプロパティを読み込んで、  
そのnameプロパティによって、ゲームに追加するキャラクター等オブジェクトのクラスを決定していました。

例えば、nameプロパティには"Player"や"Serpent"（敵キャラ）が設定されており、  
NSClassFromStringでPlayerクラスやSerpentクラスのクラスオブジェクトを取得して、  
オブジェクトを生成しています。

RRGLevel.m

[code]

    for (NSDictionary* property in tiledMap.objectGroup.objects) {
        NSString* name = property[@"name"];
    
        RRGCharacter* character = [RRGCharacter levelObjectWithName:name level:self];
        [character setAttributesWithProperties:property];
    
        int x = [property[@"x"] intValue];
        int y = [property[@"y"] intValue];
        CGPoint tileCoord = [tiledMap tileCoordForTilePoint:ccp(x,y)];
        [self addCharacter:character atTileCoord:tileCoord];
    }
    
[/code]

RRGLevelObject.m (RRGCharacterの親クラス)

[code]

    +(instancetype)levelObjectWithName:(NSString *)name
                                 level:(RRGLevel *)level
    {
        Class class = NSClassFromString(name);
    
        RRGLevelObject* obj = [class levelObjectWithLevel:level];
        NSAssert(obj != nil, @"Invalid name : %@", name);
        return obj;
    }
    
[/code]

しかしcocos2d-xではNSClassFromStringのような関数がないので、  
同様のことをやろうとすると、数十個のクラスについて場合分けを書かねばならず、  
また、クラスが追加された時に、いちいち場合分け文を追加しなければなりません。

cocos2d-xで、Tiledのプロパティに設定したnameから、対応したクラスのオブジェクトを生成する効率的な方法がありましたら教えてください。


CC BY-SA 3.0
2015-09-04T03:37:12.567
16221
2015-09-06T10:50:08.550
8825
question
c++ cocos2d-x
cocos2d-xで文字列によって生成するオブジェクトのクラスを決定する


効率的とは言えないかもしれませんが、C++ではFactory Methodパターンを使う場面だと思います。  
例えば、[Loki::Factory](http://loki-
lib.sourceforge.net/html/a00621.html)を使うと以下のように書けます。

[code]

    enum RRGLevel {RRGLevel_normal};
    
    // RRGCharacter.hpp
    struct RRGCharacter {
        virtual const char *type_name() const = 0;
    };
    
    // RRGCharacterFactory.hpp
    #include <string>
    #include "loki/Factory.h"
    #include "loki/Singleton.h"
    typedef Loki::SingletonHolder<Loki::Factory<RRGCharacter, std::string, Loki::Seq<RRGLevel> > > RRGCharacterFactory;
    
    // Player.hpp
    struct Player: RRGCharacter {
        Player(RRGLevel level) {}
        virtual const char *type_name() const { return "Player"; };
    };
    
    // Player.cpp
    namespace {
        RRGCharacter *createPlayer(RRGLevel level) { return new Player(level); }
        const bool registered = RRGCharacterFactory::Instance().Register("Player", createPlayer);
    }
    
    // Serpent.hpp
    struct Serpent: RRGCharacter {
        Serpent(RRGLevel level) {}
        virtual const char *type_name() const { return "Serpent"; };
    };
    
    // Serpent.cpp
    namespace {
        RRGCharacter *createSerpent(RRGLevel level) { return new Serpent(level); }
        const bool registered = RRGCharacterFactory::Instance().Register("Serpent", createSerpent);
    }
    
    // main.cpp
    #include <iostream>
    #include <string>
    
    int main()
    {
        std::string name("Player");
        RRGLevel level = RRGLevel_normal;
        std::auto_ptr<RRGCharacter> character(
          RRGCharacterFactory::Instance().CreateObject(name, level));
        std::cout << character->type_name() << "\n";
    
        name = "Serpent";
        character.reset(
          RRGCharacterFactory::Instance().CreateObject(name, level));
        std::cout << character->type_name() << "\n";
    }
    
[/code]

◇追記  
`RRGCharacterFactory`は、`Register(識別子,
関数ポインター)`で指定された識別子とそれに対応する関数ポインターを保持し、`CreateObject(識別子,
対応する関数への引数)`で対応する関数を検索して呼び出します。  
従って、キャラクタークラスを増やす場合に`RRGCharacterFactory`を一切変更する必要はありません。  
ファクトリクラスを自分で一々作るのは面倒だし同じような実装になるので、Lokiライブラリではクラステンプレートで汎用的なファクトリクラステンプレートを定義しています。

自分でRRGCharacterFactoryを作る場合は、例えば以下のようになります。

[code]

    // RRGCharacterFactory.hpp
    #include <map>
    #include <stdexcept>
    #include <string>
    #include <utility>
    
    class RRGCharacterFactory
    {
      public:
        typedef RRGCharacter *(*CharacterCreator)(RRGLevel);
      private:
        typedef std::map<std::string, CharacterCreator> NameToCharacterMap;
    
      public:
        //! @attention  コンパイラーによってはスレッドアンセーフ
        static RRGCharacterFactory &Instance() {
            static RRGCharacterFactory instance;
            return instance;
        }
    
      private:
        NameToCharacterMap m_associations;
    
      private:
        RRGCharacterFactory() {}
        ~RRGCharacterFactory() {}
        RRGCharacterFactory(const RRGCharacterFactory &);
        RRGCharacterFactory &operator=(const RRGCharacterFactory &);
    
      public:
        //! @attention  スレッドアンセーフ
        bool Register(const std::string &name, CharacterCreator creator) {
            return m_associations.insert(
              std::make_pair(name, creator)).second;
        }
    
        //! @attention  スレッドアンセーフ
        bool Unregister(const std::string &name) {
            return m_associations.erase(name) != 0;
        }
    
        RRGCharacter *CreateObject(const std::string &name, RRGLevel level) {
            NameToCharacterMap::const_iterator i = m_associations.find(name);
            if ( i == m_associations.end() ) {
                throw std::invalid_argument("Unknown Name");
            }
            return (i->second)(level);
        }
    };
    
[/code]


CC BY-SA 3.0
2015-09-04T07:11:47.060
16239
2015-09-06T10:50:08.550
2015-09-06T10:50:08.550
7291
7291
16221
answer