ゲームエンジンのシンプルなレンダリングシステムを実装しています。私のエンジンには、モデルコンポーネントのあるレンダリング可能なエンティティがあります(今のところ、エンジンのECSではなく継承を使用していますが、コンポーネントに共通の動作をカプセル化すると、コンポーネントベースに遷移したい場合や、建築)。このModelクラスには、メッシュ(頂点バッファーとインデックスバッファー)、テクスチャ、およびマテリアルが含まれています。
一部のエンティティのモデルはファイル(モデリングアプリケーションによって生成された3Dモデル)から読み込まれるため、モデルをコンストラクターに渡すだけですが、一部のエンティティには、クラスに「ハードワイヤード」されたメッシュと頂点の属性があります。たとえば、SkyBoxクラスは次のようにコード化されます。
// SkyBox.hpp
class SkyBox
{
public:
SkyBox(const wchar_t *cubeMapFilePath);
~SkyBox();
XMFLOAT4X4 const &GetWorldMatrix() const;
Model const &GetModel() const;
void SetRotation(XMFLOAT3 const &rotation);
static Model CreateSkyBox(const wchar_t *cubeMapFilePath); // static member function called from constructor
private:
Model mModel; // the member with no def-ctor
XMFLOAT3 mRotation;
mutable XMFLOAT4X4 mWorldMatrix;
mutable bool mDirtyFlag = true;
};
// SkyBox.cpp
#include "SkyBox.h"
SkyBox::SkyBox(const wchar_t *cubeMapFilePath) : mModel(CreateSkyBox(cubeMapFilePath)), mRotation(0.0f, 0.0f, 0.0f)
{
}
SkyBox::~SkyBox()
{
}
Model SkyBox::CreateSkyBox(const wchar_t *cubeMapFilePath)
{
Mesh mesh;
std::vector<XMFLOAT3> positions;
positions.Push_back(XMFLOAT3(-0.5f, 0.5f, -0.5f));
positions.Push_back(XMFLOAT3(0.5f, 0.5f, -0.5f));
positions.Push_back(XMFLOAT3(0.5f, -0.5f, -0.5f));
positions.Push_back(XMFLOAT3(-0.5f, -0.5f, -0.5f));
positions.Push_back(XMFLOAT3(-0.5f, 0.5f, 0.5f));
positions.Push_back(XMFLOAT3(0.5f, 0.5f, 0.5f));
positions.Push_back(XMFLOAT3(0.5f, -0.5f, 0.5f));
positions.Push_back(XMFLOAT3(-0.5f, -0.5f, 0.5f));
std::vector<unsigned int> indices{ 0, 1, 3, 3, 1, 2, 5, 4, 6, 6, 4, 7, 4, 0, 7, 7, 0, 3, 1, 5, 2, 2, 5, 6, 4, 5, 0, 0, 5, 1, 3, 2, 7, 7, 2, 6 };
mesh.LoadAttribute("POSITION", &positions[0], positions.size());
mesh.LoadIndexBuffer(indices);
std::vector<Texture> cubeMap;
Texture texture(Texture::CUBE_MAP);
texture.LoadCubeMap(cubeMapFilePath);
cubeMap.Push_back(texture);
Material material = {};
return Model(mesh, cubeMap, material);
}
// .... other members
私のモデルクラスにはデフォルトコンストラクターがないため、モデルコンポーネントを持つクラスを定義するときは、エンティティーのクラスコンストラクターでコンストラクターを作成する方法を提供する必要がありますが、モデルを作成するには、最初にいくつかの計算を実行する必要があります(計算頂点位置、法線など)。
私はこのソリューションを思いつき、必要な計算を実行してモデルを返す(プライベート)静的メンバー関数を呼び出して、クラスのモデルメンバーを初期化します。代わりの方法は、Modelクラスにデフォルトのコンストラクターを与え、エンティティーのコンストラクターで計算を実行してから、Modelメンバーに割り当てることですが、空のモデルを持つことはあまり意味がありません。
それで、この解決策はハックですか、それとも良いですか、またはこの種の問題のパターンがありますか、それともデフォルトのコンストラクタに行くべきですか?
実際には、他のクラスのモデルの構築中にデータメンバーにアクセスする必要があるため、Modelクラスにデフォルトのコンストラクターを指定する必要がありました(たとえば、Terrainクラスは、衝突検出のためにメッシュグリッドの高さをデータメンバー配列に格納します) )。それが正しい方法かどうかはわかりませんが、すばやく簡単に動作します。
テキストで、CreateSkyBox()はプライベートであると述べましたが、プライベートとして宣言されていません。
静的な「ヘルパー」関数をクラスに含めることには何の問題もありません。これらは、モデルなどの補助オブジェクトの作成を含め、意味のあるあらゆることを支援するために使用されます。
空のModelを使用しても意味がない場合は、引数のないコンストラクターを使用しないでください(これまでとまったく同じです)。
あなたのコード/アプローチは素晴らしいと思いますが、同様の状況で使用できる別のパターンがあります。 mModelを構築されていない(null)状態にする場合は、Modelの代わりにオプションを使用します。次に、コンストラクターの本体内でそれを作成できます。あなたの特定のケースでは、あなたがやったことはより良いと思いますが、ニーズが変化し、まだすべてのデータがない場合(ファイルからオブジェクトを読み取るとき)にいくつかのオブジェクト(Skyboxなど)を構築できる必要がある場合これが発生する可能性のある一般的なケースです)-そして、std :: optionalを使用すると、そこを助けることができます。
それが実際にbetterであるかどうかを尋ねるのは自由ですが、考慮すべきもう1つの可能性は、Model
にモデルを適切に構築するコンストラクターを装備してから、その依存関係をスカイボックス:
class Model {
public:
Model(Mesh const &mesh, Texture const &texture, Material const &mat);
};
class SkyBox {
public:
SkyBox(Model const &m) : mModel(Model(input_data)) {}
// ...
};
これが必ずしも正しい方法であると主張するつもりはありません。特に、モデルが主に一連のデータの集約である場合、これはまったく役に立たない可能性がありますが、確かに役立つ場合があります。