Programmatically Changing Cinemachine Blends

By Eric Lathrop on

Background

My game Kick Bot uses Cinemachine to control Unity's cameras. Cinemachine lets you create multiple virtual cameras, and the real Unity camera will focus on the one with the highest priority. The priority is just an integer you can change with code whenever you want. When a new virtual camera becomes the "live" camera, Cinemachine will perform a blend from the old camera to the new one. Cinemachine will also let you create a blend list specifying blends in between specific virtual cameras.

The CinemachineBrain component shown in the Unity Inspector panel

First Problem

My problem was that I want to use a certain smooth blend most of the time, but when the character dies and respawns, I want to use a sudden "cut" blend. I can't use the blend list because the virtual cameras aren't named in any special way, each camera is just a different chunk of the level. I need a way to programmatically tell Cinemachine that just this one time I want to use a different blend than the default.

First Solution

Digging through the Cinemachine documentation I found the CinemachineCore.GetBlendOverride hook which will be called every time Cinemachine wants to create a blend between two cameras. I wrote a simple class to let me specify the next blend I want Cinemachine to use:

public class CinemachineBlendManager : MonoBehaviour {

  private static CinemachineBlendDefinition? nextBlend;

  static CinemachineBlendManager() {
    CinemachineCore.GetBlendOverride = GetBlendOverrideDelegate;
  }

  public static void ClearNextBlend() {
    nextBlend = null;
  }

  public static void SetNextBlend(CinemachineBlendDefinition blend) {
    nextBlend = blend;
  }

  public static CinemachineBlendDefinition GetBlendOverrideDelegate(ICinemachineCamera fromVcam, ICinemachineCamera toVcam, CinemachineBlendDefinition defaultBlend, MonoBehaviour owner) {
    if (nextBlend.HasValue) {
      var blend = nextBlend.Value;
      nextBlend = null;
      return blend;
    }
    return defaultBlend;
  }
}

There was some weird rubber-banding which was fixed when I set CinemachineVirtualCameraBase.PreviousStateIsValid = false, but then I ran into a second problem.

Second Problem

Setting the next blend to a "cut" after a respawn worked perfectly when the player died some distance from the respawn location, and was being viewed from a different virtual camera than the respawn location used.

However, sometimes the player dies while being viewed from the same virtual camera that the respawn location uses, so there is no blend to perform. In that case, later on the player changes areas, and since the blend was set but not used, an unwanted "cut" blend is performed. I needed a way to detect when there won't be a blend and reset it.

Second Solution

When the player is respawned, I just update its transform.location to the respawn location. The important thing to know is that the RigidBody2D doesn't update until the following frame, and that is when the virtual camera's priority will be updated. Since it happens in the next frame, I had to do a bit of a hack and just use a coroutine to call CinemachineBlendManager.ClearNextBlend at the end of the next frame.

public void Warp(Vector3 destination) {
  CinemachineBlendManager.SetNextBlend(new CinemachineBlendDefinition(CinemachineBlendDefinition.Style.Cut, 0));

  transform.position = destination;

  foreach (var vcam in allVirtualCameras) {
    vcam.PreviousStateIsValid = false;
  }
  StartCoroutine(ResetBlendNextFrame());
}

private IEnumerator ResetBlendNextFrame() {
  yield return new WaitForEndOfFrame();
  yield return new WaitForFixedUpdate();
  CinemachineBlendManager.ClearNextBlend();
}