Custom Cinemachine OnCameraBlendCompleteEvent implementation

Hey guys I’ve been messing around with Cinemachine code for the past few weeks and was a bit frustrated by the lack of a OnCameraBlendCompleteEvent in CinemachineBrain. So I did some work on the Cinemachine git repo to get it working (here for anyone curious). It became apparent later though that my implementation wasn’t going to work in all cases and thus is not likely to get added to master.

I took @Gregoryl advice though and whipped up my own CinemachineHelper script to fire an OnCameraBlendCompleteEvent as he suggested. Below is the result I’ve come up with. I figured I would share it in the hopes that it may help somebody :slight_smile:

using Cinemachine;
using UnityEngine;

namespace _Game_Assets.Scripts
{
    /// <summary>
    /// Adds additional functionality and events to the <see cref="CinemachineBrain"/>
    /// </summary>
    public class CinemachineBrainHelper : MonoBehaviour
    {
        /// <summary>
        /// THe cinemachine brain this class will help with
        /// </summary>
        [Tooltip("The cinemachine brain this class will help.")]
        public CinemachineBrain cinemachineBrain;
        /// <summary>
        /// Invoked when the camera has completely blended from one camera to another. Signature = toCamera, fromCamera.
        /// </summary>
        [Space]
        [Tooltip("Invoked when the camera has completely blended from one camera to another. Signature = toCamera, fromCamera.")]
        public CinemachineBrain.VcamActivatedEvent onCameraBlendCompleteEvent;
  
        /// <summary>
        /// In initial camera we were blending from. In other words the camera that started the blend sequence.
        /// </summary>
        private ICinemachineCamera fromCamera;
        /// <summary>
        /// The polling rate at which we will check if the IsBlending flag is set to false.
        /// </summary>
        private const float CHECK_BRAIN_IS_BLENDING_REPEAT_RATE = 0.1f;
  
        private void Awake()
        {
            cinemachineBrain.m_CameraActivatedEvent.AddListener(OnCameraActivatedEventHandler);
        }
  
        /// <summary>
        /// Attaches to <see cref="cinemachineBrain"/> in order to spawn our <see cref="onCameraBlendCompleteEvent"/>
        /// when the cameras are finished blending (brain.IsBlending = false).
        /// </summary>
        /// <param name="toCamera">The camera we blended to.</param>
        /// <param name="fromCamera">The camera we blended away from.</param>
        private void OnCameraActivatedEventHandler(ICinemachineCamera toCamera, ICinemachineCamera fromCamera)
        {
            if (fromCamera == null)
            {
                return;
            }

            var oldFromCamera = this.fromCamera;
            this.fromCamera = fromCamera;
            if (oldFromCamera != null)
            {
                return;
            }
            InvokeRepeating(nameof(CheckBrainIsBlending),0,CHECK_BRAIN_IS_BLENDING_REPEAT_RATE);
        }

        /// <summary>
        /// Invoked Repeatedly when our <see cref="cinemachineBrain"/> <see cref="m_CameraActivatedEvent"/> has fired
        /// in order to poll <see cref="cinemachineBrain.IsBlending"/> to find when the blend has been completed.
        /// </summary>
        private void CheckBrainIsBlending()
        {
            if (!cinemachineBrain.IsBlending)
            {
                CancelInvoke(nameof(CheckBrainIsBlending));
                onCameraBlendCompleteEvent?.Invoke(cinemachineBrain.ActiveVirtualCamera,fromCamera);
                fromCamera = null;
            }
        }
    }
}
3 Likes

I went ahead and open sourced this here for anyone interested. GitHub - Breakstep-Studios/toms-cinemachine-tools: A collection of tools for use with Unity's Cinemachine

1 Like

Thank you, it was helpful!

1 Like

If you don’t need an anonymous event firing, I was able to achieve a similar effect with a co-routine.

    void Update()
    {
        if(Input.GetKeyUp(KeyCode.Space))
        {
            if(rtsCamActive)
            {
                vCamRTS.Priority = 0;
                vCamFPS.Priority = 1;
                StartCoroutine("FireOnEndBlend");
            }
            else
            {
                vCamRTS.Priority = 1;
                vCamFPS.Priority = 0;
                StartCoroutine("FireOnEndBlend");
            }

            rtsCamActive = !rtsCamActive;
        }
    }

    IEnumerator FireOnEndBlend()
    {
        while(brain.IsBlending == false) //It Takes a couple frames for the IsBlending to switch to true
        {
            Debug.Log("Not Yet");
            yield return new WaitForEndOfFrame();
        }

        while(brain.IsBlending)
        {
            Debug.Log("Blending");
            yield return new WaitForSecondsRealtime(1);
        }

        Debug.Log("Done!");
    }
1 Like