Skip to main content

DxLibで3Dキャラクターの移動(基本)

カメラの違いによるキャラクター移動操作

このサンプルでは、同じプレイヤー操作を使いながら、カメラの種類によって見え方や操作感がどのように変わるかを確認します。

使用するカメラは次の3種類です。

  • 固定カメラ
  • TPSカメラ
  • OTSカメラ

カメラの種類は CAMERA_MODE で管理しており、CAMERA_FIXEDCAMERA_TPSCAMERA_OTS の3種類が定義されています。
実行中は Cキー を押すことで、固定カメラ → TPSカメラ → OTSカメラ の順に切り替わります。
サンプルプログラムをクローンして、実行してみてください。
https://github.com/youetsux/radioControlMan


共通のプレイヤー操作

プレイヤーの基本操作は、どのカメラでも共通です。

キー

動作

W

前進

S

後退

A

左回転

D

右回転

C

カメラ切り替え

プレイヤーは AキーDキー でY軸方向に回転します。
その回転角度から、現在の進行方向ベクトル dir_ を計算しています。

float rad = DirectX::XMConvertToRadians(rotY_);dir_ = VGet(sinf(rad), 0.0f, cosf(rad));

Wキー を押すと、進行方向 dir_ に向かって前進します。
Sキー を押すと、進行方向と逆向きに後退します。

pos_ = VAdd(pos_, VScale(dir_, speed_ * dt));

つまり、このサンプルの移動は「カメラの向き」ではなく、「キャラクター自身の向き」を基準にしています。


固定カメラでのラジコン移動操作

固定カメラとは

固定カメラは、カメラの位置と注視点が常に決まっているカメラです。

このサンプルでは、カメラ位置を (0, 10, -10)、注視点を (0, 0, 0) に固定しています。

VECTOR FIXED_POS    = VGet(0.0f, 10.0f, -10.0f);VECTOR FIXED_TARGET = VGet(0.0f, 0.0f, 0.0f);

固定カメラモードでは、プレイヤーが移動してもカメラは動きません。
常に同じ位置からステージを見下ろすような見え方になります。

SetCameraPositionAndTarget_UpVecY(FIXED_POS, FIXED_TARGET);

固定カメラのサンプル動画


ラジコン操作の特徴

固定カメラでは、画面の上方向とキャラクターの前方向が常に一致するとは限りません。

そのため、操作感としてはラジコンに近くなります。

  • Aキー:キャラクターが左に向きを変える
  • Dキー:キャラクターが右に向きを変える
  • Wキー:キャラクターが向いている方向へ進む
  • Sキー:キャラクターが向いている方向の逆へ下がる

例えば、キャラクターが画面手前を向いている状態で Wキー を押すと、画面上では手前方向に移動します。
これは、Wキー が「画面の上へ進む」ではなく、「キャラクターの前へ進む」操作だからです。

固定カメラのメリット

固定カメラは、ステージ全体の位置関係を把握しやすいのが特徴です。

プレイヤーがどこにいてもカメラが動かないため、背景や地形の見え方が安定します。
パズルゲーム、見下ろし型アクション、昔の3Dアドベンチャーゲームなどで使いやすい方式です。

一方で、キャラクターの向きと画面方向がずれるため、慣れるまでは操作が難しく感じることがあります。


TPSカメラによるキャラクター移動

TPSカメラとは

TPSカメラは、キャラクターの後ろから追いかけるカメラです。
TPSは Third Person Shooter、または Third Person View のように、三人称視点のゲームでよく使われます。

このサンプルでは、プレイヤーの進行方向 playerDir の逆方向にカメラを配置しています。
つまり、キャラクターの背後にカメラが来るようになっています。

VECTOR back = VScale(playerDir, -TPS_DISTANCE);VECTOR camPos = VAdd(playerPos, back);camPos.y += TPS_HEIGHT;

カメラの高さは TPS_HEIGHT、キャラクターからの距離は TPS_DISTANCE で調整しています。

float TPS_DISTANCE = 8.0f;float TPS_HEIGHT   = 4.0f;

カメラは、プレイヤーの少し上あたりを見るように設定されています。

VECTOR target = VAdd(playerPos, VGet(0.0f, TPS_HEIGHT * 0.5f, 0.0f));SetCameraPositionAndTarget_UpVecY(camPos, target);

TPSカメラサンプル動画


TPSカメラでの操作感

TPSカメラでは、カメラがキャラクターの後ろに回り込むため、キャラクターの前方向が画面奥方向になりやすくなります。

そのため、Wキー を押したときに「画面の奥へ進む」ように見えます。
固定カメラよりも、プレイヤーの進行方向が直感的に分かりやすくなります。

ただし、このサンプルではカメラがマウス操作で自由に回る方式ではありません。
キャラクターの向きに合わせてカメラが後ろへ回り込む形です。

TPSカメラのメリット

TPSカメラは、キャラクターの進行方向と画面の見え方が一致しやすいため、アクションゲームで使いやすいカメラです。

  • キャラクターの前方が見やすい
  • 移動方向を把握しやすい
  • プレイヤーと周囲の位置関係を見やすい
  • 3Dアクションや探索ゲームに向いている

固定カメラと比べると、キャラクターを中心にした操作感になります。


OTSカメラによるキャラクター移動

OTSカメラとは

OTSカメラは、Over The Shoulder の略で、キャラクターの肩越しに見るカメラです。

TPSカメラと似ていますが、カメラがキャラクターの真後ろではなく、少し右側や左側にずれた位置に置かれます。
このサンプルでは、プレイヤーの後ろに下がりつつ、右方向にオフセットした位置へカメラを配置しています。

VECTOR up = VGet(0.0f, 1.0f, 0.0f);VECTOR right = VNorm(VCross(playerDir, up));VECTOR camPos = playerPos;camPos = VAdd(camPos, VScale(playerDir, -OTS_DISTANCE));camPos = VAdd(camPos, VScale(right, OTS_RIGHT_OFFSET));camPos.y += OTS_HEIGHT;

OTSカメラ用の距離、高さ、横方向のずれは、次の値で設定しています。

float OTS_DISTANCE     = 5.0f;float OTS_HEIGHT       = 2.5f;float OTS_RIGHT_OFFSET = 2.0f;

OTSカメラのサンプル


肩越し視点の作り方

OTSカメラでは、まずプレイヤーの進行方向 playerDir から、右方向ベクトル right を作っています。

VECTOR right = VNorm(VCross(playerDir, up));

その後、カメラ位置を次のように調整しています。

  1. プレイヤー位置を基準にする
  2. プレイヤーの後ろに下げる
  3. 右方向にずらす
  4. 高さを加える

これにより、キャラクターの背後やや右側から見る「肩越し視点」になります。

さらに、注視点も少し左側にずらしています。
これにより、キャラクターが画面の中央に完全に重なるのではなく、少し横に寄った構図になります。

VECTOR target = playerPos;target = VAdd(target, VScale(right, -OTS_RIGHT_OFFSET * 0.5f));target.y += OTS_HEIGHT * 0.5f;

OTSカメラでの操作感

OTSカメラでは、キャラクターの近くから肩越しに前方を見るため、プレイヤーの視点に近い感覚になります。

TPSカメラよりもキャラクターに近いため、前方の対象物や敵を狙うようなゲームに向いています。

  • シューティング
  • アクション
  • ホラーゲーム
  • 近距離でキャラクターを見せたいゲーム

などでよく使われます。

一方で、カメラがキャラクターに近いため、周囲全体の状況はTPSカメラより少し見づらくなります。


3種類のカメラの比較

カメラ

カメラの位置

操作感

向いているゲーム

固定カメラ

常に固定位置

ラジコン操作に近い

パズル、見下ろし型、固定画面アクション

TPSカメラ

キャラクターの後ろ

進行方向が分かりやすい

3Dアクション、探索ゲーム

OTSカメラ

キャラクターの肩越し

狙う・見る感覚が強い

TPS、ホラー、アクションシューティング


プログラム全体の流れ

このサンプルでは、プレイヤーの Update() の最後で、プレイヤーの現在位置 pos_ と進行方向 dir_ をカメラに渡しています。

Camera::Update(pos_, dir_);

カメラ側では、その位置と向きを使って、現在のカメラモードに応じたカメラ位置を計算しています。

つまり、処理の流れは次のようになります。

  1. キー入力を確認する
  2. プレイヤーの回転角度を更新する
  3. 回転角度から進行方向 dir_ を作る
  4. W / S キーでプレイヤー位置を移動する
  5. プレイヤーの位置と向きをモデルに反映する
  6. プレイヤーの位置と進行方向をカメラに渡す
  7. カメラモードに応じてカメラ位置を更新する

まとめ

固定カメラ、TPSカメラ、OTSカメラは、同じキャラクター移動を使っていても、プレイヤーに与える操作感が大きく変わります。固定カメラでは、カメラが動かないため、キャラクター自身の向きに合わせて操作するラジコン操作になります。TPSカメラでは、カメラがキャラクターの後ろに回り込むため、進行方向が分かりやすく、3Dアクション向きの操作感になります。OTSカメラでは、キャラクターの肩越しから見ることで、狙う・注視する感覚が強くなり、シューティングやホラーゲームのような演出に向いています。


おまけ

固定カメラ+固定方向(上下左右)移動+なめらかな角度変化

ここまでの固定カメラでは、キャラクターの向きを基準にして前進・後退する「ラジコン操作」を説明しました。

このおまけでは、固定カメラ用の別操作として、画面方向に対して固定された移動を行います。

サンプルプログラムのURLは
https://github.com/youetsux/radioControllman2

ラジコン操作では、

  • A / D でキャラクターの向きを変える
  • W でキャラクターの向いている方向へ進む
  • S でキャラクターの後ろへ下がる

という操作でした。

それに対して、固定方向移動では、

  • W:奥へ移動
  • S:手前へ移動
  • A:左へ移動
  • D:右へ移動

のように、キーと移動方向を固定します。

固定カメラの場合、カメラの位置が変わらないため、画面上の上下左右とワールド座標の前後左右を対応させやすくなります。



固定カメラのときだけ移動処理を切り替える

このサンプルでは、カメラモードが固定カメラのときだけ UpdateFixedCameraMove() を使い、それ以外のカメラでは UpdateFreeCameraMove() を使っています。

if (Camera::GetMode() == CAMERA_FIXED){	UpdateFixedCameraMove(dt);}else{	UpdateFreeCameraMove(dt);}

これにより、固定カメラでは画面方向に合わせた移動、TPSカメラやOTSカメラではキャラクターの向きを基準にした移動、というように操作方法を分けています。


ワールド固定の前後左右ベクトルを用意する

固定方向移動では、まずワールド空間での前方向と右方向を決めています。

const VECTOR forward = VGet(0.0f, 0.0f, 1.0f);const VECTOR right   = VGet(1.0f, 0.0f, 0.0f);

ここでは、

  • forward がZ軸プラス方向
  • right がX軸プラス方向

を表しています。

つまり、キャラクターが今どちらを向いているかに関係なく、W を押したらZ軸プラス方向、D を押したらX軸プラス方向へ移動するようになります。


キー入力から移動方向を作る

次に、押されているキーに応じて移動方向 moveVec を作ります。

VECTOR moveVec = VGet(0.0f, 0.0f, 0.0f);if (Input::IsKeepKeyDown(KEY_INPUT_W)) moveVec = VAdd(moveVec, forward);if (Input::IsKeepKeyDown(KEY_INPUT_S)) moveVec = VAdd(moveVec, VScale(forward, -1.0f));if (Input::IsKeepKeyDown(KEY_INPUT_D)) moveVec = VAdd(moveVec, right);if (Input::IsKeepKeyDown(KEY_INPUT_A)) moveVec = VAdd(moveVec, VScale(right, -1.0f));

この処理では、押されたキーに対応する方向ベクトルを足し合わせています。

例えば、W と D を同時に押した場合は、

forward + right

となるため、右奥方向への移動になります。


斜め移動が速くならないように正規化する

W だけを押した場合、移動方向の長さは 1 です。

しかし、W と D を同時に押した場合、移動方向は斜めになります。
このまま移動すると、斜め移動だけ距離が長くなり、移動速度が速くなってしまいます。

そこで、移動方向の長さを求めています。

float moveLen = sqrtf(moveVec.x * moveVec.x + moveVec.z * moveVec.z);if (moveLen < 0.0001f) return;VECTOR moveDir = VScale(moveVec, 1.0f / moveLen);

moveLen がほぼ0の場合は、何もキーが押されていない状態なので移動処理を終了します。

キー入力がある場合は、moveVec を moveLen で割って、長さ1のベクトルにしています。
これを正規化といいます。

正規化することで、上下左右に移動しても、斜めに移動しても、同じ速度で移動できます。


移動方向へキャラクターを向ける

固定方向移動では、キャラクターの向きで移動方向を決めるのではなく、入力された移動方向へキャラクターを向けます。

そのため、まず移動方向 moveDir から目標のY軸回転角 targetRotY を計算します。

float targetRotY = DirectX::XMConvertToDegrees(atan2f(moveDir.x, moveDir.z));

atan2f() を使うことで、X方向とZ方向の成分から、キャラクターが向くべき角度を求めています。

例えば、

入力

移動方向

キャラクターの向き

W

奥を向く

S

手前

手前を向く

A

左を向く

D

右を向く

W + D

右奥

右奥を向く

という動きになります。

急に向きを変えず、なめらかに回転させる

目標角度をそのまま rotY_ に代入すると、キャラクターの向きが一瞬で切り替わってしまいます。

そこで、このサンプルでは現在の角度 rotY_ と目標角度 targetRotY の差を求め、少しずつ回転するようにしています。

float diff = targetRotY - rotY_;while (diff >  180.0f) diff -= 360.0f;while (diff < -180.0f) diff += 360.0f;float rotStep = rotYSpeed_ * dt;rotY_ += (fabsf(diff) < rotStep) ? diff : (diff > 0.0f ? rotStep : -rotStep);

角度の差が 180度 を超える場合は、逆回りした方が近いことがあります。
そのため、while 文で角度差を -180度 ~ 180度 の範囲に収めています。

その後、1フレームで回転できる量 rotStep を使って、少しずつ目標方向へ回転します。

これにより、キーを押した方向へキャラクターが自然に向き直るようになります。


実際に移動する

最後に、正規化した移動方向 moveDir に速度とデルタタイムを掛けて、プレイヤーの位置に加算します。

pos_ = VAdd(pos_, VScale(moveDir, speed_ * dt));dir_ = moveDir;

pos_ がプレイヤーの位置です。
moveDir の方向へ speed_ * dt 分だけ移動します。

また、dir_ にも moveDir を入れています。
これは、カメラ更新時にプレイヤーの進行方向として使うためです。

プレイヤーの Update() の最後では、現在位置 pos_ と進行方向 dir_ をカメラに渡しています。

Camera::Update(pos_, dir_);

ラジコン操作と固定方向移動の違い

操作方式

移動方向の基準

特徴

ラジコン操作

キャラクターの向き

向きを変えてから前進する

固定方向移動

ワールド座標・画面方向

押したキーの方向へ直接移動する

ラジコン操作は、キャラクターを「操作している」感覚が強い方式です。
一方、固定方向移動は、キャラクターを画面上で直接動かしている感覚に近い方式です。

固定カメラのゲームでは、カメラの向きが変わらないため、固定方向移動の方が直感的に操作しやすい場合があります。

ただし、カメラが回転するゲームで固定方向移動をそのまま使うと、画面方向と移動方向がずれて分かりにくくなることがあります。
そのため、このサンプルでは固定カメラのときだけ固定方向移動を使い、TPSカメラやOTSカメラでは通常のキャラクター向き基準の移動に戻しています。