13.Draw系の操作とオーディオ

1301.さまざまなスプライト

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

 実行結果は以下のような画面が出ます。

 

図1301a

 

 ここで表示されているスプライトCharacter.h/cppに記述があります。

半透明のスプライト

 半透明のスプライト(左側のもの)はTraceSpriteクラスです。
 このスプライトはVertexPositionColorという頂点定義で構成されるスプライトです。メンバ関数OnCreate()により初期化されます。以下はTraceSprite::OnCreate()関数です。
void TraceSprite::OnCreate() {
    float helfSize = 0.5f;
    //頂点配列
    m_BackupVertices = {
        { VertexPositionColor(Vec3(-helfSize, helfSize, 0),Col4(1.0f,0.0f,0.0f,0.0f)) },
        { VertexPositionColor(Vec3(helfSize, helfSize, 0), Col4(0.0f, 1.0f, 0.0f, 0.0f)) },
        { VertexPositionColor(Vec3(-helfSize, -helfSize, 0), Col4(0.0f, 0.0f, 1.0f, 0.0f)) },
        { VertexPositionColor(Vec3(helfSize, -helfSize, 0), Col4(0.0f, 0.0f, 0, 0.0f)) },
    };
    //インデックス配列
    vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
    SetAlphaActive(m_Trace);
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetScale(m_StartScale.x, m_StartScale.y, 1.0f);
    ptrTrans->SetRotation(0, 0, 0);
    ptrTrans->SetPosition(m_StartPos);
    //頂点とインデックスを指定してスプライト作成
    AddComponent<PCSpriteDraw>(m_BackupVertices, indices);
}
 まず、ここでは、メンバ変数(vector<VertexPositionColor>型)のm_BackupVerticesに頂点を設定します。
 頂点は4つです。ローカル座標は原点を中心に、上下左右1.0の大きさになるようにします。この、頂点のローカルサイズはどの頂点型でも同じです。
 頂点の配列を作成したらインデックスを作成します。時計回りの三角形を2つ描画するように、頂点の順番(インデックス)を配列化します。インデックス配列はバックアップしておく必要はありません。動的に頂点の変更は行いますが、インデックスは変化させません。
 頂点を作成したらTransformコンポーネントで大きさや位置を設定するわけですが、スプライトの場合、通常の3Dとは座標系が違います。スプライトの座標系はディスプレイのピクセルになります。(ただしfloatで設定)。
 以下は、TraceSpriteをステージに実装しているコードです。GameStage.cppにあります。
    void GameStage::CreateTraceSprite() {
        AddGameObject<TraceSprite>(true,
            Vec2(320.0f, 320.0f), Vec3(-400.0f, 0.0f, 0.0f));
    }
 パラメータはtrue,Vec2(320.0f, 320.0f), Vec3(-400.0f, 0.0f, 0.0f)となってますが、最初からtraceするかどうか、スケール、位置の順番です。スケールはVec2(320.0f, 320.0f)(Zスケールは1.0固定)で、位置は、X方向に400.0fだけ左に寄っています。3Dがメートル単位を想定しているのに対して、このようにディスプレイのピクセルになっているので注意しましょう。
 このスプライトは動的に色が変化します。これは頂点を変更することで実装します。TraceSprite::OnUpdate()関数に記述があります。
void TraceSprite::OnUpdate() {
    float elapsedTime = App::GetApp()->GetElapsedTime();
    m_TotalTime += elapsedTime;
    if (m_TotalTime >= XM_PI) {
        m_TotalTime = 0;
    }
    vector<VertexPositionColor> newVertices;
    for (size_t i = 0; i < m_BackupVertices.size(); i++) {
        Col4 col = m_BackupVertices[i].color;
        col.w = sin(m_TotalTime);
        auto v = VertexPositionColor(
            m_BackupVertices[i].position,
            col
        );
        newVertices.push_back(v);
    }
    auto ptrDraw = GetComponent<PCSpriteDraw>();
    ptrDraw->UpdateVertices(newVertices);

}
 m_TotalTimeというのはメンバ変数で、時間のカウンタです。毎ターンごとにelapsedTime(1つ前のターンからの経過時間)を加算します。
 このオブジェクトの頂点の変化は色の変化です。変化させるのにsin(サイン)カーブを使います。その変数にm_TotalTimeを使います。sinカーブはXM_PI(すなわち、ラジアンではπ。度数では180度)を超えたところで初期化(0.0fにする)します。
 sinカーブは2π(つまり360度)で一回りしますが、マイナス値にならないようにしています。
 頂点の変化はm_BackupVerticescolor要素を変化させてnewVerticesを作成します。
        col.w = sin(m_TotalTime);
 という計算で、色の透明度を変化させます。sin(m_TotalTime)は0.0から1.0を行ったり来たりすることになります。
 新しい頂点ができたら、描画コンポーネントを取り出し
    ptrDraw->UpdateVertices(newVertices);
 と頂点を更新します。

壁模様のスプライト

 右側のスプライトのうち下地になっているのが壁模様のスプライト(WallSpriteクラス)です。
 VertexPositionColorTexture頂点を使います。以下はWallSprite::OnCreate()関数です。
void WallSprite::OnCreate() {
    float helfSize = 0.5f;
    //頂点配列(縦横5個ずつ表示)
    vector<VertexPositionColorTexture> vertices = {
        { VertexPositionColorTexture(Vec3(-helfSize, helfSize, 0),Col4(1.0f,1.0f,1.0f,1.0f), Vec2(0.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, helfSize, 0), Col4(0.0f, 1.0f, 1.0f, 1.0f), Vec2(5.0f, 0.0f)) },
        { VertexPositionColorTexture(Vec3(-helfSize, -helfSize, 0), Col4(1.0f, 0.0f, 1.0f, 1.0f), Vec2(0.0f, 5.0f)) },
        { VertexPositionColorTexture(Vec3(helfSize, -helfSize, 0), Col4(0.0f, 0.0f, 0, 1.0f), Vec2(5.0f, 5.0f)) },
    };
    //インデックス配列
    vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
    SetAlphaActive(m_Trace);
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetScale(m_StartScale.x, m_StartScale.y, 1.0f);
    ptrTrans->SetRotation(0, 0, 0);
    ptrTrans->SetPosition(m_StartPos);
    //頂点とインデックスを指定してスプライト作成
    auto ptrDraw = AddComponent<PCTSpriteDraw>(vertices, indices);
    ptrDraw->SetSamplerState(SamplerState::LinearWrap);
    ptrDraw->SetTextureResource(m_TextureKey);
}
 ここで重要なのは赤くなっている、描画コンポーネントのSetSamplerState()関数です。
 SamplerState::LinearWrapを使うことで、テクスチャが繰り返し(タイリング)描画されるようになります。
 このタイリング描画は重要で、例えば3D上でも、大きなオブジェクトを演出するのに、小さなオブジェクトを並べるのではなく、大きなオブジェクトをタイリング処理することで、例えば衝突判定のひっかかりもなくなりますし、動作速度も速くなります。
 もう一つこのオブジェクトで注意したいのは表示場所(ポジション)です。
 以下はGameStage.cpp内のWallSpriteを作り出す部分です。
    void GameStage::CreateWallSprite() {
        AddGameObject<WallSprite>(L"WALL_TX", false,
            Vec2(320.0f, 320.0f), Vec3(400.0f, 0.0f, 0.1f));
    }
 赤くなっているのはポジションのZ位置です。0.0fではなく0.1fになっています。
 これはこの上にもう一つスプライトが乗るので、少し奥に引っ込めているのです。
 スプライトのZ位置は0.0fから1.0fの間で指定します。これは、デバイス座標と呼ばれるものです。DirectXはアクセラレータのZ方向は0.0fから1.0fの間です。1.0に近い方が遠くなります。
 これを超えた値に設定しても、有効範囲にクランプ(切り詰め)られるので注意しましょう。
 正確にはこの値が、すべてのケースでデバイス座標になるわけではなく、ビューの深さによって調整されます。例えば複数のビューを持った場合は、ターゲットとなるビューの深さの範囲に収められます。
 ただしこのサンプルは単一のビューなので、デバイス座標と同じになります。

スクロールするスプライト

 壁模様のスプライトの上に乗っているのが、スクロールするスプライトです。ScrollSpriteクラスになります。
 以下はScrollSprite::OnCreate()関数です。
void ScrollSprite::OnCreate() {
    float helfSize = 0.5f;
    //頂点配列
    m_BackupVertices = {
        { VertexPositionTexture(Vec3(-helfSize, helfSize, 0), Vec2(0.0f, 0.0f)) },
        { VertexPositionTexture(Vec3(helfSize, helfSize, 0), Vec2(4.0f, 0.0f)) },
        { VertexPositionTexture(Vec3(-helfSize, -helfSize, 0), Vec2(0.0f, 1.0f)) },
        { VertexPositionTexture(Vec3(helfSize, -helfSize, 0), Vec2(4.0f, 1.0f)) },
    };
    //インデックス配列
    vector<uint16_t> indices = { 0, 1, 2, 1, 3, 2 };
    SetAlphaActive(m_Trace);
    auto ptrTrans = GetComponent<Transform>();
    ptrTrans->SetScale(m_StartScale.x, m_StartScale.y, 1.0f);
    ptrTrans->SetRotation(0, 0, 0);
    ptrTrans->SetPosition(m_StartPos);
    //頂点とインデックスを指定してスプライト作成
    auto ptrDraw = AddComponent<PTSpriteDraw>(m_BackupVertices, indices);
    ptrDraw->SetSamplerState(SamplerState::LinearWrap);
    ptrDraw->SetTextureResource(m_TextureKey);
}
 このスプライトはVertexPositionTexture頂点を使っています。つまり色要素を含まない頂点となります。
 このスプライトも壁模様のスプライト同様、サンプラーにSamplerState::LinearWrapを使ってタイリング処理をします。
 このスプライトはVertexPositionTexture頂点ですので、描画コンポーネントはPTSpriteDrawを使います。
 このスプライトの変化処理は模様が流れるように動く処理です。ScrollSprite::OnUpdate()に記述します。
void ScrollSprite::OnUpdate() {
    float elapsedTime = App::GetApp()->GetElapsedTime();
    m_TotalTime += elapsedTime;
    if (m_TotalTime > 1.0f) {
        m_TotalTime = 0;
    }
    vector<VertexPositionTexture> newVertices;
    for (size_t i = 0; i < m_BackupVertices.size(); i++) {
        Vec2 uv = m_BackupVertices[i].textureCoordinate;
        if (uv.x == 0.0f) {
            uv.x = m_TotalTime;
        }
        else if (uv.x == 4.0f) {
            uv.x += m_TotalTime;
        }
        auto v = VertexPositionTexture(
            m_BackupVertices[i].position,
            uv
        );
        newVertices.push_back(v);
    }
    auto ptrDraw = GetComponent<PTSpriteDraw>();
    ptrDraw->UpdateVertices(newVertices);
}
 ここではm_BackupVertices[i].textureCoordinateを変化させます。流れるように模様が変わるのはすなわちテクスチャUV値が変化するということです。
 バックアップ頂点のUV値のX(すなわちU値)は、0.0fもしくはタイリング数4.0fのどちらかです。
 それを時間経過によって変化させます。もし縦方向の変化が必要な場合はV値を変化させます。
 このスプライトは壁模様のスプライトの上に乗っています。壁模様のスプライトZ位置を0.1fとしていました。ですのでこのスプライトのZ位置は0.0fで、手前に描画されます。

スコア表示のスプライト

 ゲーム中にスコア(ポイントなど)を表示したいことはよくあります。
 こんな時はスコア表示のスプライトのように、複数の数字を変化させながら表示させると便利です。
 以下はScoreSprite::OnCreate()の抜粋ですが、
void ScoreSprite::OnCreate() {
    float xPiecesize = 1.0f / (float)m_NumberOfDigits;
    float helfSize = 0.5f;

    //インデックス配列
    vector<uint16_t> indices;
    for (UINT i = 0; i < m_NumberOfDigits; i++) {
        float vertex0 = -helfSize + xPiecesize * (float)i;
        float vertex1 = vertex0 + xPiecesize;
        //0
        m_BackupVertices.push_back(
            VertexPositionTexture(Vec3(vertex0, helfSize, 0), Vec2(0.0f, 0.0f))
        );
        //1
        m_BackupVertices.push_back(
            VertexPositionTexture(Vec3(vertex1, helfSize, 0), Vec2(0.1f, 0.0f))
        );
        //2
        m_BackupVertices.push_back(
            VertexPositionTexture(Vec3(vertex0, -helfSize, 0), Vec2(0.0f, 1.0f))
        );
        //3
        m_BackupVertices.push_back(
            VertexPositionTexture(Vec3(vertex1, -helfSize, 0), Vec2(0.1f, 1.0f))
        );
        indices.push_back(i * 4 + 0);
        indices.push_back(i * 4 + 1);
        indices.push_back(i * 4 + 2);
        indices.push_back(i * 4 + 1);
        indices.push_back(i * 4 + 3);
        indices.push_back(i * 4 + 2);
    }

//中略

    //頂点とインデックスを指定してスプライト作成
    auto ptrDraw = AddComponent<PTSpriteDraw>(m_BackupVertices, indices);
}
 ここで頂点の配列は、表示する桁数分の横並びに作成します。
 ScoreSprite::OnUpdate()ではスコアであるm_Scoreの値をもとに、10進数の各桁を数値に変換し、そのテクスチャ位置のUV値を設定します。
 このm_Scoreは外部から変更できるように SetScore()関数を用意しています。
 実際のm_Scoreの変更は、GameStage.cppにあるGameStage::OnUpdate()で行います。
void GameStage::OnUpdate() {
    float elapsedTime = App::GetApp()->GetElapsedTime();
    m_TotalTime += elapsedTime;
    if (m_TotalTime >= 10000.0f) {
        m_TotalTime = 0.0f;
    }
    //スコアを更新する
    auto ptrScor = GetSharedGameObject<ScoreSprite>(L"ScoreSprite");
    ptrScor->SetScore(m_TotalTime);
}

 このサンプルでは、このようにいろんな頂点タイプが違うスプライトの表示と更新について説明しました。