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 では、計算した vPos を transform_.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. 現在位置に足す
重要:
位置を回転させるのではなく、
移動方向を回転させてから位置に足す。
No Comments