今回はカメラの挙動を作ってみました。
移動制御するオブジェクトを常に追いかけて映し続けるカメラが欲しいのです。
さーて、作りましょうか。
この記事にはUnity2017.1.2f1を使用しています。
- StandardAssetsのカメラじゃダメなの?
- また操作するオブジェクトを作るの?
- StandardAssetsのカメラを参考にしないの?
- カメラの角度は自分で設定するの?
- カメラは常に追いかけてくるの?
- カメラは自分で操舵できないの?
- カメラの方向に前進できないの?
- できたの?
StandardAssetsのカメラじゃダメなの?
スクリプトを作る前に、そもそもStandardAssetsの中にもCameraの処理はあります。
それを利用するのはダメなのでしょうか。
以前、StandardAssetsのCameraにどんなものがあるかは調べました。
んー……、ちょっと違うんだよなー。
映すターゲットと一定の距離を取り続けつつ追いかけるカメラを作りたいのです。
MultipurposeCameraRigが似ているのですが、これはカメラ自身を回転させて焦点を変更し、わざわざオブジェクトの背後に回り込もうとする機能があります。
それは要らないのです。
やっぱり自分で作りましょうか。
また操作するオブジェクトを作るの?
カメラの挙動を作る前に、移動操作ができるオブジェクトを作り必要があります。
以前作ったスクリプトを参考に、パパっと作ってしまいましょう。
www.urablog.xyz
ObjectController.cs
using UnityEngine; [RequireComponent( typeof( Rigidbody ) )] public class ObjectController : MonoBehaviour { [SerializeField] private float m_moveSpeed = 0.0f; [SerializeField] private float m_turnSpeed = 0.0f; [SerializeField] private float m_jumpForce = 0.0f; private Rigidbody m_rigidbody = null; private void Awake() { m_rigidbody = GetComponent<Rigidbody>(); } private void Update() { ControlObject(); } private void ControlObject() { Vector3 moveDir = Vector3.zero; Vector3 forwardDir = Vector3.forward; Vector3 rightDir = Vector3.right; if( Input.GetKey( KeyCode.UpArrow ) ) { moveDir += forwardDir; } if( Input.GetKey( KeyCode.DownArrow ) ) { moveDir -= forwardDir; } if( Input.GetKey( KeyCode.RightArrow ) ) { moveDir += rightDir; } if( Input.GetKey( KeyCode.LeftArrow ) ) { moveDir -= rightDir; } if( moveDir.sqrMagnitude > Mathf.Epsilon ) { moveDir = moveDir.normalized; Turn( moveDir ); Move( moveDir ); } if( Input.GetKeyDown( KeyCode.Space ) ) { Jump(); } } private void Move( Vector3 i_forward ) { Vector3 delta = i_forward * m_moveSpeed * Time.deltaTime; Vector3 targetPos = transform.position + delta; m_rigidbody.MovePosition( targetPos ); } private void Turn( Vector3 i_forward ) { Quaternion toRot = Quaternion.LookRotation( i_forward ); Quaternion fromRot = transform.rotation; float delta = m_turnSpeed * Time.deltaTime; Quaternion targetRot = Quaternion.RotateTowards( fromRot, toRot, delta ); m_rigidbody.MoveRotation( targetRot ); } private void Jump() { // えっ、このままじゃ空中でもジャンプできちゃうって!? // 仕様だよ! m_rigidbody.velocity = Vector3.zero; Vector3 jumpVec = Vector3.up * m_jumpForce; m_rigidbody.AddForce( jumpVec, ForceMode.VelocityChange ); } } // class ObjectController
移動、ジャンプとアクションゲームに必要な最低限の機能ができましたー。
StandardAssetsのカメラを参考にしないの?
さて、カメラの挙動を作っていきましょう。
オブジェクトの構成はStandardAssetsのCameraにあるMultipurposeCameraRigを参考にしてみましょう。
Rig、Pivot、Cameraの三段構成にしましょう。
Rigはターゲットとなるオブジェクトとの位置の調整に使用して、
Pivotはターゲットとカメラの距離の調整用に使用します。
Cameraにカメラを設定し、x軸回転を用いてオブジェクトに焦点を合わせます。
以前、MultipurposeCameraRigを使って、この構成だとカメラの制御がしやすかったので、自分で作る際も参考にさせてもらいましょう。
ではスクリプトを作りましょう。
CameraController.cs
using UnityEngine; public class CameraController : MonoBehaviour { [SerializeField] private Transform m_target = null; [SerializeField] private float m_speed = 0.0f; public Transform Target { get { return m_target; } } private Transform m_cameraTransform = null; private Transform m_pivot = null; private void Awake() { Camera camera = GetComponentInChildren<Camera>(); Debug.AssertFormat( camera != null, "カメラが無ぇよ!" ); if( camera == null ) { return; } m_cameraTransform = camera.transform; m_pivot = m_cameraTransform.parent; } private void LateUpdate() { UpdateCamera(); } private void UpdateCamera() { if( Target == null ) { return; } Vector3 targetPos = Target.position; float deltaSpeed = m_speed * Time.deltaTime; transform.position = Vector3.MoveTowards( transform.position, targetPos, deltaSpeed ); } } // class CameraController
ルートのオブジェクトであるRigには、ターゲットの位置に移動させます。
子オブジェクトであるPivotにターゲットから離したい分の距離を設定すれば、いい感じに映ります。
カメラの移動速度よりターゲットの方が速い場合は上記のようにカメラが少しずつ離れていく演出が出来ます。
カメラの方が早ければ、常に画面中央にターゲットを捉えられます。
カメラの角度は自分で設定するの?
今回はMultipurposeCameraRigのようにカメラを回転して焦点を合わせる処理は行いません。
初期状態カメラの角度を維持しつつ、位置の移動によって焦点を合わせます。
ということは、エディタ上で現在設定しているターゲットに焦点が合うような回転角度を指定しておく必要がありますよね。
ただ、Pivotの位置を意識しつつ、カメラの角度を調整するのは面倒くさいです。
エディタ上において自動でカメラの角度を設定できる処理を作成しましょうか。
CameraController.cs
[ContextMenu( "ApplyTarget" )] private void ApplyForceTarget() { if( Target == null ) { return; } transform.position = Target.position; SetCameraTransform(); if( m_cameraTransform == null ) { return; } m_cameraTransform.transform.LookAt( Target ); } private void SetCameraTransform() { Camera camera = GetComponentInChildren<Camera>(); Debug.AssertFormat( camera != null, "カメラが無ぇよ!" ); if( camera == null ) { return; } m_cameraTransform = camera.transform; m_pivot = m_cameraTransform.parent; }
簡単ではありますが、メニューからApplyTargetを呼び出すことで、カメラの角度が指定できるようになりました。
カメラは常に追いかけてくるの?
現在、カメラは常にターゲットを追いかけてきます。
ちょっとターゲットが動いただけで、カメラが動き始めてしまいます。
もう少しカメラに心の余裕を持たせるようにしましょう。
ターゲットが焦点位置から一定以上離れたらカメラが動くようにしましょうか。
CameraController.cs
[SerializeField] private float m_waitRange = 0.0f; private void UpdateCamera() { if( Target == null ) { return; } Vector3 toTargetVec = Target.position - transform.position; float sqrLength = toTargetVec.sqrMagnitude; // 設定した範囲内なら更新しない。 // magnitudeはルート計算が重いので、二乗された値を利用しよう。 if( sqrLength <= m_waitRange * m_waitRange ) { return; } // ターゲットの位置から指定範囲内ギリギリの位置を目指すようにするよ。 Vector3 targetPos = Target.position - toTargetVec.normalized * m_waitRange; float deltaSpeed = m_speed * Time.deltaTime; transform.position = Vector3.MoveTowards( transform.position, targetPos, deltaSpeed ); }
少し動いただけでは、カメラが動かないようになりました。
カメラは自分で操舵できないの?
せっかくなのでカメラを自分で操作して、周りを見渡す機能が欲しいものです。
今回のカメラのオブジェクトは分割して処理しているので、RigのオブジェクトをY軸回転させれば何とかなりそうです。
CameraController.cs
public void Turn( float i_angle ) { transform.rotation *= Quaternion.AngleAxis( i_angle, Vector3.up ); }
ObjectController.cs
[Header("Camera")] [SerializeField] private CameraController m_camera = null; [SerializeField] private float m_camraTurnSpeed = 0.0f; private void Update() { ControlCamera(); ControlObject(); } private void ControlCamera() { if( Input.GetKey( KeyCode.A ) ) { m_camera.Turn( m_camraTurnSpeed * Time.deltaTime ); } if( Input.GetKey( KeyCode.D ) ) { m_camera.Turn( -m_camraTurnSpeed * Time.deltaTime ); } }
あら簡単。あっさり出来ました。
カメラの方向に前進できないの?
カメラが回転するようになったのですが、おかげでオブジェクト操作が思ったようにいかなくなりました。
前進操作を行っているのに、全然前に進んでいない。
現在のオブジェクトの操作では前進というのはワールド座標におけるZ方向に進んでいるだけですからねー。
カメラは関係ないのです。
それでは困るので、カメラの向いている方向を基準に移動できるように修正しましょう。
ObjectController.cs
private void ControlObject() { Vector3 moveDir = Vector3.zero; Vector3 forwardDir = m_camera.transform.forward; Vector3 rightDir = m_camera.transform.right; if( Input.GetKey( KeyCode.UpArrow ) ) { moveDir += forwardDir; } if( Input.GetKey( KeyCode.DownArrow ) ) { moveDir -= forwardDir; } if( Input.GetKey( KeyCode.RightArrow ) ) { moveDir += rightDir; } if( Input.GetKey( KeyCode.LeftArrow ) ) { moveDir -= rightDir; } if( moveDir.sqrMagnitude > Mathf.Epsilon ) { moveDir = moveDir.normalized; Turn( moveDir ); Move( moveDir ); } if( Input.GetKeyDown( KeyCode.Space ) ) { Jump(); } }
今回作ったカメラの挙動では、RigのオブジェクトはY軸回転以外の回転はさせていません。
そのため、Rigのtransform.forward
とtransform.right
をそのまま使うだけで何とかなりました。
カメラが自動で回転しているときはどうしているの?
MultipurposeCameraRigのようにカメラが回転までしている際には、上記のやり方では正しい方向が取得できません。
うーん、どうすればいいのでしょうか。
Exsampleプロジェクトを参考にしてみましょうか。
CharacterThirdPersonシーンあたりがなんかやってんじゃないかなぁ。
このシーンでは、カメラをX軸方向にも操作できます。
こんな状態でカメラのtransform.forward
を使って操作制御をしてしまうと、進行方向にY軸の値が絡んできてしまいます。
空や地面に向かって移動しようとしてしまいます。
これをどうやっているのでしょうか。
スクリプトを見てみましょう。
ThirdPersonUserControl.cs
private void FixedUpdate() { // read inputs float h = CrossPlatformInputManager.GetAxis("Horizontal"); float v = CrossPlatformInputManager.GetAxis("Vertical"); bool crouch = Input.GetKey(KeyCode.C); // calculate move direction to pass to character if (m_Cam != null) { // calculate camera relative direction to move: m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized; m_Move = v*m_CamForward + h*m_Cam.right; } else { // we use world-relative directions in the case of no main camera m_Move = v*Vector3.forward + h*Vector3.right; } #if !MOBILE_INPUT // walk speed multiplier if (Input.GetKey(KeyCode.LeftShift)) m_Move *= 0.5f; #endif // pass all parameters to the character control script m_Character.Move(m_Move, crouch, m_Jump); m_Jump = false; }
えーと、m_CamForward
を設定しているところっぽいですな。
ふーむ、なるほど。
Vector3.Scale()
を使ってY軸の値を0にしてから、ベクトルを正規化したものを利用しているみたいですね。
ということはこんな感じにもスクリプトを書けそうですね。
if (m_Cam != null) { // calculate camera relative direction to move: // m_CamForward = Vector3.Scale(m_Cam.forward, new Vector3(1, 0, 1)).normalized; // ↑これと一緒かな m_CamForward = m_Cam.forward; m_CamForward.y = 0.0f; m_CamForward = m_CamForward.normalized; m_Move = v*m_CamForward + h*m_Cam.right; }
こっちのほうが処理的に早そうですが、元のスクリプトだと一行で書けるメリットがありますね。
ちなみにこの処理では、カメラが真上または真下まで角度が返れる場合は注意が必要ですね。
そうなると、進行方向として利用するのがY軸になってしまいます。
ただ、今回の処理でY軸方向は0になってしまうので、前進後進は出来なくなってしまいます。
ライトベクトルは何か処理するわけでもなく、カメラのtransform.right
をそのまま使っているみたいですね。
カメラがz軸に回転しない限り、ライトベクトルの方向が乱れることはないからかなぁ。
飛行機とかでは何か手を考える必要があるかもしれませんけどね。
できたの?
今回作ったスクリプトを以下にまとめました。
ゲームにとってカメラはとても大切ですからね。
まだまだ勉強せねばなりませんね。