ここまで、Cinemachineを使わない純粋なUnityカメラを用いて画面分割を行っていました。
Cinemachineを使用した環境下でも画面分割は可能です。
通常、Cinemachineはシーン上に配置されているすべてのバーチャルカメラから最も優先度の高いものを選択して制御します。
カメラワークの制御はCinemachine Brainが担っています。
ただし、制御対象のバーチャルカメラは、カメラのCulling Maskで設定されている対象レイヤーのものに限ります。
(Culling Mask:特定のゲームオブジェクトだけをカメラに映す階層)
例えば、カメラのCulling MaskにレイヤーP1が設定され、レイヤーP2が設定されていない場合、レイヤーP2のバーチャルカメラは制御の対象外となります。
ここにもう一つのカメラとCinemachine Brainを配置して、Culling MaskにレイヤーP1は設定せず、レイヤーP2を設定すると、こちらはレイヤーP1のバーチャルカメラが制御対象から除外されます。
この仕組みを利用して、Cinemachineで複数カメラを独立制御させます。
まず、プレイヤーPrefab配下のカメラにCinemachine Brain
コンポーネントを追加します。
次に、プレイヤー用のCinemachineカメラ(バーチャルカメラ)
を配置します。
ヒエラルキー左上の+アイコン > Cinemachine > Virtual Cameraより配置できます。
必要に応じて、カメラの追従設定を行います。
FollowとLook At項目にプレイヤーオブジェクトを指定
BodyにTransposerを指定
Binding ModeにSimple Follow With World Upを指定
して追従させてみます。
各プレイヤー毎のレイヤーを追加します。
4人対戦ゲームを想定し、P0、P1、P2、P3の4レイヤーを追加します。
プレイヤー入室時に割り当てられるプレイヤーインデックスに応じたレイヤーを設定するスクリプトの実装例になります。
PlayerCameraLayerUpdater.cs
using System;
using Cinemachine;
using UnityEngine;
using UnityEngine.InputSystem;
public class PlayerCameraLayerUpdater : MonoBehaviour
{
[SerializeField] private PlayerInput _playerInput;
[SerializeField] private CinemachineVirtualCamera _cinemachineCamera;
// プレイヤーインデックスとレイヤーの対応
[Serializable]
public struct PlayerLayer
{
public int index;
public int layer;
}
[SerializeField] private PlayerLayer[] _playerLayers;
private int _currentIndex = -1;
// 初期化
private void Awake()
{
if (_playerInput == null) return;
// レイヤー更新
OnIndexUpdated(_playerInput.user.index);
}
// 有効化
private void OnEnable()
{
if (PlayerInputManager.instance == null) return;
// プレイヤーが退室した時のイベントを監視する
PlayerInputManager.instance.onPlayerLeft += OnPlayerLeft;
}
// 無効化
private void OnDisable()
{
if (PlayerInputManager.instance == null) return;
PlayerInputManager.instance.onPlayerLeft -= OnPlayerLeft;
}
// プレイヤーが退室した時に呼ばれる
private void OnPlayerLeft(PlayerInput playerInput)
{
// 他プレイヤーが退室した時はインデックスがずれる可能性があるので
// レイヤーを更新する
if (playerInput.user.index >= _playerInput.user.index)
return;
// この時、まだインデックスは前のままなので-1する必要がある
OnIndexUpdated(_playerInput.user.index - 1);
}
// プレイヤーインデックスが更新された時に呼ばれる
private void OnIndexUpdated(int index)
{
if (_currentIndex == index) return;
// インデックスに応じたレイヤー情報取得
var layerIndex = Array.FindIndex(_playerLayers, x => x.index == index);
if (layerIndex < 0) return;
// プレイヤー用のカメラ取得
var playerCamera = _playerInput.camera;
if (playerCamera == null) return;
// カメラのCullingMaskを変更
// 自身のレイヤーは表示、他プレイヤーのレイヤーは非表示にする
for (var i = 0; i < _playerLayers.Length; i++)
{
var layer = 1 << _playerLayers[i].layer;
if (i == index)
playerCamera.cullingMask |= layer;
else
playerCamera.cullingMask &= ~layer;
}
// Cinemachineカメラのレイヤー変更
_cinemachineCamera.gameObject.layer = _playerLayers[layerIndex].layer;
_currentIndex = index;
}
}
上記をPlayerCameraLayerUpdater.csとしてUnityプロジェクトに保存し、プレイヤーPrefabにアタッチ。
インスペクターよりPlayer InputとCinemachineカメラを設定します。
各プレイヤーインデックスとレイヤーの対応テーブルを定義します
例では、レイヤーP0、P1、P2、P3のインデックスがそれぞれレイヤーの7、8、9、10要素目であるため、上記の設定にしています。
レイヤー値は数値を直接入力する必要がありますが、構造体やProperty Drawerを自作すると選択式でレイヤー値を指定することも可能です。 (レイヤー名で設定する方法)
サンプルスクリプトでは、C#イベント経由で退室通知を受け取るため、Player Input ManagerコンポーネントのNotification Behaviour項目にはInvoke C Shard Events
を指定してください。
実行すると、Cinemachineが適用された状態で、独立してカメラが制御されます。
この時、プレイヤーのUnityカメラのCulling Maskには、自身のレイヤーが設定され、他プレイヤーのレイヤーが未設定になります。
また、Cinemachineカメラのレイヤーには自身に対応するレイヤーが設定されます。
自身のプレイヤーインデックスが更新される時、次の処理でレイヤー情報とカメラの取得を行います。
// インデックスに応じたレイヤー情報取得
var layerIndex = Array.FindIndex(_playerLayers, x => x.index == index);
if (layerIndex < 0) return;
// プレイヤー用のカメラ取得
var playerCamera = _playerInput.camera;
if (playerCamera == null) return;
次に、自身のカメラのCulling Maskを更新します。この時、他プレイヤーのレイヤーは除外する必要があります。
// カメラのCullingMaskを変更
// 自身のレイヤーは表示、他プレイヤーのレイヤーは非表示にする
for (var i = 0; i < _playerLayers.Length; i++)
{
var layer = 1 << _playerLayers[i].layer;
if (i == index)
playerCamera.cullingMask |= layer;
else
playerCamera.cullingMask &= ~layer;
}
そして、Cinemachineカメラのレイヤーを自身のものに設定します。
// Cinemachineカメラのレイヤー変更
_cinemachineCamera.gameObject.layer = _playerLayers[layerIndex].layer;
ここまでの処理は、自身が入室した時に行うほか、他プレイヤーが退室した時もインデックスがずれる可能性があるため行います。
// 初期化
private void Awake()
{
if (_playerInput == null) return;
// レイヤー更新
OnIndexUpdated(_playerInput.user.index);
}
// プレイヤーが退室した時に呼ばれる
private void OnPlayerLeft(PlayerInput playerInput)
{
// 他プレイヤーが退室した時はインデックスがずれる可能性があるので
// レイヤーを更新する
if (playerInput.user.index >= _playerInput.user.index)
return;
// この時、まだインデックスは前のままなので-1する必要がある
OnIndexUpdated(_playerInput.user.index - 1);
}