Move camera around on a fixed/dolly cam

Hi,

I wonder if it is possible to get cinemachine to have an “extra” layer, so you could have let’s say a dolly cam following and looking at the player but beeing able to move the camera around with the mouse or joystick.

An example of a game using something similar would be ICO. They are all fixed cams/or splines following the player, but you can move the view around with joystick, and when release it, the cam will target the player normally again.

If I have to code this myself, anyone has any tips on what would be the best approach?

Thanks.

Hi,
I would add two vcams:

  • The first one is a dolly cam following and looking at the player (vcam1).
  • The second one would be following the player, but the rotation would be controlled by your input (vcam2).

When you detect that the user moves their joystick, you switch to the second vcam. When the user releases the joystick, you switch to the first vcam.

The second vcam would have Do Nothing for Aim. This way, you have full control over the rotation part of the vcam. Before switching from vcam1 to vcam2, you’d set vcam2’s rotation to vcam1’s rotation. This way the two cameras are the same at switch, then you start applying the user input to change the rotation of vcam2. When you switch from vcam2 to vcam1, I would use a blend. You can setup blends on CinemachineBrain by either modifying the default blend or by setting up a custom blend.

Thanks @gaborkb ! That would cover most cases, there is one that I still have some doubts, if I were to move this secondary camera while the main one (vcam1 in your example) is transitioning to another vcam is there any callback or similar in cinemachine that I could use to get the final rotation and position after blending so I could apply the new rotations of vcam2 on top of that?

You could try to enable this option in vcam2:

6608266--751906--upload_2020-12-10_9-17-13.png

That will apply the outgoing vcam position/rotation to the incoming one, even if the outgoing is a blend.

Depending on your setup, you might be interested in an alternative approach, which is to add a custom extension to your main vcam that adds rotation based on user input. It could reset itself if there is no user input after a specified delay.

As luck would have it, I happen to have just such an extension. To try it out, just drop it in your project and add it to your vcam via the Extensions dropdown in the vcam’s inspector.

using UnityEngine;
using Cinemachine;

/// <summary>
/// An add-on module for Cinemachine Virtual Camera that adds extra rotation
/// in response to user input.
/// </summary>
public class CinemachineExtraPOV : CinemachineExtension
{
    /// <summary>The Vertical axis.  Value is -90..90. Controls the vertical orientation</summary>
    [Tooltip("The Vertical axis.  Value is -90..90. Controls the vertical orientation")]
    [AxisStateProperty]
    public AxisState m_VerticalAxis;

    /// <summary>Controls how automatic recentering of the Vertical axis is accomplished</summary>
    [Tooltip("Controls how automatic recentering of the Vertical axis is accomplished")]
    public AxisState.Recentering m_VerticalRecentering;

    /// <summary>The Horizontal axis.  Value is -180..180.  Controls the horizontal orientation</summary>
    [Tooltip("The Horizontal axis.  Value is -180..180.  Controls the horizontal orientation")]
    [AxisStateProperty]
    public AxisState m_HorizontalAxis;

    /// <summary>Controls how automatic recentering of the Horizontal axis is accomplished</summary>
    [Tooltip("Controls how automatic recentering of the Horizontal axis is accomplished")]
    public AxisState.Recentering m_HorizontalRecentering;

    private void Reset()
    {
        m_VerticalAxis = new AxisState(-70, 70, false, false, 1, 0.1f, 0.1f, "Mouse Y", true)
            { m_SpeedMode = AxisState.SpeedMode.InputValueGain };
        m_VerticalRecentering = new AxisState.Recentering(true, 0.5f, 0.5f);
        m_HorizontalAxis = new AxisState(-180, 180, true, false, 1, 0.1f, 0.1f, "Mouse X", false)
            { m_SpeedMode = AxisState.SpeedMode.InputValueGain };
        m_HorizontalRecentering = new AxisState.Recentering(true, 0.5f, 0.5f);   
    }

    private void OnValidate()
    {
        m_VerticalAxis.Validate();
        m_VerticalRecentering.Validate();
        m_HorizontalAxis.Validate();
        m_HorizontalRecentering.Validate();
    }

    protected override void OnEnable()
    {
        base.OnEnable();
        UpdateInputAxisProvider();
    }
   
    void UpdateInputAxisProvider()
    {
        m_HorizontalAxis.SetInputAxisProvider(0, null);
        m_VerticalAxis.SetInputAxisProvider(1, null);
        if (VirtualCamera != null)
        {
            var provider = VirtualCamera.GetInputAxisProvider();
            if (provider != null)
            {
                m_HorizontalAxis.SetInputAxisProvider(0, provider);
                m_VerticalAxis.SetInputAxisProvider(1, provider);
            }
        }
    }

    protected override void PostPipelineStageCallback(
        CinemachineVirtualCameraBase vcam,
        CinemachineCore.Stage stage, ref CameraState state, float deltaTime)
    {
        if (stage == CinemachineCore.Stage.Aim)
        {
            // Only read joystick when game is playing
            if (deltaTime >= 0 && CinemachineCore.Instance.IsLive(VirtualCamera))
            {
                if (m_HorizontalAxis.Update(deltaTime) | m_VerticalAxis.Update(deltaTime))
                {
                    m_HorizontalRecentering.CancelRecentering();
                    m_VerticalRecentering.CancelRecentering();
                }

                m_HorizontalRecentering.DoRecentering(ref m_HorizontalAxis, deltaTime, 0);
                m_VerticalRecentering.DoRecentering(ref m_VerticalAxis, deltaTime, 0);
            }
            var tilt = state.RawOrientation * Quaternion.AngleAxis(m_VerticalAxis.Value, Vector3.right);
            var rot = Quaternion.AngleAxis(m_HorizontalAxis.Value, state.ReferenceUp) * tilt;
            state.OrientationCorrection = Quaternion.Inverse(state.CorrectedOrientation) * rot;
        }
    }
}
2 Likes

Thanks so much @Gregoryl ! That’s exaclty what I needed. Also it is good to know how easy is to implement extensions for the cams, so double thanks :slight_smile:

1 Like

hey @Gregoryl one question regarding your code, currently it applies a local rotation with regards to the current rotation right?

I mean, for example, if you have a camera pointing 45 degrees down and you rotate left, the Y axis is not going to rotate around a plane with a normal pointing upwards (0,1,0) but around a plane whose normal is 45 degrees from (0,1,0).

Is there a way to make this rotate on the Y axis as if it wasn’t tilted?

Great idea, and a worthwhile improvement. I edited my earlier post to make it do that.

1 Like

Thanks @Gregoryl for your help again!

Unfortunately, that is not what I was looking for.


(excuse me for the lame drawing XD) If the camera is the blue circle and the green line being the “viewing direction”, it currently rotates around the yellow line (current camera.up vector), but what I’m looking for is rotating around the red line ((0,1,0) vector).

My understanding of quatertions is very basic, I thought that to change from local to world rotation the rotation generated had to be applied before/after the current rotation. I went down the rabbit hole, and it seems OrientationCorrection gets multiplied by RawOrientation, in this order: RawOrientation * OrientationCorrection . So I thought to change the order and have OrientationCorrection multiplied before I could do:

Quaternion.Inverse(state.RawOrientation) * state.OrientationCorrection * rot * state.RawOrientation;

This rotated correctly from left to right, but when rotating up or down it would yield different results depending on the orignal camera orientation.

After different tries I ended up with this code:

 Quaternion rot = Quaternion.Euler(m_VerticalAxis.Value, m_HorizontalAxis.Value, 0) * Quaternion.Inverse(state.RawOrientation);
            state.OrientationCorrection = Quaternion.Inverse(state.RawOrientation) * state.OrientationCorrection * rot * state.RawOrientation;
;

All the rotation axis works fine here, BUT the default orientation is always wrong, so when you switch to this camera or try to recenter it will always point at a wrong place.

And there is where I got stuck…

Yes, my bad, I tested with a horizontal camera :-/
Edited again to fix it :slight_smile:

1 Like

Yahoo! It works perfectly now :slight_smile: Thanks for all the help @Gregoryl

1 Like