グラフィックモジュール:私は正しい方向に進んでいますか?


7

エンジンのグラフィックスモジュールを記述しようとしています。つまり、コードのこの部分は、画像、フォントなどをロードして画面に描画するためのインターフェースのみを提供します。また、私が使用しているライブラリ(この場合はSDL)のラッパーでもあります。

これがImageFontGraphicsRendererクラスのインターフェースです。私が正しい方向に進んでいるかどうか教えてください。

画像

class Image
{
  public:
    Image();
    Image(const Image& other);
    Image(const char* file);
    ~Image();

    bool load(const char* file);
    void free();
    bool isLoaded() const;

    Image& operator=(const Image& other);

  private:
    friend class GraphicsRenderer;
    void* data_;
};

フォント

class Font
{
  public:
    Font();
    Font(const Font& other);
    Font(const char* file, int ptsize);
    ~Font();

    void load(const char* file, int ptsize);
    void free();
    bool isLoaded() const;

    Font& operator=(const Font& other);

  private:
    friend class GraphicsRenderer;
    void* data_;
};

GrapphicsRenderer

class GraphicsRenderer
{
  public:
    static GraphicsRenderer* Instance();

    void blitImage(const Image& img, int x, int y);
    void blitText(const char* string, const Font& font, int x, int y);
    void render();

  protected:
    GraphicsRenderer();
    GraphicsRenderer(const GraphicsRenderer& other);
    GraphicsRenderer& operator=(const GraphicsRenderer& other);
    ~GraphicsRenderer();

  private:
    void* screen_;

    bool initialize();
    void finalize();
};

編集:コードの一部の変更と詳細。

ここでのいくつかの議論に従って、私はの使用void*を次のようなものに置き換えることにしました:

class Image
{
  private:
    struct ImageData;
    std::shared_ptr<ImageData> data_;
};

(もちろん、Fontクラスでも同じことをします。)

また、これらは私の最後の完全なクラスではないことにも触れておきます。ここでは、基本的な機能(読み込みとレンダリング)のみを示します。追加する必要があると思われる機能(画像の回転、傾斜、スケーリングなど)を伝える代わりに、すでに持っているものを確認することに集中してください。私はできる限り自分の選択を守ろうとします。できない場合は私のアプローチを変えます。


質問:これはどのようなエンジンですか?プロジェクトの予想される範囲は何ですか?あなたがあなたのプロジェクトに何を意図していたかについての誤解に基づいて、私が貧弱なアドバイスをしているのではないかと私は少し心配しています。
ChrisE

回答:


1

私にとって最もかゆいのはvoid *「虐待」です。

無効なポインタ:必要ありません。SDL.hを含むファイルの数を制限する方法です(void * data_は、SDL_Surface *をvoid *にキャストしただけです)。

まあ、あなたがどこか都合の良い場所でそれを前もって宣言することで、インクルージョン(私はBTWを承認します)を避けることができます。


コメントのいくつかの後、私は、私はプライベートを定義することを決めたstruct FontDataとしていstd::shared_ptr<FontData>代わりのプライベートメンバーとしてvoid*。これは、前方宣言も回避できることを意味します。つまり、ヘッダーファイルにライブラリの記述がありません。:)
ポールマンタ

OK、いい解決策です。
jv42 2011年

6

インターフェース上(一般)

だから、あなたは私たちにあなたのインターフェースのデザインを見直すように頼んだ。

あなたは私たちにインターフェースを与えなかった、あなたは私たちに完全なクラス宣言を与えた。これらがインターフェースである場合、私は次のようなものを期待します:

virtual bool load file(const char* file) = 0;

これは、C ++ではインターフェースです。機能を実装するサブクラスでオーバーライドできます(実際、私はそうしなければなりません)。あなたがインターフェースを書いているなら、あなたはポリシーを強制している、そして上記はあなたがこれをする方法である。

他の回答でvoid *の使用に関する不満の半分は、インターフェイス関数を公開し、メンバー変数を(インターフェイスクラスで)隠しておけば、回避されたでしょう。

Rawr。

インターフェース上(あなたのもの)

画像:コピー

コピーコンストラクターと等号演算子があります。ここで私が目にする問題は、ユーザーが画像の愚かな無関係なコピーを作成することを防ぐ良い方法がないということです。

SDL_surfacesを使用するあなたにとって、これは大きな問題です。別の画像の複製である画像を解放するときに何が起こるかを考慮していないことは、不快感を与えることを意味するものではありません。SDL_surfaceの完全なディープコピーを処理することを計画していなかったので、私はさらに喜んで賭けます。そのため、前述の場合、画像を解放し、その後、他の画像のコピーが爆発し、愛する人すべてが殺されます。 。

解決策:コピーなし。許可しないでください。ファクトリ関数またはCスタイルのローダー関数を使用して、Imageの新しいインスタンスを作成し、コピーまたは同等の割り当てを許可する代わりにそれらを使用します。または、SDL_imageをディープコピーする方法を完全に理解します(非常に難しくはありませんが、煩わしい)。

画像:操作

ロードした画像をどのように変更しますか?あなたのインターフェースによると、私はしません。それでも、それは良い考えですか?画像のビット深度を確認するにはどうすればよいですか?その高さ?幅?色空間?

フォント

このフォントでどのように描画しますか?その名前はどのようにしてわかりますか?上記で不満を述べたコピーの問題を防ぐにはどうすればよいですか?色を設定するにはどうすればよいですか?字詰め?Unicodeサポートはどうですか?

レンダラー:一般

だから、あなたはいくつかのblit *()関数とrender()関数を持っていることに気づきました。これは、ユーザーが大量のブリッティング操作をキューに入れ、render()呼び出しを使用してそれらをすべて一度に画面にフラッシュできるようにすることを意味するようです。

それはいいです; 実際、それは私のグループのエンジン技術もそれを処理する方法です。:)

ここでシングルトンを使用しても問題ありません。主に、レンダラーに描画を完全に制御させたいと思われるためです。そのインスタンスが1つしかない場合は(おそらくあるはずですが)、問題はありません。私たちがすることではありませんが、ちょっと、それは好みの問題です。

しかし、ここで私が目にする大きな問題がいくつかあります。

レンダラー:変換

2Dでのみ作業しているようです。それはいいです。だが...

描画するときに画像を回転するなどの処理をどのように行いますか?スケーリング?アフィン変換と呼ばれるものを完全にサポートする必要があります。これにより、簡単に回転、拡大縮小、移動、傾斜、その他の方法できれいな画像を作成できます。

これは、テキストと画像の両方で(何らかの形で)サポートされる必要があります。

レンダラー:カラーリングとブレンド

画像に色を混ぜて、テキストの色を設定したい。これを公開する必要があります。

また、ブリッティング時にブレンドイメージなどを実行できるようにしたいので、半透明のゴーストや煙や火などを実行できます。

トラブルを自分で救う方法

SFMLを使用します。SDLのほとんどすべての設計が改善されています。彼らはあなたがここでしようとしていることをすでに行っています。少なくとも、インターフェースの仕様やクラス階層の設計方法を見てください。

また、それらはDrawableクラスで以前に指摘した変換の問題に対処していることにも注意してください。そして着色。そしてブレンド。

彼らは良いチュートリアルとドキュメントを持っているので、少しそれをいじって、あなたのコードが達成することができるはずであるものの感触を得るためにあなたの時間の価値があるかもしれません。

幸運を!


1
正直なところ、提案されたインターフェースが公開する機能の欠如によって完全に覆い隠されている他のスタイル上の決定(load()メソッド、void *、シングルトンの使用など)があります。ここのソフトウェアエンジニアリングが非常に壊れているので、目が出血するような場合に、使用するソフトウェア開発の問題を心配する時間を無駄にすべきではありません。
ChrisE、2011年

1
良いスタートを切ったので、必要な機能を見つける方法を学ぶために、もう少し経験が必要です。SFMLをチェックして、楽しい小さなアプリをいくつか作成し、どの機能を公開する必要があるのか​​が明確になったら、これに戻ってください。必要なものがすべて揃うまでは、配管がいかに醜いか心配する必要はありません。
ChrisE、2011年

1
インターフェース:はい、それはインターフェースです。これはAPIの一種のインターフェースであり、仮想クラスのインターフェースではありません。Void-pointer:すでに整理されています。jv42の回答に関する私のコメントを参照してください。| 画像のコピー:その仮定は何に基づいていますか?| 操作:まだ実装していないクラスの詳細の多く、または気が散るだけの質問だと考えました。また、画像の回転、フォント名の取得などを求められているようです。今ではなく、必要に応じて実装します。[続き]
ポールマンタ

1
[続き] SFML:確かに、私はこのライブラリーを発見したばかりで、もっと好きです。| 結論:ここで示したのは、コア(ロードとレンダリング)だけです。表示する必要がないと考えたその他の詳細。また、後で必要になる場合を除いて、機能(回転、傾斜、スケーリング)の実装は開始しません。とにかくそれを行うのは簡単でしょう。
ポールマンタ

1
SFMLの場合は+1。APIを使用して、このインターフェイスのほとんどを取り除くことができます。:P
共産主義者アヒル

0

単一責任の原則を破る "ちなみに、" loadメソッドに少し悩んでいます(ちなみに、共産主義者のダックにとって、これは彼がconst char *を使用している理由だと思います。SDLロードイメージ関数がコーディングされているためです) Cでは、std :: stringを使用しないでください。少なくとも2つの理由で、それ自体をロードするのはImageクラスの仕事ではありません。

  • メモリの使用を最適化する場合は、リソースをロードするための特殊なクラスが必要になります。
  • 専用クラスを使用すると、例外をより簡単に処理できます。

ロード:いいえ、それ自体をロードするのは、それ自体をロードするstd :: ifstreamのジョブと同じように、Imageクラスのジョブでなければなりません。これはメモリ管理を行う場所ではありません。別のResourceManagerクラスで行われます。| 文字列:に変換std::stringするのは難しくないchar*ので、それは理由ではありません。その理由は単にそれstd::stringが何の利点も提供しないことです。
ポールマンタ

-1

シングルトン=不良、すぐに削除。フォントと画像はfree()機能を持っていてはいけません、それはデストラクタの仕事であるべきです。GraphicsRendererこれらの提供はならないBlit機能を、あなたは自動的にGraphicsRendererによって管理される各最終結果のための位置、および実際のレンダリングを提供するオブジェクト指向のクラスを提供する必要があります。最後に、カプセル化には、PIMPLではなく継承を使用し、確実に void *を使用しないでください。強く型付けされた不透明なポインタを使用してください。

以下は、私自身の設計からの抜粋ですが、実行時の継承ではなく、コンパイル時の切り替えを使用しています。

class D3D9Render 
{
public:
    std::shared_ptr<D3D9Font> CreateFont();
};
class D3D9Font 
{
public:
    // PUBLIC INTERFACE ---------------------------------------------------
    std::unique_ptr<D3D9Text> CreateText();

    int Height();
    D3D9Font* Height(char newheight);
    D3D9Font(D3D9Render& ref);

    int Width();
    D3D9Font* Width(char newwidth);
    int Weight();
    D3D9Font* Weight(short newweight);
    bool Italic();
    D3D9Font* Italic(bool newitalic);
    string Font();
    D3D9Font* Font(string str);
    D3D9Font* CommitChanges();
};

class D3D9Text 
{
public:

    // PUBLIC INTERFACE ---------------------------------------------------
    D3D9Text* Text(string str);
    D3D9Text* PositionSizeX(short newx, short newxsize);
    D3D9Text* PositionSizeY(short newy, short newysize);
    D3D9Text* Font(std::shared_ptr<D3D9Font> ref);
    D3D9Text* Colour(unsigned int newcolour);
    string Text();
    int PositionX();
    int PositionY();
    int SizeX();
    int SizeY();
    std::shared_ptr<D3D9Font> Font();
    unsigned int Colour();
    D3D9Text* CommitChanges();
};

ここでは、メモリが管理されます。すべての所有権はスマートポインティングによって管理され、インターフェイスは完全にオブジェクト指向です。


2
シングルトン:シングルトンは、正しく使用すれば問題ありません。基本的に、レンダラーには話す状態がないため、シングルトンで問題ありません(ただし、それよりも複雑です)。| 無料:割り当て解除デストラクタで処理されます。free()機能は同じ理由のためにそこにあるifstream::close()関数が存在します。| ブリット:データを使用するメカニズムからデータを分離しようとしています。| スプライト:これは単なるグラフィックスモジュールです。スプライトは、説明したのと同様の方法で、エンジンの上位レベルで処理されます。[続き]
ポールマンタ

[続き] 継承:ここで継承をどのように使用できるかわかりません。| Void-pointer:なぜそれを使用すべきではないのですか?サードパーティのライブラリにアクセスできるファイルの数を制限する良い方法だと思いました。data_は単なるSDL_Surface*キャストvoid*です。使用するたびに、にキャストバックしSDL_Surface*ます。| ブリット[続き]:これについてのもう1つの考え方は、画像自体が描画されないことです&mdash; レンダラーがそれを描画します。画像は単に存在します。
Paul Manta

見せてくれてありがとうstd::shared_ptr。重宝すること間違いなしです。
ポールマンタ

1
Free:システムによっては、オブジェクトとそれに関連するリソース(テクスチャなど)の破棄が異なるスレッドで発生する必要があるfree()場合があるため、関数が必要です。また、デストラクタを呼び出さないカスタム割り当てスキームを使用している可能性もあります。
sam hocevar 2011年

1
@DeadMG継承を伴うカプセル化を提供する方法についても混乱しています。私は常に、プライベートメンバーを非表示にする最善の方法は、強く型付けされた不透明なポインターに固執することだと考えていました。継承の提案について詳しく教えてください。
michael.bartnett 2011年

-1

私が見つけることができる最初のもの:

  • シングルトンは通常、非常に悪いです。それは「うーん、私はこれらのうちの1つだけが欲しいのですか?」ではありません。しかし、「うーん、これらのうちの1つ以上がプログラムを壊しますか?」
  • void *ポインターもかなり危険です。なぜ必要なのですか?どこか悪いデザイン。
  • FontそしてImage密接に関連しているようです。たぶん、機能の一部を階層化してにすることができますRenderable
  • これは私だと思いますが、何を使っconst char*ているのstd::stringですか?

の良い場所const char*、私はそれに気づかなかった。
DeadMG

無効ポインタ:必要ありません。これは、含めるファイルの数を制限する方法ですSDL.hvoid* data_単なるSDL_Surface*キャストですvoid*)。| 文字列:使用しない理由はありますか?何も操作しません。文字列が必要です。std::string必要に応じて、これらのクラス以外でも自由に使用できます。| シングルトン:はい、複数のGRが私のコードを壊します。GRはSDLを初期化します。シングルトンがあれば、SDLがいつ初期化または終了するかについて心配する必要はありません。
ポールマンタ

含むファイルが少ないという利点はSDL.hほとんどゼロです。どういうわけか、レンダラーがSDLを初期化することはありません。それをメインエンジンなどに抽象化します。グラフィック以外のSDLの他の部分が必要な場合はどうでしょうか。
共産主義者のダック

その他のコンポーネント:それらを必要とするコンポーネントで、個別に初期化します。| 初期化:メインエンジン(通常、GameManager)は、使用しているライブラリなどの詳細を気にする必要はありません。現在のセットアップでは、1ビットの初期化について心配する必要はありません。すべてが自動的に行われます(必要なときに正確に行われることが保証されています)。| ヘッダー:はい、本当の技術的な利点はありません。ただし、基になるライブラリがクラスインターフェイスで公開されていない場合は、コードがすっきりしていることがわかります。
ポールマンタ

1
あなたが理由を持っているデザインで私が間違って見ることができる唯一の点はより多くのものです。シングルトンはここでは不要であり、そのvoidポインターはSDL_Surfaceポインターに置き換える必要があることを私はまだ待機しています。:P
共産主義者ダック
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.