[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 : マウスによる視点回転の有効/無効
内部パラメータ
カメラはターゲットの頭上を覗き込む動きをしています。その高さはスクリプト内部の以下の変数で設定されています。(デフォルト 5m)
private float targetHeight = 5.0f;
3. カメラ向きをマウスと右スティックで制御(XBOXコントローラ)
unityはデフォルトではコントローラの右スティックの設定がされていないため、以下の手順で設定します。
1) Menu: Edit > Project Settings > Input Manager を選択します。
2) Size(項目数) を2増やします。
この場合、18 → 20に増やします。
追加された項目を、以下の図ように設定します。
・Nameを Horizontal_RS、Vertical_RS
・各ボタン類の設定は削除
・Dead(しきい値)を0.2くらい、Sensitivityを1
・Type を Joystick Axis
・Axis を 4th(Horizontal_RS) および 5th(Vertical_RS)に変更
スクリプト側の、コントロールパッド部制御のコメントを取り払います。
// コントローラの右スティックによる回転を入れる場合は、ここに入れる。
// 回転速度はお好みに調整してください。
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...
このスクリプトが、あなたのゲームの世界観を表現する支えとなることがありましたら幸いです。