Quantcast
Channel: C#タグが付けられた新着記事 - Qiita
Viewing all articles
Browse latest Browse all 9747

【Unity】「機動戦士ガンダムVS.」シリーズっぽいカメラの動きを作ってみる

$
0
0

はじめに

本記事ではUnityで「機動戦士ガンダムVS.」シリーズにあるような、操作キャラクターを視界に入れつつ、ターゲットに向き続けるTPSカメラを作ってみます。
近い動きで「ディシディア ファイナルファンタジー」や「Fate/Grand Order Arcade」のようなカメラにも応用できるんじゃないかと思います。

完成イメージ

outcome.gif

デモプロジェクト

GitHubにアップロードしています。
unity-tps-lock-on-camera

位置と向きのロジック

カメラの動きを作るときは先に位置を考え、その後に向きを考えるのが進めやすいと思います。

位置

まずカメラの位置ですが、下図のようにターゲットと操作キャラを結んだ線上、操作キャラ後方の少し上辺りにいる感じがしますね。

camera_image01.png

camera_image02.png

なので、操作キャラ→ターゲットのベクトル$\vec{V_{tgt}}$を基準に、後ろ距離と高さを示すベクトル$\vec{V_{ofsLocal}}$分ずらせば欲しい位置が得られそうです。ただそれは$\vec{V_{tgt}}$を前方とするローカル空間での話なので、ワールド座標に直す必要があります。
あるベクトルを座標系(の一部)とみなしてそれを基準に位置を得る方法ですが、クォータニオンを掛けることでベクトルを回転することができるという性質を利用します。
$\vec{V_{tgt}}$を前方とするクォータニオン$q_{tgt}$を得ることができれば、ワールド座標でずらすべき位置ベクトル$\vec{V_{ofs}}$は、

\vec{V_{ofs}} = q_{tgt} \times \vec{V_{ofsLocal}}

となります。
幸いUnityにはQuaternion.LookRotationという便利なAPIが用意されていますので、$q_{tgt}$を得るのは難しくありません。
カメラの位置を算出するまでのコードイメージはこんな感じになります。

// ターゲットへのベクトルVector3vTgt=target.position-player.position;// ターゲットへのベクトルを前方とするクォータニオン// 第二引数はワールド空間的な上(Vector3.up)でいいので省略QuaternionqTgt=Quaternion.LookRotation(vTgt);// ずらすべき位置ベクトル// ずらしたい量をここでは後方5、高さ2とした場合Vector3vOfs=qTgt*newVector3(0f,2f,-5f);// 最終的なカメラ位置(ワールド座標)Vector3cameraPosition=player.position+vOfs;

向き

次に向きですが、こちらはシンプルです。
常にターゲットに向くだけなので、カメラの位置が決まればQuaternion.LookRotationにカメラ→ターゲットのベクトルを前方ベクトルとして渡してあげれば得られます。

// ターゲットへの向きQuaternioncameraRotation=Quaternion.LookRotation(target.position-cameraPosition);

プロトタイプ完成

変数名を分かりやすくして、MonoBehaviourにのっけたコードに直すとこんな感じです。
ここまでで操作キャラの後方を維持しつつ、ターゲットを見続けるカメラができました。

TpsLockOnCameraPrototype.cs
usingUnityEngine;publicclassTpsLockOnCameraPrototype:MonoBehaviour{/// <summary>/// 取りつくキャラクター/// </summary>[SerializeField]privateTransform_attachTarget=null;/// <summary>/// 取りつくキャラクターからのカメラオフセット位置/// </summary>[SerializeField]privateVector3_attachOffset=newVector3(0f,2f,-5f);/// <summary>/// 注視ターゲット/// </summary>[SerializeField]privateTransform_lookTarget=null;/// <summary>/// 現在の注視点/// </summary>privateVector3_lookTargetPosition=Vector3.zero;privatevoidLateUpdate(){_lookTargetPosition=_lookTarget.position;// ターゲットへのベクトルVector3targetVector=_lookTargetPosition-_attachTarget.position;// ターゲットへのベクトルを前方とするクォータニオンQuaterniontargetRotation=targetVector!=Vector3.zero?Quaternion.LookRotation(targetVector):transform.rotation;// 位置と向きVector3position=_attachTarget.position+targetRotation*_attachOffset;Quaternionrotation=Quaternion.LookRotation(_lookTargetPosition-position);transform.SetPositionAndRotation(position,rotation);}}

prototype.gif

ターゲット切り替えのロジック

続いてターゲット切り替えの動きを考えます。
最終的にはターゲットとして指定しているtransformを変えればいいわけですが、単純に変えるとカメラが位置と向きをがっつりワープすることになるので、プレイヤーは切り替わり前後で混乱してしまいますよね。滑らかに繋ぎたいところです。

ターゲット切り替え

処理の流れを考えます。
まず、元々見ていたターゲットは、切り替わり動作中も含めて無視としたいので、切り替え開始の瞬間の位置$P_{old}$だけ覚えておけばよさそうです。新しく見るターゲットは切り替わり動作中も含めてその位置$P_{new}$を追従し続けたいです。
なので$P_{old}$は固定位置、$P_{new}$はTransformのpositionとして、$P_{old}$から$P_{new}$へ補間しつつ一定時間かけて移動すればよさそうです。
(見る位置を滑らかに変えることで、カメラの位置と向きもスムーズに変化するよねという考え方です。)

camera_image03.png

位置の補間にはVector3.Lerpのメソッドが使えます。(好みでイージングしてもいいかもしれません。)
よって、先程のコードにターゲット変更の処理を組み込んだ最終版はこのようになります。

TpsLockOnCamera.cs
usingUnityEngine;publicclassTpsLockOnCamera:MonoBehaviour{/// <summary>/// 取りつくキャラクター/// </summary>[SerializeField]privateTransform_attachTarget=null;/// <summary>/// 取りつくキャラクターからのカメラオフセット位置/// </summary>[SerializeField]privateVector3_attachOffset=newVector3(0f,2f,-5f);/// <summary>/// 注視ターゲット/// </summary>[SerializeField]privateTransform_lookTarget=null;/// <summary>/// ターゲットがいないときの注視点/// </summary>[SerializeField]privateVector3_defaultLookPosition=Vector3.zero;/// <summary>/// ロック切り替え時間/// </summary>[SerializeField]privatefloat_changeDuration=0.1f;/// <summary>/// ロック切り替えタイマー/// </summary>privatefloat_timer=0f;/// <summary>/// 現在の注視点/// </summary>privateVector3_lookTargetPosition=Vector3.zero;/// <summary>/// ロックを移すときの最後の注視点/// </summary>privateVector3_latestTargetPosition=Vector3.zero;/// <summary>/// ターゲット切り替え/// </summary>/// <param name="target"></param>publicvoidChangeTarget(Transformtarget){_latestTargetPosition=_lookTargetPosition;_lookTarget=target;_timer=0f;}privatevoidLateUpdate(){vartargetPosition=_lookTarget!=null?_lookTarget.position:_defaultLookPosition;// 現在の注視点を更新if(_timer<_changeDuration){_timer+=Time.deltaTime;_lookTargetPosition=Vector3.Lerp(_latestTargetPosition,targetPosition,_timer/_changeDuration);}else{_lookTargetPosition=targetPosition;}// ターゲットへのベクトルVector3targetVector=_lookTargetPosition-_attachTarget.position;// ターゲットへのベクトルを前方とするクォータニオンQuaterniontargetRotation=targetVector!=Vector3.zero?Quaternion.LookRotation(targetVector):transform.rotation;// 位置と向きVector3position=_attachTarget.position+targetRotation*_attachOffset;Quaternionrotation=Quaternion.LookRotation(_lookTargetPosition-position);transform.SetPositionAndRotation(position,rotation);}}

おわりに

このタイプのカメラ挙動はカメラ操作がシンプルになるというメリットがありますね。
拙い説明であることに加え、細かくは再現しきれておらず申し訳ないですが、少しでも何かの参考になれば幸いです。
また、説明の間違いやソースコードの不具合などあるかもしれません。その場合は是非ご指摘いただけますと幸いです。


Viewing all articles
Browse latest Browse all 9747

Trending Articles