[unity]PCアクションゲーム向けカメラ制御スクリプト(仮公開)

本記事の最終版はzennに記載します。


PCで3Dのアクションゲーム遊ぶとき、シンプルで使いやすい(と思われる)カメラ制御のスクリプトを紹介します。

1. ユーザの追尾

カメラの角度と距離を一定にして追尾するベーシックなカメラです。
トップビューのゲームの場合、これでなんとかなってしまうかもしれません。

// 2021.02 K1Togami Follow Camera Primitive
// BASICなカメラ "FollowCameraPrimitive.cs"
using UnityEngine;

public class FollowCameraPrimitive : MonoBehaviour
{
    public Transform target;
    public Vector3 offset;
    public float positionSmoothing = 5f;
    public float lookAtSmoothing = 5f;

    Vector3 currentLookAt;

    void Start ()
    {
		   currentLookAt = target.position;
    }


    void LateUpdate()
    {

        Vector3 targetCamPos = target.position + offset;

        transform.position = Vector3.Lerp (transform.position, targetCamPos, positionSmoothing * Time.deltaTime);
        currentLookAt = Vector3.Lerp (currentLookAt, target.position, lookAtSmoothing * Time.deltaTime);
        transform.LookAt(currentLookAt);
	}
}
・target に 追尾する GameObjectを設定します。
・offset に カメラとtargetの距離を設定します。
 追尾するオブジェクトの大きさやゲーム画面作りに応じて修正してください。
 ex)
 水平距離を5m、垂直距離を3mとする場合: x=5、y=3、z=0
 ・斜め45度から見るとき: x=3.535(12.5の平方根)、y=3、z=3.535
・positionSmoothing と lookAtSmoothing は、カメラをスムースに動かすためのなめらかさです。

2. カメラ向きを変えられるようにする。

ほぼGaiaのアセット付属のThird Persons用カメラがよかったので、いろいろと調べ、機能をシンプルにし、コメントを日本語にするなどしてみました。
サンプルそのものは末尾(4章)にありますが、ダウンロードもできるようにしておきました。

お願い
このスクリプトの大本は、下のほうに記載しますが無償で配布してくださっているFCCさんのスクリプトをベースにしています。
彼らの迷惑とならない範疇で、ナイスジェントル的にご利用ください。

このコントローラは、以下の動作を想定しています。
・Targetに設定したオブジェクトをフォローします。
・カメラはDistanceぶん離れ、Horaizonal Angle および Vertical Angle の角度の位置に設定されます。
・マウスの右クリックをしながらマウス移動で視点を変更できます。
 上下角度は Vartical Angle Max/Min Limitで設定できます。
・カメラとターゲットの間が壁など Collision Layers に設定されたレイヤーのオブジェクト(メッシュ)に遮られた場合は、カメラをターゲットの方向に移動して視界を確保しようとします。
・カメラ制御の入力が1秒間なかった場合は、ターゲットの視線にカメラ向きをあわせはじめます。

他の項目の設定は以下のようになっています。
・Max/Min Distance: カメラとターゲットの最大/最短距離
・Rotation Speed: 1秒間に回転する角度を制限(なめらかに回転)
・Rotation/Zoom Dumpening: 回転とズームのなめらさ
・Offset from wall: カメラを遮る物体からのオフセット
・Allow Mouse Input : マウスによる視点回転の有効/無効

画像4

内部パラメータ
カメラはターゲットの頭上を覗き込む動きをしています。その高さはスクリプト内部の以下の変数で設定されています。(デフォルト 5m)
private float targetHeight = 5.0f;

3. カメラ向きをマウスと右スティックで制御(XBOXコントローラ)

 unityはデフォルトではコントローラの右スティックの設定がされていないため、以下の手順で設定します。

1) Menu: Edit > Project Settings > Input Manager を選択します。

画像1

2) Size(項目数) を2増やします。
 この場合、18 → 20に増やします。

画像2

追加された項目を、以下の図ように設定します。
・Nameを Horizontal_RS、Vertical_RS
・各ボタン類の設定は削除
・Dead(しきい値)を0.2くらい、Sensitivityを1
・Type を Joystick Axis
・Axis を 4th(Horizontal_RS) および 5th(Vertical_RS)に変更

画像4

スクリプト側の、コントロールパッド部制御のコメントを取り払います。

// コントローラの右スティックによる回転を入れる場合は、ここに入れる。
// 回転速度はお好みに調整してください。
var controllerHorizonal = Input.GetAxis("Horizontal_RS");
horizontalAngle += controllerHorizonal * rotationSpeed * 0.001f;
verticalAngle += Input.GetAxis("Vertical_RS") * rotationSpeed * 0.001f;
if (controllerHorizonal != 0f ) CameraFollowDelay = 1.0f;

4. スクリプト

このスクリプトが参考にしたGaia付属のカメラは、以下のサイトのCameraControllercsv2.1.cs をベースにしています。ということで、このスクリプトも実はGaia付属のカメラといいうよりは、このFCCさんのスクリプトをベースにしています。

サイト「FCC」の運営者さんに感謝します。

また、FCCさんのコメントに以下のような記載がありました。

Unity-Communityの助けを借りて、ついにWoWライクなカメラとキャラクターコントローラーの目標に到達しました。

 これに感謝しつつ、私も使用させていただきます。
 また、できるだけ質問等対応し、unityコミュニティーを盛り上げる運動に参加したいなと思いました。
 なにか質問がありましたらコメントかtwitterでお問い合わせください。
(ただ、DM等には対応しかねる場合がありますのでご承知おきください)

// 2021.02 K1Togami Follow Camera
// Base on CameraControllercsv2.1.cs in https://ruhrnuklear.de/fcc/

using UnityEngine;

public class FollowCamera : MonoBehaviour
{

   public Transform target; // ターゲット
   private float targetHeight = 5.0f;

   public float distance = 10.0f;
   public float horizontalAngle = 0.0f;
   public float verticalAngle = 10.0f;

   // カメラの移動限界
   public float verticalAngleMinLimit = -30f; // 見上げ限界角度
   public float verticalAngleMaxLimit = 80f; // 見下ろし限界角度 
   public float maxDistance = 20f; // 最大ズーム距離 
   public float minDistance = 0.6f; // 最小ズーム距離 

   //public Vector3 offset = Vector3.zero; // ターゲットとカメラのオフセット

   public float rotationSpeed = 180.0f; // 画面の横幅分カーソルを移動させたとき何度回転するか.
   public float rotationDampening = 0.5f; // 回転の減衰速度 (higher = faster) 
   public float zoomDampening = 5.0f; // Auto Zoom speed (Higher = faster) 

   // 衝突検知用
   public LayerMask collisionLayers = -1; // What the camera will collide with 
   public float offsetFromWall = 0.1f; // 衝突する物体からカメラを遠ざけるときのオフセット 


   private float currentDistance; // 現在のカメラ距離
   private float desiredDistance; // 目標とするカメラ距離
   private float correctedDistance; // 矯正後のカメラ距離

   private float CameraFollowDelay; // カメラ回転後のカメラフォローまでの遅延

   // ユーザによる回転の許可
   public bool allowMouseInput = true; // カメラの方向をマウスでコントロールすることを許可するか。

   void Start()
   {
       Vector3 angles = transform.eulerAngles;
       horizontalAngle = angles.x;
       verticalAngle = angles.y;

       currentDistance = distance;
       desiredDistance = distance;
       correctedDistance = distance;

       CameraFollowDelay = 0f;
   }

   void LateUpdate()
   {
       // ターゲットが定義されていない場合は何もしない
       if (target == null)
           return;

       Vector3 vTargetOffset; // ターゲットからのオフセット

       if (GUIUtility.hotControl == 0)
       {
           // マウス入力が許可されているかどうかを確認する
           if (allowMouseInput)
           {
               // マウスの右ボタンを押しながらマウスを動かすと、視点を変更できる。
               if (Input.GetMouseButton(1))
               {
                   horizontalAngle += Input.GetAxis("Mouse X") * rotationSpeed * 0.02f;
                   verticalAngle -= Input.GetAxis("Mouse Y") * rotationSpeed * 0.02f;
                   CameraFollowDelay = 1.0f;
               }
           }

           // コントローラの右スティックによる回転を入れる場合は、ここに入れる。
           // 回転速度はお好みに調整してください。
           //var controllerHorizonal = Input.GetAxis("Horizontal_RS");
           //horizontalAngle += controllerHorizonal * rotationSpeed * 0.001f;
           //verticalAngle += Input.GetAxis("Vertical_RS") * rotationSpeed * 0.001f;
           //if (controllerHorizonal != 0f ) CameraFollowDelay = 1.0f;


           if (CameraFollowDelay > 0f)
           {
               CameraFollowDelay -= Time.deltaTime;
           } else
           {
               // マウスによる回転が無効の場合、カメラ視線をターゲットの視線にじわじわあわせる
               RotateBehindTarget();
           }

           verticalAngle = ClampAngle(verticalAngle, verticalAngleMinLimit, verticalAngleMaxLimit);

           // カメラの向きを設定
           Quaternion rotation = Quaternion.Euler( verticalAngle,horizontalAngle, 0);

           // 希望のカメラ位置を計算
           vTargetOffset = new Vector3(0, -targetHeight, 0);
           Vector3 position = target.transform.position - (rotation * Vector3.forward * desiredDistance + vTargetOffset);

           // 高さを使ってユーザーが設定した真のターゲットの希望の登録点を使って衝突をチェック
           RaycastHit collisionHit;
           Vector3 trueTargetPosition = new Vector3(target.transform.position.x,
               target.transform.position.y + targetHeight, target.transform.position.z);

           // 衝突があった場合は、カメラ位置を補正し、補正後の距離を計算
           var isCorrected = false;
           if (Physics.Linecast(trueTargetPosition, position, out collisionHit, collisionLayers))
           {
               // 元の推定位置から衝突位置までの距離を計算し、衝突した物体から安全な「オフセット」距離を差し引く
               // このオフセットは、カメラがヒットした面の真上にいないよう逃がす距離
               correctedDistance = Vector3.Distance(trueTargetPosition, collisionHit.point) - offsetFromWall;
               isCorrected = true;
           }

           // スムージングのために、距離が補正されていないか、または補正された距離が現在の距離より
           // も大きい場合にのみ、距離を返す。
           currentDistance = !isCorrected || correctedDistance > currentDistance
               ? Mathf.Lerp(currentDistance, correctedDistance, Time.deltaTime * zoomDampening)
               : correctedDistance;

           // 限界を超えないようにする
           currentDistance = Mathf.Clamp(currentDistance, minDistance, maxDistance);

           // 新しい currentDistance に基づいて位置を再計算する。
           position = target.transform.position - (rotation * Vector3.forward * currentDistance + vTargetOffset);

           // 最後にカメラの回転と位置を設定。
           transform.rotation = rotation;
           transform.position = position;

       }

   }


   // カメラを背後にまわす。
   private void RotateBehindTarget()
   {
       float targetRotationAngle = target.transform.eulerAngles.y;
       float currentRotationAngle = transform.eulerAngles.y;
       horizontalAngle = Mathf.LerpAngle(currentRotationAngle, targetRotationAngle, rotationDampening * Time.deltaTime);
   }

   // 角度クリッピング
   private float ClampAngle(float angle, float min, float max)
   {
       if (angle < -360f)
           angle += 360f;
       if (angle > 360f)
           angle -= 360f;
       return Mathf.Clamp(angle, min, max);
   }

}

おわりに
 なぜ、カメラコントローラなんて世の中にたくさんあるのに(略
→カメラコントローラは、思ったより深い世界です。たとえばゲームの世界観にあわせ、効果的な変更を加えていく。しかし、その母体となるわかりやすくシンプルなコントローラがなかったら、それを諦める場面もあるかもしれません。

変更例)
・ターゲットをロックオンしたらそちらに視点を変更する
・速度によってターゲットとの距離を変更する
・キャラクタが立った位置によってカメラをひねる(天井立ちなど)
・高いところから飛び降りたとき、自動的に下(着地点方向)を向くようにする
・敵と味方の位置が離れたらカメラをひき、近づいたらカメラを近づける
etc...

 このスクリプトが、あなたのゲームの世界観を表現する支えとなることがありましたら幸いです。

この記事が気に入ったらサポートをしてみませんか?