16.データの読み込み

1601.CSVからのデータの読み込み


 このサンプルはFullSample601というディレクトリに含まれます。
 BaseCrossDx11.slnというソリューションを開くとDx11版が起動します。
 このサンプルは現在Dx12版はありません。

 実行結果は以下のような画面になります。
 16.データの読み込みの章のサンプルはすべて同じオブジェクト、同じ動きになります。CSVもしくはXMLデータから、データの構造の違いを超えて同じオブジェクトを生成する方法を述べることで、それぞれのデータ構造や読み込み方を解説するためです。

 

図1601a

 

CSVデータ

 この項ではCSVデータの読み込みを説明します。ソリューションのmediaディレクトリにはあらかじめGameStageA.csvGameStageB.csvがあります。これらはエクセルなどで作成しておきます。

CsvFileクラス

 CSVファイルCsvFileクラスを使って読み込みます。データを読み込む方法は大きく2つの方法があります。

Csvをマップで読み込む

 まず、直感的な方法がこの方法です。ステージのマップと同じイメージのマス目のようなCSVファイルを作成し、それを読み込む方法です。以下のようなCSVファイルを作成します。GameStageA.csvです。
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,3,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,2,0,0,0,0,1,0,0,0,0,0,0,0,0,0,A,0,0,0,0,0,0,0,0,0,1,0,0,0,0,2,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,2,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0
 0が並んでいますが、ところどころに1とか23とかがあります。このCSVは、全体がステージのマップを表し、1は敵1、2は敵2、3は敵3を表します。Aはプレイヤーを表し、このサンプルではプレイヤーはデータからは読み込みません。
 上記のCSVファイルはmediaディレクトリにGameStageA.csvがありますので見てみましょう。

 さて、このようにマップ化されたCSVを読み込むのは、まず、CsvFileクラスを準備します。
 GameStageのヘッダ部に以下の記述があります。
class GameStage : public Stage {
    //CSVファイルそのA
    CsvFile m_GameStageCsvA;
    //CSVファイルそのB
    CsvFile m_GameStageCsvB;
    //ビューの作成
    void CreateViewLight();
    //ボックスの作成
    void CreateFixedBox();
    //CSVのAオブジェクトの作成
    void CreateCsvAObjects();
    //プレイヤーの作成
    void CreatePlayer();
    //敵の作成
    void CreateEnemy();
public:
    //構築と破棄
    GameStage() :Stage() {}
    virtual ~GameStage() {}
    //初期化
    virtual void OnCreate()override;
};
 CsvFileクラスが2つあります。これらの初期化をOnCreate()関数で行います。
void GameStage::OnCreate() {
    try {
        wstring DataDir;
        App::GetApp()->GetDataDirectory(DataDir);
        ////CSVファイルそのAの読み込み
        m_GameStageCsvA.SetFileName(DataDir + L"GameStageA.csv");
        m_GameStageCsvA.ReadCsv();
        ////CSVファイルそのBの読み込み
        m_GameStageCsvB.SetFileName(DataDir + L"GameStageB.csv");
        m_GameStageCsvB.ReadCsv();
        //ビューとライトの作成
        CreateViewLight();
        //ボックスの作成
        CreateFixedBox();
        //CSVのAオブジェクトの作成
        CreateCsvAObjects();
        //プレーヤーの作成
        CreatePlayer();

    }
    catch (...) {
        throw;
    }
}
 これで、m_GameStageCsvAGameStageA.csvの内容がセットされました。m_GameStageCsvBについてはのちほど説明します。
 このようにして読み込んだCSVはマップで読み取るオブジェクトの展開用のものです。これらはGameStage::CreateCsvAObjects()で反映させます。
void GameStage::CreateCsvAObjects() {
    //オブジェクトのグループを作成する
    auto group = CreateSharedObjectGroup(L"SeekGroup");
    //CSVの全体の配列
    //CSVからすべての行を抜き出す
    auto& LineVec = m_GameStageCsvA.GetCsvVec();
    for (size_t i = 0; i < LineVec.size(); i++) {
        //トークン(カラム)の配列
        vector<wstring> Tokens;
        //トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
        Util::WStrToTokenVector(Tokens, LineVec[i], L',');
        for (size_t j = 0; j < Tokens.size(); j++) {
            //XとZの位置を計算
            float XPos = (float)((int)j - 19);
            float ZPos = (float)(19 - (int)i);
            if (Tokens[j] == L"1") {
                AddGameObject<SeekObject>(Vec3(XPos, 0.125f, ZPos));
            }
            if (Tokens[j] == L"2") {
                AddGameObject<MoveBox>(
                    Vec3(1.0f, 1.0f, 1.0f),
                    Vec3(0.0f, 0.0f, 0.0f),
                    Vec3(XPos, 0.5f, ZPos));
            }
        }
    }
}
 CsvFileクラスはデータを行単位で扱います。このことはすごく重要なので覚えておきましょう。
 上記の
    //CSVの全体の配列
    //CSVからすべての行を抜き出す
    auto& LineVec = m_GameStageCsvA.GetCsvVec();
 はコメントのようにデータをすべて読む関数です。この操作によりLineVecには行の配列が代入されます。
 続く
    for (size_t i = 0; i < LineVec.size(); i++) {
 では、それを各行に分けます。そして
        //トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
        Util::WStrToTokenVector(Tokens, LineVec[i], L',');
 で各行をに分けます。Util::WStrToTokenVector()関数は、文字列を指定したデリミタ(区切り文字)によってわけその配列を作成します。
 各列の配列はTokensにセットされますので、下の階層のループで
        for (size_t j = 0; j < Tokens.size(); j++) {
 とスキャンします。その中には012かもしくはAが入ってます。
 Aはプレイヤーの位置ですから無視するとして、12の場合は、その位置にオブジェクトを配置します。
            //XとZの位置を計算
            float XPos = (float)((int)j - 19);
            float ZPos = (float)(19 - (int)i);
 はマップ上の位置を計算(調整)しているところで、その後以下のように
                if (Tokens[j] == L"1") {
                    AddGameObject<SeekObject>(Vec3(XPos, 0.125f, ZPos));
                }
 と、数字によってエネミーのタイプを選んで構築します。

Csvを行の抽出で読み込む

 さて、マップ上の1つのセルに収まるオブジェクトの場合は、これでいいのですがセルをまたぐオブジェクトの場合は、このデータ形式では厄介です。例えば以下のようなCSVの場合です
4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
4,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,4
...以下省略
 ここで4は、壁を表します。しかしこのデータ構造では40個の壁が横に並んでしまいます。
 これではコストが膨大になります。
 もちろん同じ数字が続いたら同じオブジェクトが続くとするという形で読み込むことは可能です。その場合読み込みながら最適化するというのが必要になり、それもまた厄介です。
 そこで登場するのが行の抽出での読み込みです。
 CsvFileクラスには行を抽出する関数であるGetSelect()関数及びGetSelect2()関数があります。
 まず、GameStageB.csvは以下のようなCSV構造になってます。
Name,Scale,,,Rot,,,Pos,,
TilingFixedBox,50,1,50,0,0,0,0,-0.5,0,SKY_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,19.5,WALL_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,-19.5,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,19.5,0.5,0,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,-19.5,0.5,0,WALL_TX
 ここには1行で1つのオブジェクトの定義が記載されています。
 TilingFixedBoxです。CsvFileクラスは、別のオブジェクトが定義されていても、それらを別々に抽出することが可能になってます。
 TilingFixedBoxの読み込みですがGameStage::CreateFixedBox()関数を見てください。
//ボックスの作成
void GameStage::CreateFixedBox() {
    //CSVの行単位の配列
    vector<wstring> LineVec;
    //0番目のカラムがL"TilingFixedBox"である行を抜き出す
    m_GameStageCsvB.GetSelect(LineVec, 0, L"TilingFixedBox");
    for (auto& v : LineVec) {
        //トークン(カラム)の配列
        vector<wstring> Tokens;
        //トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
        Util::WStrToTokenVector(Tokens, v, L',');
        //各トークン(カラム)をスケール、回転、位置に読み込む
        Vec3 Scale(
            (float)_wtof(Tokens[1].c_str()),
            (float)_wtof(Tokens[2].c_str()),
            (float)_wtof(Tokens[3].c_str())
        );
        Vec3 Rot;
        //回転はXM_PIDIV2の文字列になっている場合がある
        Rot.x = (Tokens[4] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[4].c_str());
        Rot.y = (Tokens[5] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[5].c_str());
        Rot.z = (Tokens[6] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[6].c_str());
        Vec3 Pos(
            (float)_wtof(Tokens[7].c_str()),
            (float)_wtof(Tokens[8].c_str()),
            (float)_wtof(Tokens[9].c_str())
        );
        //各値がそろったのでオブジェクト作成
        AddGameObject<TilingFixedBox>(Scale, Rot, Pos, 1.0f, 1.0f, Tokens[10]);
    }
}
 ここでまずポイントとなるのは
    //CSVの行単位の配列
    vector<wstring> LineVec;
    //0番目のカラムがL"TilingFixedBox"である行を抜き出す
    m_GameStageCsvB.GetSelect(LineVec, 0, L"TilingFixedBox");
 です。m_GameStageCsvBCsvFileクラスのインスタンスです。あらかじめGameStageB.csvの内容が読み込まれています。これをGetSelect()関数行の抽出を行います。最初の引数のLineVecは抽出された行の配列が返される文字列の配列です。2番目の00番目のカラム(列)という意味です。3番目の引数 L"TilingFixedBox"は指定したカラムがこの文字列だったらとおいう意味です。
 ですから上記の抽出条件は0番目のカラムがL"TilingFixedBox"である行を抽出しなさいという意味です。
 こうすると、LineVecには
TilingFixedBox,50,1,50,0,0,0,0,-0.5,0,SKY_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,19.5,WALL_TX
TilingFixedBox,40,1,1,0,0,0,0,0.5,-19.5,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,19.5,0.5,0,WALL_TX
TilingFixedBox,40,1,1,0,XM_PIDIV2,0,-19.5,0.5,0,WALL_TX
 だけが抽出されます。
 それぞれの行には名前のあとにスケーリング、回転、位置が各3つのカラムを使用してセットされています。回転に関しては、2分のパイを表すXM_PIDIV2が入っている可能性があります。これらを加味して、各値を読み込みます。また、行の最後にテクスチャ名があります。
        //トークン(カラム)の配列
        vector<wstring> Tokens;
        //トークン(カラム)単位で文字列を抽出(L','をデリミタとして区分け)
        Util::WStrToTokenVector(Tokens, v, L',');
        //各トークン(カラム)をスケール、回転、位置に読み込む
        Vec3 Scale(
            (float)_wtof(Tokens[1].c_str()),
            (float)_wtof(Tokens[2].c_str()),
            (float)_wtof(Tokens[3].c_str())
        );
        Vec3 Rot;
        //回転はXM_PIDIV2の文字列になっている場合がある
        Rot.x = (Tokens[4] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[4].c_str());
        Rot.y = (Tokens[5] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[5].c_str());
        Rot.z = (Tokens[6] == L"XM_PIDIV2") ? XM_PIDIV2 : (float)_wtof(Tokens[6].c_str());
        Vec3 Pos(
            (float)_wtof(Tokens[7].c_str()),
            (float)_wtof(Tokens[8].c_str()),
            (float)_wtof(Tokens[9].c_str())
        );
        //各値がそろったのでオブジェクト作成
        AddGameObject<TilingFixedBox>(Scale, Rot, Pos, 1.0f, 1.0f, Tokens[10]);
 で、TilingFixedBoxに渡す変数をセットして、オブジェクトを配置します。

 このようにCsvFile::GetSelect()関数を使うことで複数のタイプのオブジェクトが定義されているCSVファイルから特定の行を抽出することが可能です。
 またCsvFile::GetSelect()関数には多重定義されたもう一つの関数があり、こちらは関数へのポインタコールバック関数として渡します。これはX番目のカラムが○〇〇である行を抽出しなさいという抽出方法では特定できない場合にプログラマが抽出方法を定義できます。渡す関数へのポインタは
    void GetSelect(vector< wstring >& RetVec,bool (Func)(const wstring&));
 となってるようにwstringを受け取ってbool値を返す関数です。staticな関数を別に定義してその関数を渡します。
 そしてもう一つCsvFile::GetSelect2()テンプレート関数があります。こちらはラムダ式を渡せる関数ですので、関数を別に記述しなくても抽出ロジックを記述できます。

 さて、このようにしてTilingFixedBoxを抽出しましたが、おなじCSVに入っているTilingPlateの同様の方法で抽出します。

 このように、CsvFileクラスセル化されたマップの読み込みの他にもいろんな使い方ができます。
 この手法はデータベース操作を経験した人には、なんとなくSQL文のSELECTに似ていると思うかもしれません。SQL文のSELECTほど多機能ではありませんが、その縮小版と考えていいと思います。
 またCSVの保存も可能ですので、たとえばプレイヤーが到達した場所とかレベル、最高得点なども保存できます。

タイリングするオブジェクト

 さて、このサンプルのTilingFixedBoxはタイリング処理が実装されています。
 CSVとは直接関係ありませんが、少し説明します。
 タイリング処理(テクスチャをタイル状に並べる処理)は、L"DEFAULT_CUBE"のような、リソース化されたメッシュは使えません。頂点のUV値を変更する必要があるからですが、そのため上記クラスは独自のメッシュを作成して使用しています。以下、TilingFixedBoxOnCreate()関数ですが、
void TilingFixedBox::OnCreate() {
    auto PtrTrans = GetComponent<Transform>();
    PtrTrans->SetScale(m_Scale);
    PtrTrans->SetRotation(m_Rotation);
    PtrTrans->SetPosition(m_Position);
    auto Coll = AddComponent<CollisionObb>();
    Coll->SetFixed(true);
    vector<VertexPositionNormalTexture> vertices;
    vector<uint16_t> indices;
    MeshUtill::CreateCube(1.0f, vertices, indices);
    float UCount = m_Scale.x / m_UPic;
    float VCount = m_Scale.z / m_VPic;
    for (size_t i = 0; i < vertices.size(); i++) {
        if (vertices[i].textureCoordinate.x >= 1.0f) {
            vertices[i].textureCoordinate.x = UCount;
        }
        if (vertices[i].textureCoordinate.y >= 1.0f) {
            float FrontBetween = bsm::angleBetweenNormals(vertices[i].normal, Vec3(0, 1, 0));
            float BackBetween = bsm::angleBetweenNormals(vertices[i].normal, Vec3(0, -1, 0));
            if (FrontBetween < 0.01f || BackBetween < 0.01f) {
                vertices[i].textureCoordinate.y = VCount;
            }
        }
    }
    //描画コンポーネントの追加
    auto PtrDraw = AddComponent<BcPNTStaticDraw>();
    //描画コンポーネントに形状(メッシュ)を設定
    PtrDraw->CreateOriginalMesh(vertices, indices);
    PtrDraw->SetOriginalMeshUse(true);
    PtrDraw->SetFogEnabled(true);
    //自分に影が映りこむようにする
    PtrDraw->SetOwnShadowActive(true);
    //描画コンポーネントテクスチャの設定
    PtrDraw->SetTextureResource(m_Texname);
    //タイリング設定
    PtrDraw->SetSamplerState(SamplerState::LinearWrap);
}
 上記の中でまずしなければいけないのはオリジナルメッシュの作成準備です。
    vector<VertexPositionNormalTexture> vertices;
    vector<uint16_t> indices;
    MeshUtill::CreateCube(1.0f, vertices, indices);
    float UCount = m_Scale.x / m_UPic;
    float VCount = m_Scale.z / m_VPic;
    for (size_t i = 0; i < vertices.size(); i++) {
        if (vertices[i].textureCoordinate.x >= 1.0f) {
            vertices[i].textureCoordinate.x = UCount;
        }
        if (vertices[i].textureCoordinate.y >= 1.0f) {
            float FrontBetween = bsm::angleBetweenNormals(vertices[i].normal, Vec3(0, 1, 0));
            float BackBetween = bsm::angleBetweenNormals(vertices[i].normal, Vec3(0, -1, 0));
            if (FrontBetween < 0.01f || BackBetween < 0.01f) {
                vertices[i].textureCoordinate.y = VCount;
            }
        }
    }
 のようにCubeを作成したあと、タイリングのための変数(コンストラクタであらかじめ渡されている値)にUV値を書き換えます。
 その後、DrawコンポーネントのCreateOriginalMesh()関数でオリジナルメッシュを作成し、オリジナルメッシュを使うフラグをtrueにします。
    //描画コンポーネントの追加
    auto PtrDraw = AddComponent<BcPNTStaticDraw>();
    //描画コンポーネントに形状(メッシュ)を設定
    PtrDraw->CreateOriginalMesh(vertices, indices);
    PtrDraw->SetOriginalMeshUse(true);
 これでタイリングされたオブジェクトがメッシュになります。また、
    //タイリング設定
    PtrDraw->SetSamplerState(SamplerState::LinearWrap);
 でサンプラーの設定をSamplerState::LinearWrapにするのを忘れないで記述しましょう。