I\'m making a little arcade shooter for 2 players, and need to have the screen focused on 2 players, I got the camera moving in the center of the players in the X axis, but
jparimaa's answer helped me a lot, however it didn't work for me for narrow screens (e.g. phone portrait mode.) That's because it only accounts for height calculation, resulting in the two players being out of screen when they are close vertically but far horizontally.
I updated it to calculate camera distance correctly for height and width cases. The calculations are from Unity docs (same as jparimaa's):
The Size of the Frustum at a Given Distance from the Camera
Also note: Camera.fieldOfView is the vertical field of view
I ended up with the following code that works very well for my case: multiple space ships with 3D perspective camera that are controlled on 2D (x-z) plane:
using UnityEngine;
public class MeleeCamera : MonoBehaviour
{
public Transform[] targets;
public float padding = 15f; // amount to pad in world units from screen edge
Camera _camera;
void Awake()
{
_camera = GetComponent<Camera>();
}
private void LateUpdate() // using LateUpdate() to ensure camera moves after everything else has
{
Bounds bounds = FindBounds();
// Calculate distance to keep bounds visible. Calculations from:
// "The Size of the Frustum at a Given Distance from the Camera": https://docs.unity3d.com/Manual/FrustumSizeAtDistance.html
// note: Camera.fieldOfView is the *vertical* field of view: https://docs.unity3d.com/ScriptReference/Camera-fieldOfView.html
float desiredFrustumWidth = bounds.size.x + 2 * padding;
float desiredFrustumHeight = bounds.size.z + 2 * padding;
float distanceToFitHeight = desiredFrustumHeight * 0.5f / Mathf.Tan(_camera.fieldOfView * 0.5f * Mathf.Deg2Rad);
float distanceToFitWidth = desiredFrustumWidth * 0.5f / Mathf.Tan(_camera.fieldOfView * _camera.aspect * 0.5f * Mathf.Deg2Rad);
float resultDistance = Mathf.Max(distanceToFitWidth, distanceToFitHeight);
// Set camera to center of bounds at exact distance to ensure targets are visible and padded from edge of screen
_camera.transform.position = bounds.center + Vector3.up * resultDistance;
}
private Bounds FindBounds()
{
if (targets.Length == 0)
{
return new Bounds();
}
Bounds bounds = new Bounds(targets[0].position, Vector3.zero);
foreach (Transform target in targets)
{
if (target.gameObject.activeSelf) // if target not active
{
bounds.Encapsulate(target.position);
}
}
return bounds;
}
}
Field of view can be calculated like this:
FOV = 2 * arctan((0.5 * distanceBetweenPlayers) / (distanceFromMiddlePoint * aspectRatio));
Note that this gives the FOV where players are on the very edge of the viewport. Small margin could be added. I wanted to try this myself, here is my script:
public Transform player1;
public Transform player2;
private const float FOV_MARGIN = 15.0f;
private Vector3 middlePoint;
private float distanceFromMiddlePoint;
private float distanceBetweenPlayers;
private float aspectRatio;
void Start () {
aspectRatio = Screen.width / Screen.height;
}
void Update () {
// Find the middle point between players.
middlePoint = player1.position + 0.5f * (player2.position - player1.position);
// Position the camera in the center.
Vector3 newCameraPos = Camera.main.transform.position;
newCameraPos.x = middlePoint.x;
Camera.main.transform.position = newCameraPos;
// Calculate the new FOV.
distanceBetweenPlayers = (player2.position - player1.position).magnitude;
distanceFromMiddlePoint = (Camera.main.transform.position - middlePoint).magnitude;
Camera.main.fieldOfView = 2.0f * Mathf.Rad2Deg * Mathf.Atan((0.5f * distanceBetweenPlayers) / (distanceFromMiddlePoint * aspectRatio));
// Add small margin so the players are not on the viewport border.
Camera.main.fieldOfView += FOV_MARGIN;
}
If FOVs get big I would suggest moving the camera because perspective distorts with larger FOVs.
Come up with a range of zoom levels for your camera. Use the distance between the two characters as a ratio to determine how far along your zoom range to be.
just to add on @nokola's answer, (I cannot comment yet..) it seemed like his camera can only go above the objects, so instead of doing:
_camera.transform.position = bounds.center + Vector3.up * resultDistance;
you may do:
_camera.transform.position = bounds.center + -_camera.transform.forward * resultDistance;
now it should work for any rotation camera has. (for me it did atleast)
Moving the camera is better than changing the fov. The formula for calculating the camera distance is
cameraDistance = (distanceBetweenPlayers / 2 / aspectRatio) / Tan(fieldOfView / 2);
Note the players appear on the very edge of the viewport thus some small margin could be added. Here is my script again:
public Transform player1;
public Transform player2;
private const float DISTANCE_MARGIN = 1.0f;
private Vector3 middlePoint;
private float distanceFromMiddlePoint;
private float distanceBetweenPlayers;
private float cameraDistance;
private float aspectRatio;
private float fov;
private float tanFov;
void Start() {
aspectRatio = Screen.width / Screen.height;
tanFov = Mathf.Tan(Mathf.Deg2Rad * Camera.main.fieldOfView / 2.0f);
}
void Update () {
// Position the camera in the center.
Vector3 newCameraPos = Camera.main.transform.position;
newCameraPos.x = middlePoint.x;
Camera.main.transform.position = newCameraPos;
// Find the middle point between players.
Vector3 vectorBetweenPlayers = player2.position - player1.position;
middlePoint = player1.position + 0.5f * vectorBetweenPlayers;
// Calculate the new distance.
distanceBetweenPlayers = vectorBetweenPlayers.magnitude;
cameraDistance = (distanceBetweenPlayers / 2.0f / aspectRatio) / tanFov;
// Set camera to new position.
Vector3 dir = (Camera.main.transform.position - middlePoint).normalized;
Camera.main.transform.position = middlePoint + dir * (cameraDistance + DISTANCE_MARGIN);
}