Unity Tutorial - C# RTS or City Builder Camera Controller Script


This is a tutorial on how to build a camera controller for an RTS/City Builder style game. I've seen a lot of other tutorials online on how to make this, but none seem to handle zoom in the same way as the RTS & City Building games I play, opting to just move the camera up and down instead so I created my own.

The entirety or elements of this tutorial may even be able to be used with other game types if you find it suitable. The screenshots below are from Unity 2019.3 & I use Visual Studio 2019 to build my scripts, but there is nothing in here specific to these versions and so older versions and different Code Editors & IDEs should work just fine as well.

First we download (if you haven't already) & create a new 3D project within Unity, there are many tutorials on this so I shan't cover it again, but here's a good one from Unity themselves: Unity Tutorial Link. The layout of creating a new project in the Unity Editor may have changed slightly since this tutorial was written, but not by much, alternatively you can use the Unity Hub to help control Unity versions, manage existing & setup new projects.

Open your new project and you should see something similar to the below:

Unity New Project

Next we create a sphere object (my preferred method of doing so is right clicking in the hierarchy > 3D Object > Sphere), and rename it to "Camera_Target". We also need to remove the Sphere Collider that is automatically added. This is done by clicking the gear icon (Pre 2019.3) or the 3 dot icon (2019.3 and later) in the sphere collider component and then "Remove Component"

Removal of the sphere collider component

Drag the Main Camera on to the Camera_Target to make the camera a child of it.

Removal of the sphere collider component

Set the camera's position to your preference (this is a relative position to it's parent object, the Camera_Target). My camera is at: 0, 10, -10 (X, Y, Z) if you wish to set yours the same. I have also set the rotation of the camera to 45 (X, pitch), 0 (Y, yaw), 0 (Z, roll) so that it faces the camera_target.

Setting Camera Position & Rotation

Select the Camera_Target, click "Add Component" at the bottom of the inspector, from the menu that appears, choose new script and give it a name of: "CameraController".

Adding Script to Camera Target
Naming the Script

Staying in the inspector, click the gear icon (Pre 2019.3) or the 3 dot icon (2019.3 and later) next to the script component and choose edit script, this will then open the script in your editor as per your Unity settings.


Opening the script for editing

In the script we need to create some variables that will be used by our camera controller.

C#


using UnityEngine;


public class CameraController : MonoBehaviour

{

// Public Variables

// How quickly the camera moves

public float panSpeed = 20f;

// How quickly the camera rotates

public float rotSpeed = 10f;

// How quickly the camera zooms

public float zoomSpeed = 50f;

// The minimum distance of the mouse cursor from the screen edge required to pan the camera

public float borderWidth = 10f;

// Boolean to control if moving the mouse within the borderWidth distance will pan the camera

public bool edgeScrolling = true;

// A placeholder for a reference to the camera in the scene

public Camera cam;


//Private Variables

// Minimum distance from the camera to the camera target

private float zoomMin = 11.0f;

// Maximum distance from the camera to the camera target

private float zoomMax = 49.0f;

// Floats to hold reference to the mouse position, no values to be assigned yet

private float mouseX, mouseY;

Next we set the value of the cam variable to be the camera in the scene tagged with "MainCamera", in the Start() method supplied by MonoBehaviour.

C#


void Start()

{

// On start, get a reference to the Main Camera

cam = Camera.main;

}

And next we need to create method to control our Movement, Rotation & Zooming.

First we'll create the Movement method:

C#


void Movement()

{

// Local variable to hold the camera target's position during each frame

Vector3 pos = transform.position;

// Local variable to reference the direction the camera is facing (Which is driven by the Camera target's rotation)

Vector3 forward = transform.forward;

// Ensure the camera target doesn't move up and down

forward.y = 0;

// Normalize the X, Y & Z properties of the forward vector to ensure they are between 0 & 1

forward.Normalize();


// Local variable to reference the direction the camera is facing + 90 clockwise degrees (Which is driven by the Camera target's rotation)

Vector3 right = transform.right;

// Ensure the camera target doesn't move up and down

right.y = 0;

// Normalize the X, Y & Z properties of the right vector to ensure they are between 0 & 1

right.Normalize();


// Move the camera (camera_target) Forward relative to current rotation if "W" is pressed or if the mouse moves within the borderWidth distance from the top edge of the screen

if (Input.GetKey("w") || edgeScrolling == true && Input.mousePosition.y >= Screen.height - borderWidth)

{

pos += forward * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Backward relative to current rotation if "S" is pressed or if the mouse moves within the borderWidth distance from the bottom edge of the screen

if (Input.GetKey("s") || edgeScrolling == true && Input.mousePosition.y <= borderWidth)

{

pos -= forward * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Right relative to current rotation if "D" is pressed or if the mouse moves within the borderWidth distance from the right edge of the screen

if (Input.GetKey("d") || edgeScrolling == true && Input.mousePosition.x >= Screen.width - borderWidth)

{

pos += right * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Left relative to current rotation if "A" is pressed or if the mouse moves within the borderWidth distance from the left edge of the screen

if (Input.GetKey("a") || edgeScrolling == true && Input.mousePosition.x <= borderWidth)

{

pos -= right * panSpeed * Time.deltaTime;

}


// Setting the camera target's position to the modified pos variable

transform.position = pos;

}

Next we create the Rotation method.

C#


void Rotation()

{

// If Mouse Button 1 is pressed, (the secondary (usually right) mouse button)

if (Input.GetMouseButton(1))

{

// Our mouseX variable gets set to the X position of the mouse multiplied by the rotation speed added to it.

mouseX += Input.GetAxis("Mouse X") * rotSpeed;

// Our mouseX variable gets set to the Y position of the mouse multiplied by the rotation speed added to it.

mouseY -= Input.GetAxis("Mouse Y") * rotSpeed;

// Clamp the minimum and maximum angle of how far the camera can look up and down.

mouseY = Mathf.Clamp(mouseY, -30, 45);

// Set the rotation of the camera target along the X axis (pitch) to mouseY (up & down) & Y axis (yaw) to mouseX (left & right), the Z axis (roll) is always set to 0 as we do not want the camera to roll.

transform.rotation = Quaternion.Euler(mouseY, mouseX, 0);

}

}

And the last method is Zoom.

C#


void Zoom()

{

// Local variable to temporarily store our camera's position

Vector3 camPos = cam.transform.position;

// Local variable to store the distance of the camera from the camera_target

float distance = Vector3.Distance(transform.position, cam.transform.position);


// When we scroll our mouse wheel up, zoom in if the camera is not within the minimum distance (set by our zoomMin variable)

if (Input.GetAxis("Mouse ScrollWheel") > 0f && distance > zoomMin)

{

camPos += cam.transform.forward * zoomSpeed * Time.deltaTime;

}


// When we scroll our mouse wheel down, zoom out if the camera is not outside of the maximum distance (set by our zoomMax variable)

if (Input.GetAxis("Mouse ScrollWheel") < 0f && distance < zoomMax)

{

camPos -= cam.transform.forward * zoomSpeed * Time.deltaTime;

}


// Set the camera's position to the position of the temporary variable

cam.transform.position = camPos;

}

// End of file

}

And finally we call all 3 methods in Update, personally I put the Update method between the Start and Movement methods.

C#


void Update()

{

Movement();

Rotation();

Zoom();

}

Don't forget to save your script, switch to Unity and you're now ready to hit play and test!

Here's the code all together if you wish to just copy and paste it.

C#


using UnityEngine;


public class CameraController : MonoBehaviour

{

// Public Variables

// How quickly the camera moves

public float panSpeed = 20f;

// How quickly the camera rotates

public float rotSpeed = 10f;

// How quickly the camera zooms

public float zoomSpeed = 50f;

// The minimum distance of the mouse cursor from the screen edge required to pan the camera

public float borderWidth = 10f;

// Boolean to control if moving the mouse within the borderWidth distance will pan the camera

public bool edgeScrolling = true;

// A placeholder for a reference to the camera in the scene

public Camera cam;


//Private Variables

// Minimum distance from the camera to the camera target

private float zoomMin = 11.0f;

// Maximum distance from the camera to the camera target

private float zoomMax = 49.0f;

// Floats to hold reference to the mouse position, no values to be assigned yet

private float mouseX, mouseY;


void Start()

{

// On start, get a reference to the Main Camera

cam = Camera.main;

}


void Update()

{

Movement();

Rotation();

Zoom();

}


void Movement()

{

// Local variable to hold the camera target's position during each frame

Vector3 pos = transform.position;

// Local variable to reference the direction the camera is facing (Which is driven by the Camera target's rotation)

Vector3 forward = transform.forward;

// Ensure the camera target doesn't move up and down

forward.y = 0;

// Normalize the X, Y & Z properties of the forward vector to ensure they are between 0 & 1

forward.Normalize();


// Local variable to reference the direction the camera is facing + 90 clockwise degrees (Which is driven by the Camera target's rotation)

Vector3 right = transform.right;

// Ensure the camera target doesn't move up and down

right.y = 0;

// Normalize the X, Y & Z properties of the right vector to ensure they are between 0 & 1

right.Normalize();


// Move the camera (camera_target) Forward relative to current rotation if "W" is pressed or if the mouse moves within the borderWidth distance from the top edge of the screen

if (Input.GetKey("w") || edgeScrolling == true && Input.mousePosition.y >= Screen.height - borderWidth)

{

pos += forward * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Backward relative to current rotation if "S" is pressed or if the mouse moves within the borderWidth distance from the bottom edge of the screen

if (Input.GetKey("s") || edgeScrolling == true && Input.mousePosition.y <= borderWidth)

{

pos -= forward * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Right relative to current rotation if "D" is pressed or if the mouse moves within the borderWidth distance from the right edge of the screen

if (Input.GetKey("d") || edgeScrolling == true && Input.mousePosition.x >= Screen.width - borderWidth)

{

pos += right * panSpeed * Time.deltaTime;

}


// Move the camera (camera_target) Left relative to current rotation if "A" is pressed or if the mouse moves within the borderWidth distance from the left edge of the screen

if (Input.GetKey("a") || edgeScrolling == true && Input.mousePosition.x <= borderWidth)

{

pos -= right * panSpeed * Time.deltaTime;

}


// Setting the camera target's position to the modified pos variable

transform.position = pos;

}


void Rotation()

{

// If Mouse Button 1 is pressed, (the secondary (usually right) mouse button)

if (Input.GetMouseButton(1))

{

// Our mouseX variable gets set to the X position of the mouse multiplied by the rotation speed added to it.

mouseX += Input.GetAxis("Mouse X") * rotSpeed;

// Our mouseX variable gets set to the Y position of the mouse multiplied by the rotation speed added to it.

mouseY -= Input.GetAxis("Mouse Y") * rotSpeed;

// Clamp the minimum and maximum angle of how far the camera can look up and down.

mouseY = Mathf.Clamp(mouseY, -30, 45);

// Set the rotation of the camera target along the X axis (pitch) to mouseY (up & down) & Y axis (yaw) to mouseX (left & right), the Z axis (roll) is always set to 0 as we do not want the camera to roll.

transform.rotation = Quaternion.Euler(mouseY, mouseX, 0);

}

}


void Zoom()

{

// Local variable to temporarily store our camera's position

Vector3 camPos = cam.transform.position;

// Local variable to store the distance of the camera from the camera_target

float distance = Vector3.Distance(transform.position, cam.transform.position);


// When we scroll our mouse wheel up, zoom in if the camera is not within the minimum distance (set by our zoomMin variable)

if (Input.GetAxis("Mouse ScrollWheel") > 0f && distance > zoomMin)

{

camPos += cam.transform.forward * zoomSpeed * Time.deltaTime;

}


// When we scroll our mouse wheel down, zoom out if the camera is not outside of the maximum distance (set by our zoomMax variable)

if (Input.GetAxis("Mouse ScrollWheel") < 0f && distance < zoomMax)

{

camPos -= cam.transform.forward * zoomSpeed * Time.deltaTime;

}


// Set the camera's position to the position of the temporary variable

cam.transform.position = camPos;

}

// End of file

}