我目前正在开发一款带有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中的许多函数都依赖于这些类的这些实例中的变量,我真的不想将大量参数传递给每个函数。
感谢。
答案 0 :(得分:1)
首先,做好转换工作。 OOP是游戏制作的最佳选择。
简短的回答是,可以将类放在其他类中。在构造函数中实例化这些并没有错。构造函数的要点是初始化该类对象开始工作所需的所有内容,因此如果您认为Engine
需要imageManager
才能正常运行,那么在构造函数中实例化它就可以了。
但是,请记住,只要您不需要经常在imageManager
函数之外调用Engine
,就可以将对象放在另一个对象中。然后你必须使用这样的语法:
engine.imageManager.somefunction()
如果你需要访问内嵌深层的东西,这可能会变得非常混乱和复杂,但只要你没有嵌套太多像这样的对象,你应该没问题。如果您认为您需要在引擎之外使用imageManager
,那么最好分别实例化imageManager
并在Engine
中拥有可以保存值的私有变量你需要的。然后,您可以在Engine
中创建一些可以从imageManager
接收任何更新信息的公共函数,并相应地更新变量。希望这有帮助!
答案 1 :(得分:1)
我认为按照你的方式去做是完全可以接受的。进入Engine::Engine()
主体后,将分配Engine
的内存。没有可能破坏imageManager
。
在构造函数中分配所有成员时,它变得非常类似于使 Engine
(称为"Composition")的一部分,即
ImageManager imageManager;
GridClass gridClass;
这样,您不必担心清理内存。
另一方面,你失去了灵活性。您实现它的方式,Engine
有一个 ImageManager
("Aggregation"),可以在程序运行时方便地替换它。
答案 2 :(得分:1)
从类中实例化成员对象会使代码变得模块化/灵活。
想象一下,您有ImageManager
课程的各种实现(例如,您可能希望在单元测试MockImageManager
时获得Engine
,或者您可能想要试用Engine
ImageManager
使用各种第三方Engine
),您ImageManager
使用Engine
或其他实现的唯一方法是修改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;
// ....
};
本身的实现。
这可以防止您利用动态调度OOP,特别是C ++(这里的关键字是:继承,接口和虚拟调用)。
你会发现你的代码会变得更灵活,例如:
Engine::Engine(int w, int h, int tileSize, const std::shared_ptr<ImageManger>& im, const std::shared_ptr<GridClass>& gc) : imageManager(im), gridClass(gc)
{
// ...
}
只需复制构造函数中给出的shared_ptr:
int main(void)
{
Engine engine(42, 42, 42, std::make_shared<ImageManager>(), std::make_shared<GridClass>());
// ...
return 0;
}
实例化该批次的方法:
ImageManager
这样,您只需要更改以提供ImageManager
的新实现(假设MyRevolutionaryImageManager
的接口由虚拟调用组成),则需要将其编码为{{1} (将从ImageManager
派生),并将主要std::make_shared
调用更改为std::make_shared<MyRevolitionaryImageManager>()
。您甚至不必重新编译Engine
,将使用新的实现(允许旧代码调用新代码的虚拟之美)!
当然,如果ImageManager
和/或GridClass
只是Engine
的实现细节,并且如果不需要灵活性,那么从{{{}}内部实例化它们是公平的。 {1}}。
如果你愿意,不需要使用Engine
你可以使用普通指针,但是你需要考虑删除东西的时间/地点。