Cinemachine camera collision

Hello,

Is there any way to stop the camera from going inside a collider, but without the feature which maintains line of sight?

I.e. I don’t want it to go in front of a cube when I’m behind it, but I don’t want to be able to move the camera inside the same cube

CM doesn’t provide that specific behaviour. You’d have to put your own collision extension together to do that.

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;
        }
    }
}

There was an error in my code above. Please change it to say

rb.isKinematic = true;

I’ve corrected this in my original post.

Should work, within certain limits. Maybe your mesh collider does not fit the rules. Check here: Unity - Scripting API: Physics.ComputePenetration

What version of Unity are you using?

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.

GIF: Screen capture - ad3536915b89d0864f8c776a46b76c2b - Gyazo

3396488–267254–compute-penetration.zip (1.69 MB)

(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;

        }
    }
}