When my player falls for an extended duration the speed keeps increasing due to the constant acceleration of gravity. When I use a Cinemachine camera with damping enabled, the player ends up falling out of view of the camera because the damping can’t keep up. It doesn’t matter how low I set the damping, eventually the player will move fast enough to leave the frame. How can I force the player to stay in frame even if damping is enabled?
I actually just figured it out right after I asked this question. There is a component called a Cinemachine Confiner 3D that does exactly what I want!
Nevermind that doesn’t work in my situation. The Confiner component applies to the actualy Unity camera gameobject after Cinemachine calculates its stuff for whatever you have set up. In my case I’m trying to set up a third person follow system. So I have “shoulder offset”, “vertical arm length”, and “camera distance” values applied. The pretend shoulders are what is getting damped and end up far away from the player at high speeds. The pretend shoulders are what I want to clamp, but instead, the actual Unity camera is getting clamped to the nearest point on the collider after the pretend shoulders are damped. See the screen shot I’m including.
Ok I think I have this one figured out. Rather than constrain the position, I slowly lerp the Damp values down to 0 when the player either moves past a certain speed threshold or when they leave the ground. Here is a MonoBehaviour I wrote that does this well I think. The IGrounding interface it’s referencing is something from my project but all it needs to know is if the target is grounded or not.
Edit: Rewrote the code to be simpler, handle a few bugs, and add docs.
using System.Collections;
using UnityEngine;
using Unity.Cinemachine;
/// <summary>
/// Smoothly disables damping on a Cinemachine camera while the target is moving quickly, especially when in free fall.
/// </summary>
[RequireComponent(typeof(CinemachineFreeLookModifier.IModifiablePositionDamping))]
[RequireComponent(typeof(CinemachineVirtualCameraBase))]
public class CinemachineDampDisable : MonoBehaviour
{
[Tooltip("As the target's speed approaches this value, the camera damping will gradually get weaker. When the " +
"target's speed reaches this value or beyond, damping will be fully disabled.")]
public float speedThreshold = 15;
[Tooltip("While the target's acceleration magnitude is greater than this value, the damping can only get " +
"smaller and will not increase back to the starting value. This is intended to prevent damping " +
"while the target is in free fall. Setting this to be a bit less than the magnitude of gravity " +
"works well.")]
public float accelerationThreshold = 8;
CinemachineFreeLookModifier.IModifiablePositionDamping damper;
Rigidbody rb;
IGrounding grounding;
Coroutine lateFixedUpdate;
Vector3 originalValue;
float t;
Vector3 velocity;
Vector3 acceleration;
void Awake()
{
damper = GetComponent<CinemachineFreeLookModifier.IModifiablePositionDamping>();
if (damper == null)
{
Debug.LogError($"The {nameof(CinemachineDampDisable)} component requires another component that " +
$"implements {nameof(CinemachineFreeLookModifier.IModifiablePositionDamping)}. " +
$"Some valid components are {nameof(CinemachineOrbitalFollow)}, " +
$"{nameof(CinemachineThirdPersonFollow)}, and " +
$"{nameof(CinemachinePositionComposer)}.");
enabled = false;
}
CinemachineVirtualCameraBase cinemachineCamera = GetComponent<CinemachineVirtualCameraBase>();
if (!cinemachineCamera || !cinemachineCamera.Follow)
{
Debug.LogError($"The {nameof(CinemachineDampDisable)} component requires that another component " +
$"which inherits {nameof(CinemachineVirtualCameraBase)} is attached to the " +
$"same GameObject and that is has a Transform assigned to the " +
$"{nameof(CinemachineVirtualCameraBase.Follow)} (Tracking Target) field.");
enabled = false;
}
else
{
rb = cinemachineCamera.Follow.GetComponentInParent<Rigidbody>();
if (!rb)
{
Debug.LogError(
$"The {nameof(CinemachineDampDisable)} component requires that the follow target " +
$"has a Rigidbody component attached to the same GameObject or a parent GameObject.");
enabled = false;
}
grounding = cinemachineCamera.Follow.GetComponentInParent<IGrounding>();
if (grounding == null)
{
Debug.LogError($"The {nameof(CinemachineDampDisable)} component requires its follow target " +
$"or a parent of the follow target to have a component that " +
$"implements {nameof(IGrounding)}.");
enabled = false;
}
}
}
void Start()
{
originalValue = damper.PositionDamping;
}
void OnEnable()
{
if (lateFixedUpdate != null) StopCoroutine(lateFixedUpdate);
lateFixedUpdate = StartCoroutine(LateFixedUpdate());
}
IEnumerator LateFixedUpdate()
{
while (enabled)
{
yield return new WaitForFixedUpdate();
Vector3 priorVelocity = velocity;
velocity = rb.velocity;
acceleration = (velocity - priorVelocity) / Time.deltaTime;
}
}
void Update()
{
float tRaw = 1 - Mathf.Clamp01(velocity.magnitude / speedThreshold);
t = acceleration.magnitude > accelerationThreshold || !grounding.IsGrounded
? Mathf.Min(t, tRaw)
: tRaw;
damper.PositionDamping = originalValue * t;
}
}
