Camera with WSAD, zoom and mouse rotate X and Y axis

Hello, Im trying to implement camera that has following features:

  • WSAD to move around (this works fairly well)
  • Rotate horizontally with right mouse (this works fairly well)
  • Rotate vertically with right mouse (this is janky, kinda works but not well)
  • Zoom in with mouse scroll (kinda works but it’s janky with combination with rotation)

I believe many games have this style of camera. Any ideas how can i improve this code?

using System;
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using CodeMonkey.Utils;
using Unity.Cinemachine;

public class CameraTarget : MonoBehaviour {
	public enum Axis {
		XZ,
		XY,
	}

	[SerializeField] protected Axis axis = Axis.XZ;
	[SerializeField] protected float moveSpeed = 50f;
	[SerializeField] protected CinemachineCamera virtualCam;
	[SerializeField] protected CinemachineFollow cinemachineFollow;

	public float rotateSpeed = 180f;
	public float verticalLimit = 80f; // Limit the up/down rotation to avoid flipping
	protected float rotationY = 0f;
	protected float rotationX = 0f; // Track the current vertical rotation
	protected Vector3 followOffset;

	protected void Start() {
		followOffset = cinemachineFollow.FollowOffset;
	}

	protected void Update() {
		HandleWSADMove();
		HandleCameraZoom();
		HandleCameraRotate();
	}

	protected void HandleWSADMove() {
		float moveX = 0f;
		float moveY = 0f;

		if (Input.GetKey(KeyCode.W)) {
			moveY = +1f;
		}

		if (Input.GetKey(KeyCode.S)) {
			moveY = -1f;
		}

		if (Input.GetKey(KeyCode.A)) {
			moveX = -1f;
		}

		if (Input.GetKey(KeyCode.D)) {
			moveX = +1f;
		}

		Vector3 moveDir;

		switch (axis) {
			default:
			case Axis.XZ:
				moveDir = new Vector3(moveX, 0, moveY).normalized;
				break;
			case Axis.XY:
				moveDir = new Vector3(moveX, moveY).normalized;
				break;
		}

		if (moveX != 0 || moveY != 0) {
			// Not idle
		}

		if (axis == Axis.XZ) {
			moveDir = UtilsClass.ApplyRotationToVectorXZ(moveDir, 30f);
		}

		transform.position += ((transform.forward * moveY) + (transform.right * moveX)) * (moveSpeed * Time.deltaTime);
	}

	protected void HandleCameraRotate() {
		if (!Input.GetMouseButton(1)) {
			return;
		}

		float mouseX = Input.GetAxis("Mouse X");
		float mouseY = Input.GetAxis("Mouse Y");

		// Rotate horizontally
		transform.Rotate(0, mouseX * rotateSpeed * Time.deltaTime, 0);

		// Adjust vertical rotation
		float verticalOffsetChange = -mouseY * rotateSpeed * Time.deltaTime * 2f; // Smooth control
		float newY = Mathf.Clamp(cinemachineFollow.FollowOffset.y + verticalOffsetChange, 10f, 80f); // Prevent going below ground

		// Adjust Z dynamically to maintain distance while looking up/down
		float newZ = followOffset.z - (verticalOffsetChange * 0.5f); // Adjust Z without smoothing

		// Apply changes
		cinemachineFollow.FollowOffset = new Vector3(followOffset.x, newY, newZ);
	}
	
	protected void HandleCameraZoom() {
		if (Input.mouseScrollDelta.y == 0) {
			return;
		}

		float zoomSpeed = 5f;
		float minZoom = 10f;
		float maxZoom = 100f;

		// Calculate zoom direction based on current offset
		Vector3 zoomDirection = followOffset.normalized; 

		// Apply zoom movement in the correct direction
		followOffset += zoomDirection * Input.mouseScrollDelta.y * zoomSpeed;

		// Clamp the zoom range to prevent extreme values
		float distance = followOffset.magnitude;
		distance = Mathf.Clamp(distance, minZoom, maxZoom);

		// Apply the clamped distance while maintaining direction
		followOffset = zoomDirection * distance;

		// Update the Cinemachine FollowOffset
		cinemachineFollow.FollowOffset = followOffset;
	}
}