FPSカメラの作り方

投稿者: | 2012年7月31日

今回はFPSカメラの作り方を紹介します。

FPSカメラを作るコツは、

  • 「3次元座標系」と「回転座標系」との相互変換

これができれば簡単に作れます。図にするとこんな感じです。

3次元座標系とは、おなじみXYZの座標系です。正確には今回はカメラの操作だけなので、視点座標と注視点座標の操作となります。

回転座標系とは、水平方向の回転と垂直方向の回転からなる座標系です(ただ、この用語は正確ではないかもしれません…)

今回カメラを左右に回したり上下を見れるようにしたいので、水平・垂直方向を基準に回転ができないとそれが実現できないわけです。

さらに流れをまとめると以下のようになります。

  1. 3次元座標系から回転座標系へ変換する
  2. プレイヤーの入力を受け取る
  3. 受け取った値を元に、3次元座標系へ逆変換する

この3つの手順ができれば、FPSゲームのようにカメラをグルグル回せます。

■1.3次元座標系から回転座標系へ変換する

まずは、3次元座標を回転座標に変換します。正確には「視点」と「中心点」から「水平方向の回転」と「垂直方向の回転」を計算します。

▼水平方向の回転角度

水平方向の回転角度を出すのは簡単です。視点から注視点への方向ベクトルを求め、Atan2を使うことで求めることができます。

/ HAngleの取得関数
float FpsCamera::_GetHAngle(Vec3D* vEye, Vec3D* vTgt)
{
	// 注視点への向きベクトルを求める
	Vec3D vDir;
	Vec3D_Sub( &vDir, vTgt, vEye );
	
	// HAngle(XZ平面での角度)を求める
	float deg = Atan2Deg( -vDir.z, vDir.x );
	
	float __ADJ = 90.0f; // 調整角度(Z方向を基準に回転する)
	deg += __ADJ;
	
	// -180~180に丸める
	if(deg > 180.0f)  { deg -= 360.0f; }
	if(deg < -180.0f) { deg += 360.0f; }
	
	return deg;
}

注意としては、(x,z)=(0,1)を正面としているので、90度の角度調整が入っていることです。なぜ(x,z)=(0,1)なのかというと、逆変換の際に計算がラクできるからです。よく分からなければ、90度足すものぐらいの理解でもいいと思います。ちなみに「HAngle」というのは水平方向(Horizon)の角度という意味です。

▼垂直方向の回転角度

続いて垂直方向の回転角度の計算方法です。

こちらはちょっと考えないとハマります。

// VAngleの取得関数
float FpsCamera::_GetVAngle(Vec3D* vEye, Vec3D* vTgt)
{
	// 注視点への向きベクトルを求める
	Vec3D vDir;
	Vec3D_Sub( &vDir, vTgt, vEye );
	
	float fFront;
	{	// カメラの前方方向値
		Vec3D _vFront;
		Vec3D_Copy( &_vFront, &vDir );
		_vFront.y = 0; // XZ平面での距離なのでYはいらない
		fFront = Vec3D_Length( &_vFront );
	}
	
	// Y軸とXZ平面の前方方向との角度を求める
	float deg = Atan2Deg( -vDir.y, fFront );
	
	// 可動範囲は-90~90
	if(deg > 90.0f)  { deg = 180.0f  - deg; }
	if(deg < -90.0f) { deg = -180.0f - deg; }
	
	return deg;

}

ポイントは2つです。

  1. 可動範囲。垂直の回転は-90~90の範囲を超えることがない
  2. fFrontを使うのはXZ平面における距離のため

1についてですが、上下にカメラを回す場合、-90~90を超えないようにしています。90を超えて回してもいいのですが、ユーザーの目には一瞬おかしなことになるので、このような可動範囲を設定しておくといいと思います。

2についてですが、横軸に当たるのはXZ平面なので、XZ方向への距離を使用しています。XでもなくZでもなく、XZというのがポイントですね。

■2.プレイヤーの入力の反映

先ほどの_GetHAngle()/_GetVAngle()で取得した値に、回転量を足しこむだけです。

■3.回転座標から3次元座標への変換

最後に入力を反映した値を逆変換します。以下、いままでの処理をまとめたコードとなります。(回転のついでにカメラの平行移動もしています)

// カメラを移動・回転させる
void FpsCamera::Move()
{
	// ★1.視点・注視点→回転座標への変換-----------
	m_HAngle = _GetHAngle(GetEye(), GetTgt());
	m_VAngle = _GetVAngle(GetEye(), GetTgt());
	
	// ★2.入力を元に回転する-----------------------
	// ■視点移動(回転)
	Vec2D stick; // ※予め移動値が入っているとします
	m_HAngle -= stick.x; // 水平方向への回転
	m_VAngle += stick.y; // 垂直方向への回転
	
	// ■平行移動
	float mx, mz; // XZ方向 ※予め移動値が入っているとします
	
	// ・3次元ベクトルにセット
	Vec3D vTranslate;
	Vec3D_Set( &vTranslate, mx, 0, mz );
	
	// ★3.回転座標→3次元座標への変換-------------
	// ■視点座標を取得する
	Vec3D vEye;
	GetEye( &vEye );
	// ■注視点は初期化しておく(後で代入します)
	Vec3D vTgt;
	Vec3D_Zero( &vTgt );
	
	// ■HAngle/VAngleを行列に変換する
	Matrix34 mRot;
	Matrix34_Identity(&mRot);
	Matrix34_RotXYZDeg( &mRot, m_VAngle, m_HAngle, 0 ); // 回転行列生成(※1)
	
	// ・平行移動
	Vec3D_Transform( &vTranslate, &mRot, &vTranslate );
	vTranslate.y = 0.0f; // XZ平面での移動なので、Y移動値は無視する
	Vec3D_Add( &vEye, &vEye, &vTranslate ); // 視点を動かす
	
	// ・回転
	Vec3D vDir;
	Vec3D_Set( &vDir, 0, 0, 1.0f ); // Z方向を基準に回転
	Vec3D_Transform( &vDir, &mRot, &vDir ); // 方向ベクトルの回転(※2)
	Vec3D_Add( &vTgt, &vEye, &vDir );
	
	// 視点・注視点を反映
	SetEye( vEye );
	SetTgt( vTgt );
}

ポイントは※1の回転行列の生成、※2の方向ベクトルの回転ですね。

まず回転行列の生成については、水平方向の回転を「Y軸の回転」、垂直方向の回転を「X軸の回転」と見立てて、回転行列を作っています。そして、視点から注視点への方向ベクトルに対して、回転行列で回転させています。

  • 垂直方向の回転を「X軸の回転」、水平方向の回転を「Y軸の回転」
  • 注視点への方向ベクトルを(x,y,z)=(0,0,1)

というところで_GetHAngle()で+90度していた(Z軸を正面にしている)のが生きてくるわけです。

■最後に

このコードでは考え方を提示しているだけなので、このままコピーしても動きません。(そもそも環境によってベクトルや行列の関数は異なりますし…)

そこでこれらを参考にFPSカメラを作る場合のデバッグ方法ですが、

  • まずは水平方向(HAngle)が正しく動くかをデバッグする
  • 続けて垂直方向(VAngle)が正しく動くかをチェックする

という手順で少しずつチェックすることをオススメします。というのも、水平方向・垂直方向を最初から一緒に動かそうとすると失敗することが多いです(経験談)

以上、3Dゲームの作成のお役に立てれば幸いです。


この記事は、ゲームの修羅場5に書かせていただいた「FPSカメラの作り方」と同一のものとなります。