Monday, January 19, 2015

Unity: Move the camera in a RTS scene with speed proportional to current zoom level

This is a solution I designed to solve issue with the camera movement that fails to adjust to the camera's current zoom level. Start by attaching a C# script to the main camera object in a scene. The following codes assumes that user expect hold down and drag the right mouse to move the camera around a scene. However, due to the zoom level, if the camera is moving at the same speed at different zoom levels when user drag the mouse, the movement will appear awkward in that when the zoom in, the user feels the camera moves too fast. On the other hand, when zoom out, the user feels the camera moves too slow. I solved the following by incorporating the field of view into the movement of the camera. That is, when zoom in, the field of view is small, and when zoom out, the field of view is large. Therefore, with the same amount of mouse drag, the camera moves slower when zoom in and moves faster when zoom out. However, from the player's point of view, it is as if the camera is moving at the same speed, and the overall effect is much better. Below is the code in the C# script attached to the main camera, which user can
  • Use A and W to zoom in and out, 
  • Use mouse scroll to move the camera up and down
  • Hold down Alt key and hold down and drag the right mouse to rotate camera up/down, left/right.
  • Hold down and drag the right mouse to move in the x and z direction in the RTS scene

public float ScrollSpeed = 10f;
public float DragSpeed = 6f;
public float RotateSpeed = 25;
public float RotateAmount = 25;

void LateUpdate()
{
  ZoomCamera(Input.GetKey(KeyCode.A), Input.GetKey(KeyCode.W));
  RotateCamera();
  MoveCamera();
}

public void ZoomCamera(bool isZoomIn, bool isZoomOut)
{
 float zoomDirection = 0f;
 if (isZoomIn) zoomDirection = 1f;
 else if (isZoomOut) zoomDirection = -1f;
 camera.fieldOfView -= zoomDirection * Time.deltaTime * ScrollSpeed;
}

private void RotateCamera()
{
 Vector3 origin = transform.eulerAngles;
 Vector3 destination = origin;

 //detect rotation amount if ALT is being held and the Right mouse button is down
 if ((Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt)) && Input.GetMouseButton(1))
 {
  destination.x -= Input.GetAxis("Mouse Y") * RotateAmount;
  destination.y += Input.GetAxis("Mouse X") * RotateAmount;
 }

 //if a change in position is detected perform the necessary update
 if (destination != origin)
 {
  transform.eulerAngles = Vector3.MoveTowards(origin, destination, Time.deltaTime * RotateSpeed);
 }
}

private void MoveCamera()
{
 float xpos = Input.mousePosition.x;
 float ypos = Input.mousePosition.y;
 Vector3 movement = new Vector3(0, 0, 0);

 float fov = camera.fieldOfView;
 bool isAltKeyDown = Input.GetKey(KeyCode.LeftAlt) || Input.GetKey(KeyCode.RightAlt);
 if (!isAltKeyDown && Input.GetMouseButton(1))
 {
  movement.x -= Input.GetAxis("Mouse X") * DragSpeed;
  movement.z -= Input.GetAxis("Mouse Y") * DragSpeed;
 }

 movement = camera.transform.TransformDirection(movement);
 movement.y = 0;
 movement.y -= ScrollSpeed * Input.GetAxis("Mouse ScrollWheel");

 Vector3 origin = transform.position;
 Vector3 destination = origin;
 destination.x += movement.x;
 destination.y += movement.y;
 destination.z += movement.z;

 if (origin != destination)
 {
  transform.position = Vector3.MoveTowards(origin, destination, Time.deltaTime * DragSpeed * fov);
 }
}

No comments:

Post a Comment