2020/11/09
というわけで、今回はUnityの「Chinemachine」に関するお話です。
最近Unity標準になったというアセットで、カメラに関する制御や機能が含まれています。その機能の1つ「Target Group Camera」を使用した際に工夫したことをご紹介します。
TargetGroupのターゲット制御
Chinemachine の VirtualCamera に TargetGroup を設定すると、Target で指定したオブジェクトがカメラ範囲内に収まるように自動調整してくれる機能です。
Cinemachine がインポートされている場合、メニューの「Cinemachine→Create Target Group Camera」から作成できます。
TargetGroup のインスペクターからターゲットを設定できますが、これは内部的には配列で実装されています。もっと言うとList型ではないので、ターゲットを動的に変えることができません。
なので「予め空のオブジェクトを指定しておき、ゲームの状態に合わせて位置を動かす」という考え方でいきます。
まず空のオブジェクトを生成し、ターゲットとして設定します。東西南北に1つずつ、計4つ用意しました。
加えて、この4ターゲットを整理・制御するための親となる空オブジェクトを作り、その中に放り込んでおきます。各オブジェクトの位置はすべて0にしておきましょう。
↑の4オブジェクトを TargetGroup に設定します。
続いて親オブジェクトに「子オブジェクトを制御する処理」を記述したスクリプトを設定します。
using System.Collections.Generic; using System.Linq; using UnityEngine; public class CameraTargetManager : MonoBehaviour { protected Transform target_n; protected Transform target_s; protected Transform target_w; protected Transform target_e; void Start() { target_n = transform.Find("Target_N"); target_s = transform.Find("Target_S"); target_w = transform.Find("Target_W"); target_e = transform.Find("Target_E"); } void Update() { List<GameObject> enemies = GameObject.FindObjectsOfType<Enemy>().Cast<GameObject>().ToList(); if (0 < enemies.Count()) { Vector3 nortest = Vector3.zero; Vector3 southerly = Vector3.zero; Vector3 westest = Vector3.zero; Vector3 eastern = Vector3.zero; foreach (var enemy in enemies) { var enemyPos = enemy.transform.position; if (0 < enemyPos.z && enemyPos.z < nortest.z) { nortest = enemy.transform.position; } if (enemyPos.z < 0 && southerly.z < enemyPos.z) { southerly = enemy.transform.position; } if (0 < enemyPos.x && eastern.x < enemyPos.x) { eastern = enemy.transform.position; } if (enemyPos.x < 0 && enemyPos.x < westest.x) { westest = enemy.transform.position; } } target_n.position = target_n.position; target_s.position = target_s.position; target_w.position = target_w.position; target_e.position = target_e.position; } } }
私が作成した際は敵キャラクターを元に制御したかったので、FindObjectsOfType()
でEnemy
クラスのオブジェクトの一覧を取得しています。
(ただしUpdate()
内でFind系メソッドを実行するのはよろしくないため、実際のコードでは別のマネージャクラスから取得するようにしています)
東西はX軸、南北はZ軸を使用していますが、ゲームによっては逆になるかもしれません。要は平面4方向の最大値が取れれば良いので、深く考えなくても大丈夫です。
カメラ移動をなめらかにする
ターゲット用オブジェクトのTransform.position
に直接代入してしまうと、カメラ範囲も瞬間的に変わってしまい、ゲーム的によろしくありません。
そこでVector3.Lerp()
を使用します。
void Update() { List<GameObject> enemies = GameObject.FindObjectsOfType<Enemy>().Cast<GameObject>().ToList(); if (0 < enemies.Count()) { Vector3 nortest = Vector3.zero; Vector3 southerly = Vector3.zero; Vector3 westest = Vector3.zero; Vector3 eastern = Vector3.zero; foreach (var enemy in enemies) { var enemyPos = enemy.transform.position; if (0 < enemyPos.z && enemyPos.z < nortest.z) { nortest = enemy.transform.position; } if (enemyPos.z < 0 && southerly.z < enemyPos.z) { southerly = enemy.transform.position; } if (0 < enemyPos.x && eastern.x < enemyPos.x) { eastern = enemy.transform.position; } if (enemyPos.x < 0 && enemyPos.x < westest.x) { westest = enemy.transform.position; } } target_n.position = Vector3.Lerp(target_n.position, nortest, 0.05f); target_s.position = Vector3.Lerp(target_s.position, southerly, 0.05f); target_w.position = Vector3.Lerp(target_w.position, westest, 0.05f); target_e.position = Vector3.Lerp(target_e.position, eastern, 0.05f); } }
Vector3.Lerp()
にターゲット用オブジェクトと東西南北最も遠くにいるオブジェクトを指定します。第3引数の値を小さくするほどターゲット用オブジェクトの移動が遅くなり、その分カメラ範囲の切り替えも遅くなるので、結果的になめらかなカメラワークが実現できます。
開発中のゲームに組み込んで動作を確認しました。
うーん…そこまで悪くはないですが、怪しい挙動をする時がちらほらとあります。
Chinemachineのパラメータの見直しと合わせて調整する必要がありそうですが、基本的な考え方は悪くなさそうです。