这里有几种方法,具体取决于您希望游戏中的图块做什么。您可以采用面向对象的方式,通过为您拥有的不同瓷砖类型设置不同的类,或者您可以更简单,只使用一个位集来表示您的瓷砖将具有的不同能力。
这个选择取决于你想做什么。
仅限位集
通常,仅 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 选项开始,因为这对于许多不同类型的游戏来说已经足够了,而且使用起来和推理起来要简单得多。
希望这能给你一些开始:)