Customized Zoom Function for the Turn Based Strategy Course Camera Controller

I wanted to share my camera controller modifications and get some feedback on the result. Here is a list to summarize what I did, and I will include the code for the class at the end of the post.

  • Added some variables to the class, including some default zoom control values. I removed the const keyword so those values could be modified in the inspector. The naming convention could be refined a bit.
  • Added a ResetDefaultZoomLevel() method that has a middle mouse button click control. I realize that as the course progresses I may need to remap that mouse control to a keyboard button.
  • HandleZoom() modifies both the Y and Z axis of the targetFollowOffset, with both being based on the CURR_ZOOM_LEVEL variable.
  • On the Virtual Camera, I set Aim > Tracked Object Offset > Y to 1.5.
  • I also set the Virtual Camera’s Lens > Vertical FOV to 45, which is slightly different from the course instructions modification but could be set to your preference.

This setup creates more of a 3rd-person view as it lower’s the camera’s height when the view is centered on a unit. I haven’t yet decided where or how to implement a method that would center the camera behind a unit upon selection. Perhaps this feature gets added later in the course? I would love to hear any feedback on my code changes or how you feel about the way the zoom feels if you try implementing it.

Here is the class’s modified code:

using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using Cinemachine;

public class CameraController : MonoBehaviour
{
	[SerializeField]
	private CinemachineVirtualCamera cinemachineVirtualCamera;
	[SerializeField]
	private float cameraMoveSpeed;
	[SerializeField]
	private float cameraRotationSpeed;
	[SerializeField]
	private float zoomSpeed = 5f; // not used in ResetDefaultZoomLevel() for snappier response
	[SerializeField]
	private int zoomIncrement = 2; // set to 1 for more granular control
	[SerializeField]
	private int defaultZoomLevel = 9;
	[SerializeField]
	private float zoomAngleFactor = 0.65f; // adjusts the slope/curve of the zoom motion. [ Y (camera height) = Z (camera distance) * zoomAngleFactor ] DO NOT set above 1.
	[SerializeField]
	private float CURR_ZOOM_LEVEL; // is set to match defaultZoomLevel in ResetDefaultZoomLevel(), which is called on Start() 
	[SerializeField]
	private int MIN_CAM_ZOOM = 3;
	[SerializeField]
	private int MAX_CAM_ZOOM = 15;

	private CinemachineTransposer cinemachineTransposer;
	private Vector3 targetFollowOffset;

	private void Start()
	{
		cinemachineTransposer = cinemachineVirtualCamera.GetCinemachineComponent<CinemachineTransposer>();
		targetFollowOffset = cinemachineTransposer.m_FollowOffset;

		ResetDefaultZoomLevel();
	}


	private void Update()
	{
		HandleMovement();
		HandleRotation();
		HandleZoom();
		if (Input.GetMouseButton(2))
		{
			ResetDefaultZoomLevel();
		}
	}

	private void HandleMovement()
	{
		Vector3 inputMoveDir = new Vector3(0, 0, 0);

		if (Input.GetKey(KeyCode.W))
		{
			inputMoveDir.z = +1f;
		}
		if (Input.GetKey(KeyCode.S))
		{
			inputMoveDir.z = -1f;
		}
		if (Input.GetKey(KeyCode.A))
		{
			inputMoveDir.x = -1f;
		}
		if (Input.GetKey(KeyCode.D))
		{
			inputMoveDir.x = +1f;
		}

		Vector3 moveVector = transform.forward * inputMoveDir.z + transform.right * inputMoveDir.x;
		transform.position += moveVector * cameraMoveSpeed * Time.deltaTime;
	}

	private void HandleRotation()
	{
		Vector3 rotationVector = new Vector3(0, 0, 0);

		if (Input.GetKey(KeyCode.Q))
		{
			rotationVector.y = -1f;
		}
		if (Input.GetKey(KeyCode.E))
		{
			rotationVector.y = +1f;
		}

		transform.eulerAngles += rotationVector * cameraRotationSpeed * Time.deltaTime;
	}

	private void HandleZoom()
	{
		if (Input.mouseScrollDelta.y > 0)
		{
			CURR_ZOOM_LEVEL -= zoomIncrement;
		}
		if (Input.mouseScrollDelta.y < 0)
		{
			CURR_ZOOM_LEVEL += zoomIncrement;
		}

		CURR_ZOOM_LEVEL = Mathf.Clamp(CURR_ZOOM_LEVEL, MIN_CAM_ZOOM, MAX_CAM_ZOOM);
		targetFollowOffset.z = -CURR_ZOOM_LEVEL;
		targetFollowOffset.y = CURR_ZOOM_LEVEL * zoomAngleFactor;
		cinemachineTransposer.m_FollowOffset = Vector3.Lerp(cinemachineTransposer.m_FollowOffset, targetFollowOffset, Time.deltaTime * zoomSpeed);
	}

	private void ResetDefaultZoomLevel()
	{
		targetFollowOffset.z = -defaultZoomLevel;
		targetFollowOffset.y = defaultZoomLevel * zoomAngleFactor;
		CURR_ZOOM_LEVEL = defaultZoomLevel;
		cinemachineTransposer.m_FollowOffset = Vector3.Lerp(cinemachineTransposer.m_FollowOffset, targetFollowOffset, Time.deltaTime);
	}
}
6 Likes

Thank you for sharing this :grin:

2 Likes

I did something similar - I wanted to raise the aim offset a bit as well when zoomed - if the camera is aimed at the controller on the ground when zoomed in close then the player character was too high up for me.

I followed the same steps as shown in the lesson to locate the aim offset in the Composer component (as opposed to the Transposer) and it works in much the same way.

Here’s the code, excluding Update, Movement and Rotation methods as they’re the same:

    [SerializeField] private CinemachineVirtualCamera cinemachineVirtualCamera;
    [SerializeField] private float moveSpeed = 10f;
    [SerializeField] private float rotateSpeed = 100f;
    [SerializeField] private float zoomAmount = 0.1f;
    [SerializeField] private float zoomSpeed = 1f;
    [SerializeField] private float minFollowYOffset = 2f;
    [SerializeField] private float maxFollowYOffset = 12f;
    [SerializeField] private float minFollowZOffset = -2.5f;
    [SerializeField] private float maxFollowZOffset = -10f;
    [SerializeField] private float minAimYOffset = 1.4f;
    [SerializeField] private float maxAimYOffset = 0f;

    private CinemachineComposer cinemachineComposer;
    private CinemachineTransposer cinemachineTransposer;
    private float zoomLevel = 0.5f;
    private Vector3 targetFollowOffset;
    private Vector3 targetAimOffset;

    private void Start()
    {
        cinemachineTransposer = cinemachineVirtualCamera.GetCinemachineComponent<CinemachineTransposer>();
        targetFollowOffset = cinemachineTransposer.m_FollowOffset;

        cinemachineComposer = cinemachineVirtualCamera.GetCinemachineComponent<CinemachineComposer>();
        targetAimOffset = cinemachineComposer.m_TrackedObjectOffset;
    }

    private void HandleZoom()
    {
        if (Input.mouseScrollDelta.y > 0)
        {
            zoomLevel -= zoomAmount;
        }

        if (Input.mouseScrollDelta.y < 0)
        {
            zoomLevel += zoomAmount;
        }
        zoomLevel = Mathf.Clamp(zoomLevel, 0f, 1f);

        targetFollowOffset.y = minFollowYOffset + (maxFollowYOffset - minFollowYOffset) * zoomLevel;
        targetFollowOffset.z = minFollowZOffset + (maxFollowZOffset - minFollowZOffset) * zoomLevel;
        targetAimOffset.y = minAimYOffset + (maxAimYOffset - minAimYOffset) * zoomLevel;

        cinemachineTransposer.m_FollowOffset = Vector3.Lerp(cinemachineTransposer.m_FollowOffset, targetFollowOffset, Time.deltaTime * zoomSpeed);
        cinemachineComposer.m_TrackedObjectOffset = Vector3.Lerp(cinemachineComposer.m_TrackedObjectOffset, targetAimOffset, Time.deltaTime * zoomSpeed);
    }
4 Likes

Privacy & Terms