Skip to main content

3Dの移動計算(回転して移動)[ゲーム数学、プログラミング基礎]

戦車を「向いている方向」に動かすプログラムを作ろう

1. 今日の目標

今回は、3D空間で戦車を動かす処理を作ります。

完成すると、次の操作ができるようになります。

キー 動き
Aキー 左に回転する
Dキー 右に回転する
Wキー 戦車が向いている方向に進む

今回のポイントは、ただ前に進むだけではありません。

ワールドの+Z方向に進むのではなく、
戦車が向いている方向に進む

という処理を作ります。


2. 今日作る処理の方針

今回の移動処理は、次の流れで作ります。

Aキー・Dキーで戦車の角度を変える
↓
現在位置を計算用のベクトル型に変換する
↓
まずは+Z方向に進む移動ベクトルを作る
↓
戦車のY軸回転角度から回転行列を作る
↓
移動ベクトルを回転行列で回転させる
↓
Wキーが押されたら、現在位置に移動ベクトルを足す
↓
計算結果をtransform_.position_に戻す

この流れを先に確認しておくと、これから書く1行1行の意味がわかりやすくなります。


3. なぜ移動ベクトルを回転させるのか

戦車を動かすだけなら、現在位置に移動量を足せば動きます。

しかし今回は、

戦車が向いている方向に進む

ようにしたいです。

例えば、移動ベクトルを常に

+Z方向に 0.1 進む

としてしまうと、戦車が右を向いても左を向いても、移動方向はずっと+Z方向のままです。

これでは、戦車の見た目は横を向いているのに、移動だけは+Z方向へ滑っていくような動きになります。

そこで、次のように考えます。

まず、前に進む移動ベクトルを作る
↓
その移動ベクトルを、戦車の向きと同じだけ回転させる
↓
回転した移動ベクトルを、現在位置に足す

今回一番大事なのは、

移動ベクトルを、戦車の向きに合わせて回転させる

という考え方です。


4. 3D空間での「戦車の向き」

今回の戦車は、最初は +Z方向 を向いているものとします。

上から見たイメージは次のようになります。

          +Z
           ↑
           |
           |
-X ←---- 戦車 ----→ +X
           |
           |
           ↓
          -Z

戦車が左右に向きを変えるときは、主に Y軸回転 を使います。

Y軸回転とは、上から見たときに向きを変える回転です。


5. 必要な情報

戦車を動かすには、次の情報が必要です。

必要な情報 内容
現在位置 戦車が今どこにいるか
回転角度 戦車がどちらを向いているか
移動量 1フレームでどれだけ進むか

今回使う変数は、次のようなものだと考えます。

transform_.position_   // 戦車の位置
transform_.rotate_.y   // 戦車のY軸回転

穴埋めでUpdate関数を作る

ここから、戦車の Update 関数を少しずつ完成させます。

最初から完成コードを見るのではなく、処理の意味を確認しながら、1行ずつ作っていきます。


6. AキーとDキーで回転させる

まず、AキーとDキーで戦車の角度を変えます。

キー 処理
Aキー Y軸回転角度を小さくする
Dキー Y軸回転角度を大きくする

穴埋め 1

次のコードを完成させなさい。

// Aキーが押されたら左回転
if (Input::IsKey(DIK_A))
{
    transform_.rotate_.y ____ 2;
}

// Dキーが押されたら右回転
if (Input::IsKey(DIK_D))
{
    transform_.rotate_.y ____ 2;
}

ヒント

左回転では角度を小さくします。
右回転では角度を大きくします。


7. 現在位置を計算用の型に変換する

transform_.position_ は、戦車の現在位置です。

ただし、DirectXMathでベクトル計算をするためには、XMVECTOR 型に変換して使います。

そのため、まず現在位置を XMVECTOR 型の変数に読み込みます。

穴埋め 2

次のコードを完成させなさい。

// 戦車の現在地をベクトル型に変換
XMVECTOR vPos = ____________________________;

使う関数

XMLoadFloat3(&transform_.position_)

8. まずは「前に進むベクトル」を作る

次に、1フレーム分の移動ベクトルを作ります。

今回は、戦車が最初に向いている方向を +Z方向 とします。

そのため、最初の移動ベクトルは次のように考えます。

X方向には進まない
Y方向には進まない
Z方向に 0.1 進む

つまり、移動ベクトルは次のようになります。

成分 意味
X 0.0 横方向には進まない
Y 0.0 上下方向には進まない
Z 0.1 前方向に進む
W 0.0 ここでは移動ベクトルとして扱う

穴埋め 3

次のコードを完成させなさい。

// 1フレームの移動ベクトル
XMVECTOR vMove{ ____ , ____ , ____ , ____ };

入れる値

0.0f
0.0f
0.1f
0.0f

9. このままだと何が問題か

ここまでだと、移動ベクトルは常に次の意味になります。

+Z方向に0.1進む

しかし、戦車が回転している場合、このままでは問題があります。

戦車が右を向いても、左を向いても、移動方向はずっと +Z方向 のままです。

穴埋め 4

次の文章を完成させなさい。

このままだと、戦車の見た目が回転しても、
移動方向はずっと __________ 方向のままになる。

そのため、戦車が横を向いているのに、
__________ 方向へ滑るように進んでしまう。

10. 回転行列を作る

移動ベクトルを戦車の向きに合わせるには、移動ベクトルを回転させる必要があります。

ベクトルを回転させるには、回転行列を作ります。

今回はY軸まわりに回転させたいので、使う関数は次のものです。

XMMatrixRotationY()

ただし、この関数に渡す角度は ではなく ラジアン です。

そのため、角度をラジアンに変換する必要があります。

使う関数は次のものです。

XMConvertToRadians()

穴埋め 5

次のコードを完成させなさい。

// transform_.rotate_.y度回転させる行列を作成
XMMATRIX mRotY = XMMatrixRotationY(____________________________);

使う式

XMConvertToRadians(transform_.rotate_.y)

11. 移動ベクトルを回転行列で変形する

回転行列を作ったら、移動ベクトルをその行列で変形します。

使う関数は次のものです。

XMVector3TransformCoord()

この処理によって、

+Z方向に進むベクトル

が、

戦車が向いている方向に進むベクトル

に変わります。

穴埋め 6

次のコードを完成させなさい。

// 移動ベクトルを戦車の向きに合わせて回転
vMove = XMVector3TransformCoord(__________, __________);

入れるもの

vMove
mRotY

12. Wキーで前進する

最後に、Wキーが押されたら戦車を移動させます。

移動は、次の考え方です。

現在位置 = 現在位置 + 移動ベクトル

つまり、現在位置に、今回作った移動ベクトルを足します。

そのあと、計算した位置を transform_.position_ に戻します。

穴埋め 7

次のコードを完成させなさい。

// Wキーが押されたら
if (Input::IsKey(DIK_W))
{
    // 移動
    vPos ____ vMove;

    // 現在地をtransform_.position_に戻す
    XMStoreFloat3(________________________, ________);
}

ヒント

現在位置に移動量を足すので、次のようになります。

vPos += vMove;

XMStoreFloat3 では、計算した vPostransform_.position_ に戻します。


13. Update関数を完成させよう

ここまでの穴埋めをすべてつなげると、戦車の移動処理になります。

穴埋め 8

次の Update 関数を完成させなさい。

// 更新
void Tank::Update()
{
    // Aキーが押されたら左回転
    if (Input::IsKey(DIK_A))
    {
        transform_.rotate_.y ____ 2;
    }

    // Dキーが押されたら右回転
    if (Input::IsKey(DIK_D))
    {
        transform_.rotate_.y ____ 2;
    }

    // 戦車の現在地をベクトル型に変換
    XMVECTOR vPos = ____________________________;

    // 1フレームの移動ベクトル
    XMVECTOR vMove{ ____ , ____ , ____ , ____ };

    // Y軸回転行列を作成
    XMMATRIX mRotY = XMMatrixRotationY(____________________________);

    // 移動ベクトルを戦車の向きに合わせて回転
    vMove = XMVector3TransformCoord(__________, __________);

    // Wキーが押されたら
    if (Input::IsKey(DIK_W))
    {
        // 移動
        vPos ____ vMove;

        // 計算結果をtransform_.position_に戻す
        XMStoreFloat3(________________________, ________);
    }
}

14. 完成コード

自分で穴埋めを終えてから確認しましょう。

// 更新
void Tank::Update()
{
    // Aキーが押されたら左回転
    if (Input::IsKey(DIK_A))
    {
        transform_.rotate_.y -= 2;
    }

    // Dキーが押されたら右回転
    if (Input::IsKey(DIK_D))
    {
        transform_.rotate_.y += 2;
    }

    // 戦車の現在地をベクトル型に変換
    XMVECTOR vPos = XMLoadFloat3(&transform_.position_);

    // 1フレームの移動ベクトル
    XMVECTOR vMove{ 0.0f, 0.0f, 0.1f, 0.0f };

    // transform_.rotate_.y度回転させる行列を作成
    XMMATRIX mRotY = XMMatrixRotationY(XMConvertToRadians(transform_.rotate_.y));

    // 移動ベクトルを戦車の向きに合わせて回転
    vMove = XMVector3TransformCoord(vMove, mRotY);

    // Wキーが押されたら
    if (Input::IsKey(DIK_W))
    {
        // 移動
        vPos += vMove;

        // 現在地をtransform_.position_に戻す
        XMStoreFloat3(&transform_.position_, vPos);
    }
}

15. 処理の流れをもう一度確認する

今回の処理は、次の順番でした。

Aキー・Dキーで戦車の角度を変える
↓
現在位置を計算用のベクトル型に変換する
↓
まずは+Z方向に進む移動ベクトルを作る
↓
戦車のY軸回転角度から回転行列を作る
↓
移動ベクトルを回転行列で回転させる
↓
Wキーが押されたら、現在位置に移動ベクトルを足す
↓
計算結果をtransform_.position_に戻す

重要なのは、次の流れです。

前方向の移動ベクトルを作る
↓
戦車の向きに合わせて回転させる
↓
現在位置に足す

16. まとめ穴埋め

次の文章を完成させなさい。

戦車を向いている方向に進ませるには、
まず __________ 方向に進む移動ベクトルを作る。

しかし、そのままだと常に __________ 方向に進んでしまう。

そこで、戦車の __________ 角度を使って、
__________ 行列を作る。

その行列で __________ ベクトルを回転させることで、
戦車の向いている方向に進めるようになる。

最後に、現在位置に __________ を足すことで、
戦車の位置が更新される。

17. 確認問題

問題1

次の処理は何をしているか説明しなさい。

transform_.rotate_.y += 2;

問題2

次の移動ベクトルは、どの方向にどれだけ進むベクトルか答えなさい。

XMVECTOR vMove{ 0.0f, 0.0f, 0.1f, 0.0f };

問題3

なぜ、移動ベクトルを回転させる必要があるのか説明しなさい。


問題4

次の処理は何をしているか説明しなさい。

vPos += vMove;

問題5

XMMatrixRotationY() に角度を渡す前に、なぜ XMConvertToRadians() を使っているのか説明しなさい。


18. 解答例

穴埋め1

transform_.rotate_.y -= 2;
transform_.rotate_.y += 2;

穴埋め2

XMLoadFloat3(&transform_.position_)

穴埋め3

XMVECTOR vMove{ 0.0f, 0.0f, 0.1f, 0.0f };

穴埋め4

このままだと、戦車の見た目が回転しても、
移動方向はずっと +Z 方向のままになる。

そのため、戦車が横を向いているのに、
+Z 方向へ滑るように進んでしまう。

穴埋め5

XMConvertToRadians(transform_.rotate_.y)

穴埋め6

vMove = XMVector3TransformCoord(vMove, mRotY);

穴埋め7

vPos += vMove;

XMStoreFloat3(&transform_.position_, vPos);

穴埋め8

// 更新
void Tank::Update()
{
    // Aキーが押されたら左回転
    if (Input::IsKey(DIK_A))
    {
        transform_.rotate_.y -= 2;
    }

    // Dキーが押されたら右回転
    if (Input::IsKey(DIK_D))
    {
        transform_.rotate_.y += 2;
    }

    // 戦車の現在地をベクトル型に変換
    XMVECTOR vPos = XMLoadFloat3(&transform_.position_);

    // 1フレームの移動ベクトル
    XMVECTOR vMove{ 0.0f, 0.0f, 0.1f, 0.0f };

    // Y軸回転行列を作成
    XMMATRIX mRotY = XMMatrixRotationY(XMConvertToRadians(transform_.rotate_.y));

    // 移動ベクトルを戦車の向きに合わせて回転
    vMove = XMVector3TransformCoord(vMove, mRotY);

    // Wキーが押されたら
    if (Input::IsKey(DIK_W))
    {
        // 移動
        vPos += vMove;

        // 計算結果をtransform_.position_に戻す
        XMStoreFloat3(&transform_.position_, vPos);
    }
}

まとめ穴埋め

戦車を向いている方向に進ませるには、
まず +Z 方向に進む移動ベクトルを作る。

しかし、そのままだと常に +Z 方向に進んでしまう。

そこで、戦車の Y軸回転 角度を使って、
回転 行列を作る。

その行列で 移動 ベクトルを回転させることで、
戦車の向いている方向に進めるようになる。

最後に、現在位置に 移動ベクトル を足すことで、
戦車の位置が更新される。

19. まとめ

戦車の移動

1. A/Dキーで角度を変える
2. 前方向の移動ベクトルを作る
3. 戦車の角度から回転行列を作る
4. 移動ベクトルを回転させる
5. 現在位置に足す

重要:
位置を回転させるのではなく、
移動方向を回転させてから位置に足す。