1

私は現在、SFML を使用した 2D ゲーム エンジンに取り組んでおり、手続き的に開始しましたが、長期的には OOP に移行することで改善されると判断しました。OOP の概念は理解していますが、他のクラス (ImageManager、Grid など) へのインスタンス化をどこで定義すればよいか正確にはわかりません。

たとえば、私Engineのクラスは に依存してImageManagerおり、 の多くの関数は のEngine変数に依存していますImageManagerImageManagerしたがって、単一の関数内でクラスのインスタンスを単純に宣言して定義することはできません。他の関数では使用できないためです。

私がやったことはこれです: 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
};

基本的に、ヘッダーで 2 つのクラスを宣言していることがわかり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必要があると思われる場合は、コンストラクターでインスタンス化しても問題ありません。imageManager

ただし、関数imageManagerの外部で頻繁に呼び出す必要がない限り、オブジェクトを別のオブジェクト内に配置しても問題ないことに注意してください。Engine次に、次のような構文を使用する必要があります。

engine.imageManager.somefunction()

深くネストされたものにアクセスする必要がある場合、これは非常に厄介で複雑になる可能性がありますが、このようなオブジェクトをネストしすぎない限り、問題はありません。エンジンの外部で多くのことを使用する必要があると思われる場合は、個別にインスタンス化して、必要な値を保持できるプライベート変数を用意imageManagerするのが最善かもしれません。次に、更新された情報を受け取り、それに応じて変数を更新できるいくつかのパブリック関数を作成できます。お役に立てれば!imageManagerEngineEngineimageManager

于 2013-07-02T15:12:37.380 に答える
1

あなたのやり方でそれを行うことは非常に受け入れられると思います。Engine::Engine()本体に入ると、Engine意志のメモリが割り当てられます。それで破損imageManagerする可能性はありません。

ただし、コンストラクター内のすべてのメンバーを割り当てる場合、それらを ( 「コンポジション」と呼ばれる)の一部に するのと非常に似ています。Engine

ImageManager imageManager;
GridClass gridClass;

これにより、メモリのクリーンアップについて心配する必要がなくなります。その反面、柔軟性は失われます。あなたがそれを実装した方法にEngine は、 ImageManager"Aggregation")があり、プログラムの実行中に簡単に置き換えることができます。

于 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、main の呼び出しを にImageManager変更することだけです。再コンパイルする必要さえありません。新しい実装が使用されます (古いコードから新しいコードを呼び出すことができる仮想の利点)!std::make_sharedstd::make_shared<MyRevolitionaryImageManager>()Engine

もちろん、ImageManagerおよび/またはGridClassが の実装の詳細に過ぎずEngine、柔軟性が必要ない場合は、 内からそれらをインスタンス化するのに十分公平Engineです。

を使用するshared_ptr必要はありません。必要に応じて通常のポインターを使用できますが、いつ/どこで削除するかを考える必要があります。

于 2013-07-02T15:49:07.643 に答える