1

我目前正在使用 SFML 开发 2D 游戏引擎,并从程序上开始,但我决定将其移至 OOP 从长远来看会更好。我理解 OOP 的概念,但是,我不确定我应该在哪里定义我对其他类(ImageManager、Grid 等)的实例化

例如,我的Engine类依赖于ImageManager,并且其中的许多函数Engine依赖于来自 的变量ImageManager。因此,我不能简单地ImageManager在单个函数中声明和定义类的实例,因为它在其他函数中不可用。

我所做的是:Engine.h:

class Engine
{
private:
    sf::RenderWindow window;
    grid (*gridArray)[SIZE];
    ...//more variables, removing for length
    //Initializes the engine
    bool Init(); //Main Game Loop
    void MainLoop(); //Renders one frame
    void RenderFrame(); //Processes user input
    void ProcessInput(); //Updates all Engine internals
public:
    Engine(int w, int h, int tileSize);
    ~Engine();
    ImageManager * imageManager;
    GridClass * gridClass;
    ...//removed some methods for length
};

所以基本上,你可以看到我在标题中声明了两个类,ImageManager并且GridClass. Engine.cpp 内部:

Engine::Engine(int w, int h, int tileSize)
{
    imageManager = new ImageManager;
    gridClass = new GridClass();
    gridArray = new grid[SIZE][SIZE]();
    masterArray = new unsigned char[MAP_SIZE][MAP_SIZE];
    videoSize = sf::Vector2i(w, h);
    this->tileSize = tileSize;
    startX = startY = endX = endY = 0;
}

我正在定义类。我在构造函数中这样做,因为我不确定我应该在哪里做,以符合良好的做法。我一直对imageManager损坏的元素有问题,所以我想知道我这样做的方式是否是个坏主意。

如果这是一个坏主意,你能告诉我应该在哪里实例化这些类吗?请记住,Engine 中的许多函数都依赖于这些类的这些实例中的变量,我真的不想向每个函数传递大量参数。

谢谢。

4

3 回答 3

1

首先,做好转换工作。OOP是游戏制作非常好的选择。

简短的回答是,可以在这样的其他类中设置类。在构造函数中实例化这些并没有错。构造函数的重点是初始化该类的对象需要开始工作的所有内容,因此如果您认为Engine需要 animageManager才能正常运行,在构造函数中实例化它就可以了。

但是,请记住,只要您不需要经常在函数imageManager外部调用,就可以将一个对象放在另一个对象中。Engine然后你必须使用这样的语法:

engine.imageManager.somefunction()

如果您需要访问嵌套在深处的东西,这可能会变得非常混乱和令人费解,但只要您不嵌套太多这样的对象,就应该没问题。如果您认为您需要imageManager在引擎之外使用很多东西,最好单独实例化并在其中包含可以保存您需要的值的imageManager私有变量。Engine然后,您可以创建一些公共函数,Engine这些函数可以从中接收任何更新的信息imageManager并相应地更新变量。希望这可以帮助!

于 2013-07-02T15:12:37.380 回答
1

我认为按照你的方式来做是完全可以接受的。进入正文后,将分配Engine::Engine()内存。Engine没有潜在的腐败imageManager

但是,当在构造函数中分配所有成员时,它变得非常类似于使它们成为 Engine(称为“组合”)的一部分,即

ImageManager imageManager;
GridClass gridClass;

这样,您就不必担心清理内存。另一方面,你失去了灵活性。您实现它的方式Engine 有一个 ImageManager“聚合”),可以在程序运行时方便地替换它。

于 2013-07-02T15:23:14.037 回答
1

从类内部实例化成员对象会使您的代码不那么模块化/灵活。

想象一下,您有各种ImageManager类的实现(例如,您可能希望在对您的MockImageManager进行单元测试时进行,或者您可能想与各种第三方Engine一起尝试您的类),您可以使用其中一种实现或另一种实现的唯一方法是通过修改自身的实现。EngineImageManagerEngineImageManagerEngine

这会阻止您利用 OOP 的动态调度,尤其是 C++(这里的关键字是:继承、接口和虚拟调用)。

您会发现您的代码将变得更加灵活,例如:

class Engine
{
public:
    Engine(int w, int h, int tileSize, const std::shared_ptr<ImageManger>& im, const std::shared_ptr<GridClass>& gc);
protected:
    std::shared_ptr<ImageManger> imageManager;
    std::shared_ptr<GridClass> gridClass;
    // ....
};

只需复制您在构造函数中给出的 shared_ptr :

Engine::Engine(int w, int h, int tileSize, const std::shared_ptr<ImageManger>& im, const std::shared_ptr<GridClass>& gc) : imageManager(im), gridClass(gc)
{
    // ...
}

实例化批次的方法:

int main(void)
{
    Engine engine(42, 42, 42, std::make_shared<ImageManager>(), std::make_shared<GridClass>());
    // ...
    return 0;
}

这样,您只需更改以提供新的实现ImageManager(假设ImageManager的接口由虚拟调用组成)就是将其编码为MyRevolutionaryImageManager(将派生自ImageManager),并将std::make_sharedmain 中的调用更改为std::make_shared<MyRevolitionaryImageManager>(). 你甚至不必重新编译Engine,新的实现就会被使用(虚拟的美妙之处在于允许旧代码调用新代码)!

当然,如果ImageManager和/或GridClass只是 的实现细节Engine,并且如果不需要灵活性,那么从内部实例化它们就足够公平了Engine

不需要使用,shared_ptr您可以根据需要使用普通指针,但是您需要考虑何时/何处删除内容。

于 2013-07-02T15:49:07.643 回答