在过去的几周里,我一直在尝试在业余时间开发一款等距、基于瓷砖的大亨风格的制作游戏(实际上只是为了看看我是否有能力),我遇到了一个我正在努力的设计挑战。我已经成功地实现了地面瓷砖并使用 Tile 类(保存在 Map 类中)绘制它们等,但我希望现在能够在瓷砖上添加对象。
我已经能够使用一个相当笨拙的调试类创建和渲染对象(如表),该类作为std::map<int, Object>
包含在 Map 类中,并为对象提供 X/Y 坐标,以便 Map 可以在正确的位置渲染它们。所有这些,虽然可能不是最好的方法,但都有效,直到我尝试对对象进行子类化以赋予它们不同的功能。
我(错误地)认为我能够为地图上的所有对象提供某种异构容器,我可以在游戏循环期间循环并运行特定于对象类型的逻辑。我还没有完全确定游戏的范围,所以目前我只是给不同的对象不同的成员变量 - 例如具有容量的容器对象,具有能量等级的加热器对象等等。我的第一个测试是能够在地图上放置项目,然后能够查询容器对象的所有容量的总和 - 只是为了确保它在继续下一步之前有效。
我一直在尝试使用OpenTTD和一些Habbo Hotel模拟器等开源项目来解决这个问题,但无济于事。我很想听听其他人认为解决这个问题的最佳方法是什么。
我已经尝试过在地图中dynamic_cast
和使用指针,但从我看到的其他答案来看,这似乎不是一个特别好的方法。我是否应该创建一个相同类型的对象池,然后在游戏循环期间遍历每个池以使事情更简单?
标准解决方案是将它们存储为基类指向具体对象的指针。
如果你有
class IObject
{
public:
virtual void render() const = 0;
virtual ~IObject(){}
};
你可以这样做
int main()
{
using IObjectUP = std::unique_ptr<IObject>;
std::map<int, IObjectUP> m;
// some concrete objects get created
for (const auto& [_, o] : m)
o->render();
}
std::unique_ptr
的使用将获得所有权。如果其他人拥有某个对象,你可以使用std::shared_ptr
、std::weak_ptr
或std::reference_wrapper
。
还有一件事。std::vector
是默认容器。如果没有充分的理由std::map
,请使用std::vector
。
std::map<int, std::unique_ptr<Object>>
可能就是你所需要的。如果您的Object
API 是干净的,则不需要dynamic_cast
s。例如,您应该能够使用:
for(auto x : objects)
{
x->draw();
x->simulate();
}
你有几个选择。
第一个是std::any
/boost::any
(C++17之前)。如果您的对象完全没有共同点,这将非常有用。
另一种选择是使用通用接口:
class Object {
public:
virtual void func() {};
};
并从该接口继承:
class A : public Object {
public:
void func() {/*my overload*/}
};
这样,您可以将它们存储在如下所示的容器中:
Object* obj = new A;
std::map<int, Object*> myMap;
myMap[0] = obj;
并像这样得到它:
myMap[0]->func(); // get the overloaded version
当然,当你以这种方式完成它时,你需要delete
obj
:
delete obj;
应该注意的是,如果在delete
它时仍设置为obj
,这将使myMap[0]
无效。这是我不一定建议这样做的原因之一(如果你能使用它,std::any
会好得多),但如果你想使用它,它就在那里。
当然,正如@Jeffrey所建议的那样,您始终可以使用智能指针。