I’ll share the approach I used in a work in progress game. Since you’re not interested in the FreelookCamera, a straight virtual camera will work better for what we’re doing.
Make the VirtualCamera follow and lookat the player, set the Body to Framing Transposer and the Aim at Do Nothing. Adjust the camera as needed to be in the correct position.
Here is my original FollowCameraZoomAndRotate script I use in SpellbourneHunter. I’ll paste the original script, and then I’ll make the changes that should adapt it to be used with the InputReader.
FollowCameraZoomAndRotate
using Cinemachine;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace TkrainDesigns.Cinematics
{
[RequireComponent(typeof(CinemachineVirtualCamera))]
public class FollowCameraZoomAndRotate : MonoBehaviour
{
[SerializeField] private float zoomSensitivity = 1.0f;
[SerializeField] private float minimumZoom = 5;
[SerializeField] private float maximumZoom = 20;
[SerializeField] private float zoomSpeed = 5;
[SerializeField] private float turnSensitivity = .1f;
CinemachineFramingTransposer transposer;
private void Awake()
{
CinemachineVirtualCamera cam = GetComponent<CinemachineVirtualCamera>();
transposer = cam.GetCinemachineComponent<CinemachineFramingTransposer>();
}
private float currentZoom = 10;
private float desiredZoom = 10;
private float currentRotation = 0;
private float desiredRotation = 0;
private float wheelValue;
// Called by new input system.
public void OnScrollWheelTurn(InputAction.CallbackContext context)
{
if (EventSystem.current.IsPointerOverGameObject()) return;
wheelValue = context.ReadValue<float>();
desiredZoom -= context.ReadValue<float>() * zoomSensitivity;
desiredZoom = Mathf.Clamp(desiredZoom, minimumZoom, maximumZoom);
}
private bool rotatePressed = false;
private Vector2 lastFrameMousePosition;
// called by new input system.
public void OnRotateButtonPressed(InputAction.CallbackContext context)
{
if (EventSystem.current.IsPointerOverGameObject()) return;
bool currentRotationPressed = context.ReadValueAsButton();
if (currentRotationPressed && !rotatePressed)
{
lastFrameMousePosition = Mouse.current.position.ReadValue();
}
rotatePressed = currentRotationPressed;
}
private void Update()
{
Rect screen = new Rect(0, 0, Screen.width, Screen.height);
if (!screen.Contains(Mouse.current.position.ReadValue()))
{
return;
}
currentZoom = Mathf.Lerp(currentZoom, desiredZoom, Time.deltaTime * zoomSpeed);
transposer.m_CameraDistance = currentZoom;
if (rotatePressed)
{
ManageRotation();
}
}
private void ManageRotation()
{
Vector2 currentPosition = Mouse.current.position.ReadValue();
Vector2 deltaVector2 = currentPosition - lastFrameMousePosition;
float delta = 0;
if (Mathf.Abs(deltaVector2.x) > Mathf.Abs(deltaVector2.y))
{
delta = lastFrameMousePosition.y > Screen.height / 2.0f ? -deltaVector2.x : deltaVector2.x;
}
else
{
delta = lastFrameMousePosition.x < Screen.width / 2.0f ? -deltaVector2.y : deltaVector2.y;
}
lastFrameMousePosition = currentPosition;
desiredRotation += (delta * turnSensitivity * Time.deltaTime);
currentRotation = Mathf.Lerp(currentRotation, desiredRotation, Time.deltaTime * zoomSpeed);
Vector3 eulers = transform.eulerAngles;
eulers.y = currentRotation;
transform.eulerAngles = eulers;
}
}
}
There is a lot to this, but it’s predicated on a different style of handling the input, rather than an InputHandler as we use in this course, we put a built in PlayerInput component on the core and use UnityEvents to call the methods.
Since we’re already using an InputHandler script, we’ll keep the calls in the InputHandler and set our CameraZoom script to grab the data from the InputHandler.
Let’s start with the changes to Controls, although if you don’t wish to have the zoom, you only need to add a Turn action as a Button, set to the Right Mouse. If you want the optional Zoom, then add a Zoom action of type Axis to the Controls, and bind it to the mouse Scroll.
In InputHandler, you’ll need to add the handler for Zoom. I made a pulbic Vector2 ScrollValue {get; private set;}
and the handler itself is the same as our Look handler, setting the value of ScrollValue to the context.ReadValue<float>();
Next, we need to get hold of the InputReader from our FollowCameraZoomAndRotate script.
Add a field for the input reader
private InputReader inputReader;
and in Awake, add
inputReader = GameObject.FindWithTag("Player").GetComponent<InputReader>();
Replace the OnScrollWheelTurn and OnRotateButtonPressed with these methods:
public void CheckScrollWheel()
{
if (EventSystem.current.IsPointerOverGameObject()) return;
wheelValue = inputReader.ScrollValue;
desiredZoom -= wheelValue * zoomSensitivity;
desiredZoom = Mathf.Clamp(desiredZoom, minimumZoom, maximumZoom);
}
public void CheckRotation()
{
if (EventSystem.current.IsPointerOverGameObject()) return;
bool currentRotationPressed = inputReader.CameraTurn;
if (currentRotationPressed && !rotatePressed)
{
lastFrameMousePosition = Mouse.current.position.ReadValue();
}
rotatePressed = currentRotationPressed;
}
And finally, call these two methods in Update().
Here’s the final script:
FollowCameraZoomAndRotate.cs revised
using Cinemachine;
using UnityEngine;
using UnityEngine.EventSystems;
using UnityEngine.InputSystem;
namespace TkrainDesigns.Cinematics
{
[RequireComponent(typeof(CinemachineVirtualCamera))]
public class FollowCameraZoomAndRotate : MonoBehaviour
{
[SerializeField] private float zoomSensitivity = 1.0f;
[SerializeField] private float minimumZoom = 5;
[SerializeField] private float maximumZoom = 20;
[SerializeField] private float zoomSpeed = 5;
[SerializeField] private float turnSensitivity = .1f;
CinemachineFramingTransposer transposer;
private InputReader inputReader;
private void Awake()
{
CinemachineVirtualCamera cam = GetComponent<CinemachineVirtualCamera>();
transposer = cam.GetCinemachineComponent<CinemachineFramingTransposer>();
inputReader = GameObject.FindWithTag("Player").GetComponent<InputReader>();
}
private float currentZoom = 10;
private float desiredZoom = 10;
private float currentRotation = 0;
private float desiredRotation = 0;
private float wheelValue;
public void CheckScrollWheel()
{
if (EventSystem.current.IsPointerOverGameObject()) return;
wheelValue = inputReader.ScrollValue;
desiredZoom -= wheelValue * zoomSensitivity;
desiredZoom = Mathf.Clamp(desiredZoom, minimumZoom, maximumZoom);
}
private bool rotatePressed = false;
private Vector2 lastFrameMousePosition;
public void CheckRotation()
{
if (EventSystem.current.IsPointerOverGameObject()) return;
bool currentRotationPressed = inputReader.CameraTurn;
if (currentRotationPressed && !rotatePressed)
{
lastFrameMousePosition = Mouse.current.position.ReadValue();
}
rotatePressed = currentRotationPressed;
}
private void Update()
{
CheckRotation();
CheckScrollWheel();
Rect screen = new Rect(0, 0, Screen.width, Screen.height);
if (!screen.Contains(Mouse.current.position.ReadValue()))
{
return;
}
currentZoom = Mathf.Lerp(currentZoom, desiredZoom, Time.deltaTime * zoomSpeed);
transposer.m_CameraDistance = currentZoom;
if (rotatePressed)
{
ManageRotation();
}
}
private void ManageRotation()
{
Vector2 currentPosition = Mouse.current.position.ReadValue();
Vector2 deltaVector2 = currentPosition - lastFrameMousePosition;
float delta = 0;
if (Mathf.Abs(deltaVector2.x) > Mathf.Abs(deltaVector2.y))
{
delta = lastFrameMousePosition.y > Screen.height / 2.0f ? -deltaVector2.x : deltaVector2.x;
}
else
{
delta = lastFrameMousePosition.x < Screen.width / 2.0f ? -deltaVector2.y : deltaVector2.y;
}
lastFrameMousePosition = currentPosition;
desiredRotation += (delta * turnSensitivity * Time.deltaTime);
currentRotation = Mathf.Lerp(currentRotation, desiredRotation, Time.deltaTime * zoomSpeed);
Vector3 eulers = transform.eulerAngles;
eulers.y = currentRotation;
transform.eulerAngles = eulers;
}
}
}
The final script should go on your Virtual Camera.
At the moment, this script doesn’t return to a “behind” state after a few seconds, I’m out of time for the evening.