Reputation: 93
I have a list of objects, these are blue stickmen on the video, I need to make the camera move away by itself and all objects (blue stickmen) always fit into it, you need to take into account that there will be more and more objects each time, so the camera should be dynamic and adapt itself to all objects
https://youtube.com/shorts/x3uSO2L22Kc?feature=share
Upvotes: 1
Views: 147
Reputation: 4313
The idea here is fairly simple.
At each step we check if each object is inside of camera view or is camera too far away, then we simply adjust the camera position towards a better one.
Step by step, our camera will follow target objects dynamically, and when stabilized, all target objects will be captured by camera.
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class CameraAutoFitSmooth : MonoBehaviour
{
public int maxNumberOfObjs = 100;
public GameObject objPrefab;
public float maxInitRange = 10f;
public float minCameraHeight = 1f;
public float maxCameraMoveSpeed = 9f;
public float marginalPos = 0.1f;
[HideInInspector]
public List<Transform> objs = new List<Transform>();
Camera cam;
void Start()
{
cam = Camera.main;
}
void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
RandomObjs();
}
// Randomly regenerate objects
void RandomObjs()
{
int nowNumberOfObjs = Random.Range(0, maxNumberOfObjs);
for (int i = objs.Count - 1; i > nowNumberOfObjs; i--)
{
Destroy(objs[i].gameObject);
objs.RemoveAt(i);
}
for (int i = objs.Count; i <= nowNumberOfObjs; i++)
objs.Add(Instantiate(objPrefab).transform);
foreach (var obj in objs)
obj.position = Random.insideUnitSphere * maxInitRange;
}
void LateUpdate()
{
SetCameraFitPosition(Time.deltaTime);
}
void SetCameraFitPosition(float deltaTime)
{
Vector3 targetCamPos = cam.transform.position;
if (objs.Count == 1)
{
targetCamPos = objs[0].position - minCameraHeight * cam.transform.forward;
}
else if (objs.Count > 1)
{
float minInsideDiff = 1f, maxOutsideDiff = 0f;
Vector3 center = Vector3.zero;
foreach (var obj in objs)
{
Vector3 screenPos = GetScreenPos(obj.position);
if (IsInsideView(screenPos))
minInsideDiff = Mathf.Min(minInsideDiff, CalculateInsideDiff(screenPos.x), CalculateInsideDiff(screenPos.y), CalculateInsideDiff(screenPos.z));
else
maxOutsideDiff = Mathf.Max(maxOutsideDiff, CalculateOutsideDiff(screenPos.x), CalculateOutsideDiff(screenPos.y), CalculateOutsideDiff(screenPos.z));
center += obj.position;
}
center /= objs.Count;
float nowHeight = Vector3.Project(cam.transform.position - center, cam.transform.forward).magnitude;
float maxDiff = maxOutsideDiff > 0f ? maxOutsideDiff : -minInsideDiff;
float finalHeight = Mathf.Max(nowHeight + maxDiff * maxCameraMoveSpeed, minCameraHeight);
targetCamPos = center - finalHeight * cam.transform.forward;
}
cam.transform.position = Vector3.MoveTowards(cam.transform.position, targetCamPos, maxCameraMoveSpeed * deltaTime);
}
Vector3 GetScreenPos(Vector3 pos)
{
return cam.WorldToViewportPoint(pos);
}
float CalculateOutsideDiff(float pos)
{
float diff = 0f;
if (pos > 1f + marginalPos)
diff = pos - 1f;
else if (pos < -marginalPos)
diff = -pos;
return diff;
}
float CalculateInsideDiff(float pos)
{
float diff = 0f;
if (pos < 1f - marginalPos && pos > marginalPos)
diff = Mathf.Min(1f - pos, pos);
return diff;
}
bool IsInsideView(Vector3 screenPoint)
{
return screenPoint.z > 0f && screenPoint.x > 0f && screenPoint.x < 1 && screenPoint.y > 0f && screenPoint.y < 1;
}
}
If you need more info feel free to contact me :) Cheers!
Upvotes: 1
Reputation: 1550
The following script will position a perspective camera in a top down view, so that all tracked GameObjects (objs
) are visible.
It is assumed that the objects are on the zero xz plane and are points, so their actual dimensions are not taken into account. There must be at least one tracked object. The objects may not be spaced in such a way that would require the cameras height to exceed the maximum floating point value.
public GameObject[] objs;//objects that must be fitted
private Camera cam;
float recXmin, recXmax, recYmin, recYmax;
Vector3 center;
void Start()
{
cam = Camera.main;
cam.transform.rotation = Quaternion.Euler(90, 0, 0);
}
void LateUpdate()
{
recXmin = objs[0].transform.position.x;
recXmax = objs[0].transform.position.x;
recYmin = objs[0].transform.position.z;
recYmax = objs[0].transform.position.z;
center = Vector3.zero;
foreach (GameObject obj in objs)
{
if (obj.transform.position.x < recXmin)
{
recXmin = obj.transform.position.x;
}
if (obj.transform.position.x > recXmax)
{
recXmax = obj.transform.position.x;
}
if (obj.transform.position.z < recYmin)
{
recYmin = obj.transform.position.z;
}
if (obj.transform.position.z > recYmax)
{
recYmax = obj.transform.position.z;
}
}
float horizontalHeight = (recYmax - recYmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * cam.fieldOfView / 2);
float verticalHeight = (recXmax - recXmin) / 2 / Mathf.Tan(Mathf.Deg2Rad * Camera.VerticalToHorizontalFieldOfView(cam.fieldOfView, cam.aspect) / 2);
float finalHeight = horizontalHeight > verticalHeight ? horizontalHeight : verticalHeight;
center = new Vector3(recXmin + (recXmax - recXmin) / 2, finalHeight, recYmin + (recYmax - recYmin) / 2);
cam.transform.position = center;
}
void OnDrawGizmos()
{
Gizmos.color = Color.red;
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmin, 0, recYmax));
Gizmos.DrawLine(new Vector3(recXmax, 0, recYmin), new Vector3(recXmax, 0, recYmax));
Gizmos.color = Color.green;
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmin), new Vector3(recXmax, 0, recYmin));
Gizmos.DrawLine(new Vector3(recXmin, 0, recYmax), new Vector3(recXmax, 0, recYmax));
Gizmos.color = Color.blue;
Gizmos.DrawSphere(center, 0.5f);
}
Upvotes: 1