ページ 11

3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月24日(月) 18:22
by Knorun
はじめまして

3Dシューティングゲームでの自機のピッチアップ/ダウンとローリングの回転がうまく行かない点(片方のみの回転ならうまく行く)について悩んでいます。(具体的な内容は下で記述します)

開発言語はC++でDirectX11を使用しています。

理想としてはエースコンバットのような自機を自由にローリング、ピッチアップ/ダウン、ヨーイングさせて戦闘機を自由に飛ばしたいです。

現時点では自機は移動せず、原点でローリング、ピッチアップ/ダウンするのみでヨーイングは実装していません。

自機とカメラの回転にはクオータニオンを使用しており、
ローリングに使うクオータニオンの軸とする前方向ベクトル、
ピッチアップ/ダウンに使うクオータニオンの軸とする横方向ベクトル、
ヨーイングに使うクオータニオンの軸とする上方向ベクトル
を使用しています。


機体の回転について

機体の回転は翼の回転している角度を機体の回転角に足して、機体の回転角に応じて機体を回転させるようにしています。

機体のそれぞれの回転角が360度・-360度を越えた場合は0度に戻しています。

ピッチアップ/ダウンのみ、もしくはローリングのみならばちゃんとそれぞれのベクトルを軸として回転してくれますが、
両方の回転を行うと機体がおかしな回転をずっとするようになり、特にピッチアップ/ダウンかローリングの角度が約90度以上約270度以下・約-90度以下約-270度以上の状態で回転を行っていないほうの回転を少しでも行うとめちゃくちゃな回転をするようになり、ずっと回り続けてしまいます(キーの入力によって落ち着かせることは可能です)。

カメラはエースコンバットの三人称視点のように機体の後方から機体を映す様にしています。

機体の回転がうまく行かないのに対してカメラの座標回転、上方向ベクトルの回転はなぜかうまく行き、機体がどう回転しようが機体の後ろにいて機体を映してます。


以下はソースです
ローカル変数

コード:

//ワールド行列
Matrix world;

//機体の回転行列
Matrix plane_roll;

//ビュー行列
Matrix view;
機体の回転を行う場所

コード:

//横方向ベクトルを軸に回転するクオータニオンを作る
S_SceneData->S_PitchUpDown_Q = Quaternion::CreateFromAxisAngle(S_SceneData->S_PlaneSideVec, XMConvertToRadians(-S_SceneData->S_PitchUpDownNum));

//前方向ベクトルを軸に回転するクオータニオンを作る
S_SceneData->S_Roll_Q = Quaternion::CreateFromAxisAngle(S_SceneData->S_PlaneFrontVec, XMConvertToRadians(S_SceneData->S_RollNum));

//上方向ベクトルを軸に回転するクオータニオンを作る
S_SceneData->S_Yaw_Q = Quaternion::CreateFromAxisAngle(S_SceneData->S_PlaneUPVec, XMConvertToRadians(S_SceneData->S_YawNum));

//前方向ベクトルにピッチアップ/ダウンとヨーイングの回転を反映させる
S_SceneData->S_PlaneFrontVec = Vector3::Transform(Vector3(0.0f, 0.0f, -1.0f), Matrix::CreateFromQuaternion(S_SceneData->S_PitchUpDown_Q) * Matrix::CreateFromQuaternion(S_SceneData->S_Yaw_Q));

//上方向ベクトルにピッチアップ/ダウンとローリングの回転を反映させる
S_SceneData->S_PlaneUPVec = Vector3::Transform(Vector3(0.0f, 1.0f, 0.0f), Matrix::CreateFromQuaternion(S_SceneData->S_PitchUpDown_Q) * Matrix::CreateFromQuaternion(S_SceneData->S_Roll_Q));

//外積によりピッチアップ/ダウンとロール後の横方向ベクトルを求める
S_SceneData->S_PlaneSideVec = -S_SceneData->S_PlaneUPVec.Cross(S_SceneData->S_PlaneFrontVec);

//機体の行列に回転データを反映させる
plane_roll = Matrix::CreateFromQuaternion(S_SceneData->S_PitchUpDown_Q) * Matrix::CreateFromQuaternion(S_SceneData->S_Roll_Q);
Matrix PlaneMat = plane_roll * PlaneTrans;

//機体のカメラ座標(機体の移動と回転有り)に ( 機体のカメラ座標(機体の移動と回転なし)*機体の移動と回転 ) を代入する
S_SceneData->S_PlaneChmera = Vector3::Transform(S_SceneData->S_PlaneCameraTmp, (Matrix::CreateFromQuaternion(S_SceneData->S_PitchUpDown_Q) * Matrix::CreateFromQuaternion(S_SceneData->S_Roll_Q)));

//機体のカメラ座標(機体の移動と回転有り)に ( 機体のカメラ座標(機体の移動と回転なし)*機体の移動と回転 ) を代入する
S_SceneData->S_PlaneChmera = Vector3::Transform(S_SceneData->S_PlaneChmera, PlaneTrans);

//機体の回転状況に応じてカメラの上方向ベクトルを回転させる
S_SceneData->S_CameraUpVec = Vector3::Transform(Vector3(0.0f, 1.0f, 0.0f), (Matrix::CreateFromQuaternion(S_SceneData->S_PitchUpDown_Q) * Matrix::CreateFromQuaternion(S_SceneData->S_Roll_Q)));

//ビュー行列の生成					   //カメラの座標			   //機体の座標				//カメラの上方向ベクトル
view = Matrix::CreateLookAt(S_SceneData->S_PlaneChmera, S_SceneData->S_PlanePos, S_SceneData->S_CameraUpVec);
回転させる値を変更する所

コード:

//下キーを押し続けている場合の処理
if (S_SceneData->S_Key->Press(VK_DOWN))
{
	//エレベーターの回転制限角まで回転するようにする
	if (S_SceneData->S_RightTaleWingRot_X > -20.0f)
	{
		S_SceneData->S_RightTaleWingRot_X -= 0.25f;
	}
	if (S_SceneData->S_LeftTaleWingRot_X > -20.0f)
	{
		S_SceneData->S_LeftTaleWingRot_X -= 0.25f;
	}
}
//上キーを押し続けている場合の処理
else if (S_SceneData->S_Key->Press(VK_UP))
{
	//エレベーターの回転制限角まで回転するようにする
	if (S_SceneData->S_RightTaleWingRot_X < 20.0f)
	{
		S_SceneData->S_RightTaleWingRot_X += 0.25f;
	}
	if (S_SceneData->S_LeftTaleWingRot_X < 20.0f)
	{
		S_SceneData->S_LeftTaleWingRot_X += 0.25f;
	}
}
//何も押していない場合の処理
else
{
	//エレベーターの回転角が0になるまでエレベーターの回転角を増やす/減らす
	if (S_SceneData->S_RightTaleWingRot_X > 0.0f)
	{
		S_SceneData->S_RightTaleWingRot_X -= 0.25f;
	}
	if (S_SceneData->S_RightTaleWingRot_X < 0.0f)
	{
		S_SceneData->S_RightTaleWingRot_X += 0.25f;
	}
	if (S_SceneData->S_LeftTaleWingRot_X > 0.0f)
	{
		S_SceneData->S_LeftTaleWingRot_X -= 0.25f;
	}
	if (S_SceneData->S_LeftTaleWingRot_X < 0.0f)
	{
		S_SceneData->S_LeftTaleWingRot_X += 0.25f;
	}
}

//右ぎーを押し続けている場合の処理
if (S_SceneData->S_Key->Press(VK_RIGHT))
{
	//エルロンの回転角の制限値までエルロンが回転できるようにする
	if (S_SceneData->S_RightAileronRot_X < 20.0f)
	{
		S_SceneData->S_RightAileronRot_X += 0.25f;
	}
}
//左ぎーを押し続けている場合の処理
else if (S_SceneData->S_Key->Press(VK_LEFT))
{
	//エルロンの回転角の制限値までエルロンが回転できるようにする
	if (S_SceneData->S_RightAileronRot_X > -20.0f)
	{
		S_SceneData->S_RightAileronRot_X -= 0.25f;
	}
}
//何も押していない場合の処理
else
{
	//エルロンの回転角が0になるまでエルロンの回転角を増やす/減らす
	if (S_SceneData->S_RightAileronRot_X < 0.0f)
	{
		S_SceneData->S_RightAileronRot_X += 0.25f;
	}
	if (S_SceneData->S_RightAileronRot_X > 0.0f)
	{
		S_SceneData->S_RightAileronRot_X -= 0.25f;
	}
}

//エレベーターの回転角をピッチアップ/ダウンする数値に反映させる
S_SceneData->S_PitchUpDownNum += S_SceneData->S_RightTaleWingRot_X;

//エルロンの回転角をロールする数値に反映させる
S_SceneData->S_RollNum += S_SceneData->S_RightAileronRot_X;

//360度以上回転したら0度に戻す
if (XMConvertToRadians(S_SceneData->S_PitchUpDownNum) > 360.0f || XMConvertToRadians(S_SceneData->S_PitchUpDownNum) < -360.0f)
{
	S_SceneData->S_PitchUpDownNum = 0.0f;
}

//360度以上回転したら0度に戻す
if (XMConvertToRadians(S_SceneData->S_RollNum) > 360.0f || XMConvertToRadians(S_SceneData->S_RollNum) < -360.0f)
{
	S_SceneData->S_RollNum = 0.0f;
}
描画する所

コード:

//機体の胴体の描画
Plane_Draw(S_ChoosingNum, 0, PlaneMat, view);

//右水平尾翼の描画
//パーツの回転
Matrix RotX = Matrix::CreateRotationX(XMConvertToRadians(S_SceneData->S_RightTaleWingRot_X));
Matrix RotY = Matrix::CreateRotationY(XMConvertToRadians(S_SceneData->S_RightTaleWingRot_Y));
Matrix RotZ = Matrix::CreateRotationZ(S_SceneData->S_RightTaleWingRot_Z);

//パーツの機体に合わせた回転
Matrix RotShapeX = Matrix::CreateRotationX(S_SceneData->S_RightTaleWingRotShape_X);
Matrix RotShapeY = Matrix::CreateRotationY(S_SceneData->S_RightTaleWingRotShape_Y);
Matrix RotShapeZ = Matrix::CreateRotationZ(DirectX::XMConvertToRadians(180.0f));

//パーツの機体の合わせた移動
Matrix Trans = Matrix::CreateTranslation(266.72f, 24.70f, 616.97f);

world = RotX * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 1, world, view);

//左水平尾翼の描画
RotX = Matrix::CreateRotationX(XMConvertToRadians(S_SceneData->S_LeftTaleWingRot_X));

Trans = Matrix::CreateTranslation(-266.72f, 24.70f, 616.97f);

world = RotX * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 2, world, view);

//右フラップの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(-0.84f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(7.31f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(0.11f));

Trans = Matrix::CreateTranslation(370.8f, 29.75f, 344.6f);

world = RotShapeX * RotShapeY * RotShapeZ * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 7, world, view);



//左フラップの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.11f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(-7.31f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.84f));

Trans = Matrix::CreateTranslation(-370.8f, 29.75f, 344.6f);

world = RotShapeX * RotShapeY * RotShapeZ * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 8, world, view);

//右エルロンの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(-0.13f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(7.31f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(0.99f));

Trans = Matrix::CreateTranslation(577.8f, 25.87f, 307.5f);

world = RotShapeX * RotShapeY * RotShapeZ * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 9, world, view);


//左エルロンの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.13f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(-7.31f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.99f));

Trans = Matrix::CreateTranslation(-577.8f, 25.87f, 307.5f);

world = RotShapeX * RotShapeY * RotShapeZ * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 10, world, view);

//右前フラップの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.84f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(45.22f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.11f));

Trans = Matrix::CreateTranslation(-493.2f, 17.24f, 70.34f);

world = RotShapeX * RotShapeY * RotShapeZ * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 19, world, view);



//左前フラップの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.11f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(-45.22f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.84f));

Trans = Matrix::CreateTranslation(493.2f, 17.24f, 70.34f);

world = RotShapeY * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 20, world, view);

//右カナードの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.84f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(-45.22f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.11f));

Trans = Matrix::CreateTranslation(180.8f, 20.35f, -405.9f);

world = RotShapeY * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 5, world, view);



//左カナードの表示
RotShapeX = Matrix::CreateRotationX(XMConvertToRadians(0.11f));

RotShapeY = Matrix::CreateRotationY(XMConvertToRadians(45.22f));

RotShapeZ = Matrix::CreateRotationZ(XMConvertToRadians(-0.84f));

Trans = Matrix::CreateTranslation(-180.8f, 20.35f, -405.9f);

world = RotShapeY * Trans * PlaneMat;

Plane_Draw(S_ChoosingNum, 6, world, view);
汚い文章ですがうまく行くやり方が分かる方がいらっしゃいましたら教えてください。

よろしくお願いします。

Re: 3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月24日(月) 18:34
by Knorun
書き忘れていましたがVisualStudio2013を使用しています。

Re: 3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月24日(月) 23:23
by lriki
中天を向くほどおかしな動きをする感じでしょうか?
XYZ各軸の回転を分けているあたり、すごくジンバルロックしそうに見えます。

「回転させる値を変更する所」では各軸の角度を保持するのではなく、
直接クォータニオンを回転させるべきかなと思います。

また、クォータニオンが正しく使えていれば「360度以上回転したら0度に戻す」処理は不要なはずです。

↓DXライブラリですが、前に私が書いた記事です。ご参考までに。
http://lriki.hatenablog.com/entry/2015/01/31/210508

Re: 3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月25日(火) 01:33
by Knorun
返信と参考になる記事の掲示ありがとうございます。

中天を向くほどおかしな動きをする感じでしょうか?
>先ほど記載したソースでは少しでもロールした状態で真後ろを向こうするほどおかしな回転をします。

lriki様のソースを参考にさせて頂き、
機体の回転を行う場所のソースを書き直し、
回転させる値を変更する所に統合し、
回転させる値の変更はクオータニオンで行うように修正、
360度以上・・・の部分を消し、
カメラ座標の変更で少し怪しいところがあったので書き直しました。

以下のようにソースを書き直しましたが最初からピッチアップ/ダウンを行うことができなくなり、
ローリングしたあとピッチアップを行うと機体の回転がおかしくなってしまいます。

相変わらずカメラはまともに動いています。

コード:

 
//ピッチアップ/ダウン
//機体のピッチアップに使用する軸を決める
Vector3 Axis_P = Vector3::Transform(Vector3(1.0f, 0.0f, 0.0f), S_SceneData->S_Rot_Q);

S_SceneData->S_Rot_Q = Quaternion::CreateFromAxisAngle(Axis_P, XMConvertToRadians(S_SceneData->S_PitchUpDownNum));

//ロール
//機体のロールに使用する軸を決める
Vector3 Axis_R = Vector3::Transform(Vector3(0.0f, 0.0f, -1.0f), S_SceneData->S_Rot_Q);

S_SceneData->S_Rot_Q = Quaternion::CreateFromAxisAngle(Axis_R, XMConvertToRadians(S_SceneData->S_RollNum));

//機体の行列に回転データを反映させる
plane_roll = Matrix::CreateFromQuaternion(S_SceneData->S_Rot_Q);
Matrix PlaneMat = plane_roll * PlaneTrans;

//機体のカメラ座標(機体の移動と回転有り)に ( 機体のカメラ座標(機体の移動と回転なし)*機体の移動と回転 ) を代入する
S_SceneData->S_PlaneChmera = Vector3::Transform(S_SceneData->S_PlaneCameraTmp, Matrix::CreateFromQuaternion(S_SceneData->S_Rot_Q) * PlaneTrans);

//ビュー行列の作成
//機体の回転状況に応じてカメラの上方向ベクトルを回転させる
S_SceneData->S_CameraUpVec = Vector3::Transform(Vector3(0.0f, 1.0f, 0.0f), Matrix::CreateFromQuaternion(S_SceneData->S_Rot_Q));

//ビュー行列の生成					   //カメラの座標			   //機体の座標				//カメラの上方向ベクトル
view = Matrix::CreateLookAt(S_SceneData->S_PlaneChmera, S_SceneData->S_PlanePos, S_SceneData->S_CameraUpVec);

//エレベーターの回転角に応じてピッチアップ/ダウンする数値に反映させる
S_SceneData->S_PitchUpDownNum += S_SceneData->S_RightTaleWingRot_X;

////エルロンの回転角に応じてロールする数値に反映させる
S_SceneData->S_RollNum += S_SceneData->S_RightAileronRot_X;

//エレベーターの回転角に応じてピッチアップ/ダウンする
S_SceneData->S_Rot_Q = Quaternion::CreateFromAxisAngle(Axis_P, XMConvertToRadians(S_SceneData->S_PitchUpDownNum));

//エルロンの回転角に応じてロールする
S_SceneData->S_Rot_Q = Quaternion::CreateFromAxisAngle(Axis_R, XMConvertToRadians(S_SceneData->S_RollNum));
せっかく参考になる記事を掲載していただいたのに生かしきれず申し訳ございません。

Re: 3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月25日(火) 10:39
by Knorun
考え直してみたところ

下の二行は不要でしたので消去しましたが症状は変わっていません。

Re: 3Dシューティングゲームの機体の回転がうまく行きません

Posted: 2015年8月26日(水) 14:42
by Knorun
何とか解決することができました。
返信してくださったlriki様とトピックを閲覧していただいた方ありがとうございました。