【问题标题】:Storing and accessing tile properties in C++在 C++ 中存储和访问磁贴属性
【发布时间】:2023-04-03 23:05:02
【问题描述】:

我正在用 C++ 构建一个磁贴引擎。在游戏中存储各个图块的逻辑属性的最有效方法是什么?我了解程序的渲染方面,但我无法将简单的 id 号转换为一组实际的属性(例如瓷砖是否可行走或易燃或是否可以触发事件等)

一个想法是让一个 tile 对象有可能成为任何类型的 tile,并根据类型打开某些布尔“开关”(请注意,以下内容大多只是伪代码,并不意味着实际编译) :

class Tile
{
private:
    int m_type;
    bool m_walkable;
    // etc...

public:
    Tile( int type ) : m_type( type)
    {
        if( type == 0 )
        {
            m_walkable = true;

        } else if( type == 1 ) {
              m_walkable = false;
        }
        // etc etc would probably be a switch
        // statement but you get the idea
    }
};

就个人而言,我不喜欢这个主意;我认为每种类型的 tile 拥有自己的数据结构会更加优雅。我想象使用某种基于继承的系统,但我似乎无法将它们放在一起。从根本上说,我认为它应该是这样的:

enum class TileType
{
     TILE_TYPE null, // 0
     TILE_TYPE floor, // 1
     TILE_TYPE wall, // 2
     // etc ...
};

class BTile
{
private:
    // Location and dimensions of tile
    int m_xOffset;
    int m_yOffset;
    int m_width;
    int m_height;

    // Type of tile, initialized to 0 for base class
    TileType m_type;

public:
    // ...
};

class Floor : public BTile
{
private:
    TileType = 1;
    bool walkable = true;

    // etc...
};

class Wall : public BTile
{
private:
    TileType = 2;
    bool walkable = false;
};

这样的东西会让人感觉更有条理和灵活,同时还允许我将 FloorWall 对象插入到任何需要 Tile 对象的函数中。问题是我似乎无法以一种实际可行的方式将所有这些放在一起 - 例如,我如何为特定的图块类型提供与其关联的图块?例如,如果我正在将一个文本文件读入我的程序,我怎样才能从001Tile->Floor?任何有关此主题的建议或意见将不胜感激。

【问题讨论】:

    标签: c++ 2d game-engine


    【解决方案1】:

    尝试使用factory method

    最简单的方法是使用开关。

    Tile* createTile(TileType tileType) {
        switch(tileType) {
        case TileType.floor: return new Floor;
        case TileType.wall: return new Wall;
        }
        return nullptr;
    }
    

    通常不建议这样做,因为每次添加新类型时都必须更新工厂。

    或者,您可以使用一些技巧将类型注册到工厂。 一种方法是描述的here。还有很多策略。

    【讨论】:

      【解决方案2】:

      您为什么要重塑 OO?对象已经有类型,不需要TileType。您可能想阅读virtual 函数。

      【讨论】:

        【解决方案3】:

        这里有几种方法,具体取决于您希望游戏中的图块做什么。您可以采用面向对象的方式,通过为您拥有的不同瓷砖类型设置不同的类,或者您可以更简单,只使用一个位集来表示您的瓷砖将具有的不同能力。

        这个选择取决于你想做什么。

        仅限位集

        通常,仅 bitset 变体就足够了。要做到这一点,你需要一些类似的东西:

        您很可能需要一组标志来代表您的瓷砖的不同能力(例如 IsWalkable、IsWater 等)。类似的东西:

        struct TileFlag
        {
            bool m_IsWalkable : 1;
            bool m_IsWater : 1;
            //other flags you might need
        };
        

        考虑到这一点,您的 Tiles 将遵循这些思路(纹理和纹理管理器仅用于示例):

        struct Tile
        {
            void Render();
        
            void Serialize(const boost::property_tree::ptree& tileData)
            {
                m_Flags.m_IsWalkable = tileData.get<bool>("walkable", false);
                m_Flags.m_IsWater = tileData.get<bool>("water", false);
                std::string texturePath = tileData.get<std::string>("texturePath", "");
                m_TileTexture = TextureManager::GetOrLoad(texturePath);
            }
        
            TileFlags m_Flags;
            std::shared_ptr<Texture> m_TileTexture;
        };
        

        您需要某种注册表,其中包含所有图块,以便您的关卡可以引用图块。这个注册表可以像 std::map 一样简单。

        一些示例代码:

        struct TileRegistry
        {
            void LoadTiles(const boost::property_tree::ptree& tiles)
            {
                for (boost::property_tree::ptree::value_type& tileType : tiles.get_child("tileTypes"))
                {
                    std::unique_ptr<Tile> newTile = std::make_unique<Tile>();
                    newTile->Serialize(tileType.second);
                    m_Tiles[tileType.first] = std::move(newTile);
                }
            }
            Tile* FindTile(const std::string& tileType)
            {
                Tile* result = nullptr;
                auto search = m_Tiles.find(tileType);
                if (search != m_Tiles.end()) {
                    result = search->second.get();
                } 
                return result;
            }
            std::map<std::string, std::unique_ptr<Tile>> m_Tiles;
        };
        

        然后,当您加载关卡时,您只需在 TileRegistry 中搜索 Tile Type,您将获得指向 Tile 的指针。

        OOP 样式对象继承

        这种方法会从前一种方法中借鉴很多,最大的不同在于您将如何创建图块。正如@artcorpse 提到的,您将需要某种工厂。

        如果您想更通用一点,可以使用一些宏来做一些自动化魔术:

        struct TileFactory
        {
            static std::map<std::string, CreateFunctionPtr> m_FactoryFunctors;
            std::unique_ptr<ITile> CreateTile(const std::string& tileType) 
            {
                std::unique_ptr<ITile> result;
                auto search = m_FactoryFunctors.find(tileType);
                if (search != m_FactoryFunctors.end()) {
                    auto creationFunctionPtr = search->second;
                    result = creationFunctionPtr(); //Notice the function invocation here
                } 
                return result;
            }
        };
        
        template<typename T>
        struct TileRegistrator
        {
            TileRegistrator(const std::string& tileTypeName){
                TileFactory::m_FactoryFunctors[tileTypeName] = &T::CreateTile;
            }
        };
        
        #define DECLARE_TILE_TYPE(TileType) \
        static std::unique_ptr<ITile> CreateTile() { return std::make_unique<TileType>();} \
        static const TileRegistrator<TileType> s_Registrator;
        
        #define DEFINE_TILE_TYPE(TileType) \
        const TileRegistrator<TileType> TileType::s_Registrator = {#TileType};
        

        以及如何使用这些宏:

        struct ITile
        {
            virtual ~ITile() = default; //Don't forget a virtual destructor when you have object which can be accessed by pointer to Base!
            virtual bool IsWalkable() const = 0;
            virtual bool IsSailable() const = 0;
            virtual void Serialize(const boost::property_tree::ptree& tileData) = 0;
        };
        

        在您的 .h 文件中,例如OceanTile.h:

        struct OceanTile : public ITile
        {
            DECLARE_TILE_TYPE(OceanTile);
            bool IsWalkable() const override;
            bool IsSailable() const override;
            void Serialize(const boost::property_tree::ptree& tileData) override;
            int m_WavesIntensity{0};
        };
        

        在您的 .cpp 文件中,例如OceanTile.cpp:

        DEFINE_TILE_TYPE(OceanTile)
        
        bool OceanTile::IsWalkable() const { 
            return false;
        }
        
        bool OceanTile::IsSailable() const { 
            return true;
        }
        void OceanTile::Serialize(const boost::property_tree::ptree& tileData) {
            m_WavesIntensity = tileData.get<int>("WavesIntensity", 0);
        }
        

        并创建一个新的 tile 对象,假设您知道它的类型为字符串(例如,来自数据文件非常简单:

        void LoadTiles(const boost::property_tree::ptree& levelData)
        {
            for (boost::property_tree::ptree::value_type& tile : levelData.get_child("levelTiles"))
            {
                std::unique_ptr<ITile> newTile = TileFactory::CreateTile(tile->first);
                newTile->Serialize(tile.second);
                //Do whatever you want to do with your Tile - maybe store it in some vector of all tiles for the level or something
            }
        }
        

        免责声明:我没有编译或测试过上述任何代码,但希望它可以让您了解如何开始。那里可能隐藏着许多错误。

        我鼓励您从仅 Bitset 选项开始,因为这对于许多不同类型的游戏来说已经足够了,而且使用起来和推理起来要简单得多。

        希望这能给你一些开始:)

        【讨论】:

          猜你喜欢
          • 2012-11-21
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-03-18
          • 2020-11-10
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多