Try this. Naively pushes the camera out of any intersecting collider. Maybe it’s enough. If not, let this be your starting point, feel free to modify and share.
using UnityEngine;
namespace Cinemachine
{
/// <summary>
/// An add-on module for Cinemachine Virtual Camera that post-processes
/// the final position of the virtual camera. Pushes the camera out of intersecting colliders.
/// </summary>
[DocumentationSorting(DocumentationSortingAttribute.Level.UserRef)]
[ExecuteInEditMode]
[AddComponentMenu("")] // Hide in menu
[SaveDuringPlay]
public class SimpleVcamCollider : CinemachineExtension
{
/// <summary>The Unity layer mask against which the collider will raycast.</summary>
[Tooltip("The Unity layer mask against which the collider will raycast")]
public LayerMask m_CollideAgainst = 1;
/// <summary>Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag</summary>
[TagField]
[Tooltip("Obstacles with this tag will be ignored. It is a good idea to set this field to the target's tag")]
public string m_IgnoreTag = string.Empty;
/// <summary>
/// Camera will try to maintain this distance from any obstacle.
/// </summary>
[Tooltip("Camera will try to maintain this distance from any obstacle.")]
public float m_CameraRadius = 0.1f;
private void OnValidate()
{
m_CameraRadius = Mathf.Max(0, m_CameraRadius);
}
/// <summary>Cleanup</summary>
protected override void OnDestroy()
{
base.OnDestroy();
CleanupCameraCollider();
}
/// <summary>Callcack to to the collision resolution and shot evaluation</summary>
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam,
CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
// Move the body before the Aim is calculated
if (stage == CinemachineCore.Stage.Body)
{
Vector3 displacement = RespectCameraRadius(state.RawPosition);
state.PositionCorrection += displacement;
}
}
private Collider[] mColliderBuffer = new Collider[5];
private SphereCollider mCameraCollider;
private GameObject mCameraColliderGameObject;
private Vector3 RespectCameraRadius(Vector3 cameraPos)
{
Vector3 result = Vector3.zero;
int numObstacles = Physics.OverlapSphereNonAlloc(
cameraPos, m_CameraRadius, mColliderBuffer,
m_CollideAgainst, QueryTriggerInteraction.Ignore);
if (numObstacles > 0)
{
if (mCameraColliderGameObject == null)
{
mCameraColliderGameObject = new GameObject("SimpleCollider Collider");
mCameraColliderGameObject.hideFlags = HideFlags.HideAndDontSave;
mCameraColliderGameObject.transform.position = Vector3.zero;
mCameraColliderGameObject.SetActive(true);
mCameraCollider = mCameraColliderGameObject.AddComponent<SphereCollider>();
var rb = mCameraColliderGameObject.AddComponent<Rigidbody>();
rb.detectCollisions = false;
rb.isKinematic = true;
}
mCameraCollider.radius = m_CameraRadius;
for (int i = 0; i < numObstacles; ++i)
{
Collider c = mColliderBuffer[i];
if (m_IgnoreTag.Length > 0 && c.CompareTag(m_IgnoreTag))
continue;
Vector3 dir;
float distance;
if (Physics.ComputePenetration(
mCameraCollider, cameraPos, Quaternion.identity,
c, c.transform.position, c.transform.rotation,
out dir, out distance))
{
result += dir * distance; // naive, but maybe enough
}
}
}
return result;
}
private void CleanupCameraCollider()
{
if (mCameraColliderGameObject != null)
{
#if UNITY_EDITOR
if (Application.isPlaying)
UnityEngine.Object.Destroy(mCameraColliderGameObject);
else
DestroyImmediate(mCameraColliderGameObject);
#else
UnityEngine.Object.Destroy(obj);
#endif
}
mCameraColliderGameObject = null;
mCameraCollider = null;
}
}
}
Just to clarify, there are certain limitations to ComputePenetration indeed. Namely, as described in the docs, one of the bodies passed to the ComputePenetration call has to be either primitive or a convex mesh. The other collider can be anything.
Unless I misunderstood you, it should work as expected in 17.3. Here is my test project I’ve just created. In it, move the GO named dollie to make it overlap with other GOs having Colliders attached. Notice that as long as one of the Colliders fits the restrictions, there is a red line showing the depenetration direction.
(I added a kinematic RB just to follow what seemed to be done here in the thread. ComputePenetration only reads the geometry from GOs, it doesn’t deal with dynamics at all)
@VideoTutorial So I have to take the blame for misleading you. SimpleVcamCollider is not enough to meet your needs, because Physics.ComputePenetration will not see backfacing triangles. This means that if the center of the sphere is inside the mesh, the mesh will be invisible and ComputePenetration will fail. This is what you’re seeing, I believe.
The code was taken from another context, one in which the sphere center was guaranteed to be outside the obstacles. In this use-case, it’s not sufficient. To make this work, we would need to keep track of the last position of the camera that was collision-free, and resolve any collision by moving the camera out towards that point.
Hello! Just found this thread while searching for the same problem. Used the SimpleVcamCollider and it performed mostly well. What I did to adapt it was replace the overlapSphere with a raycast from the target to the camera. I don’t know the difference in cost between the operations, but it worked well for my case.
Here are my alterations:
using UnityEngine;
namespace Cinemachine
{
/// <summary>
/// An add-on module for Cinemachine Virtual Camera that post-processes
/// the final position of the virtual camera. Pushes the camera out of intersecting colliders.
/// </summary>
[DocumentationSorting(0,DocumentationSortingAttribute.Level.UserRef)]
[ExecuteInEditMode]
[AddComponentMenu("")] // Hide in menu
[SaveDuringPlay]
public class MyVCamCollider : CinemachineExtension
{
/// <summary>The Unity layer mask against which the collider will raycast.</summary>
[Tooltip("The Unity layer mask against which the collider will raycast")]
public LayerMask m_CollideAgainst = 1;
/// <summary>Callcack to to the collision resolution and shot evaluation</summary>
protected override void PostPipelineStageCallback(
CinemachineVirtualCameraBase vcam,
CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
{
// Move the body before the Aim is calculated
if (stage == CinemachineCore.Stage.Body)
{
//Vector3 displacement = RespectCameraRadius(state.RawPosition);
//state.PositionCorrection += displacement;
state.PositionCorrection += RespectCameraTargetRay(state.RawPosition,vcam.LookAt.position);
}
}
private Ray mRay;
private RaycastHit mHit;
private Vector3 RespectCameraTargetRay(Vector3 cameraPos, Vector3 targetPos)
{
mRay.origin = targetPos;
mRay.direction = cameraPos - targetPos;
if (Physics.Raycast(mRay, out mHit, (cameraPos - targetPos).magnitude, m_CollideAgainst.value, QueryTriggerInteraction.Ignore))
{
return mHit.point - cameraPos;
}
else
return Vector3.zero;
}
}
}