This script lets you pan and zoom a 2D camera, confined to the boundary of the scene. It works both with the mouse, and on touch-screen devices.
To use it:
- Add a Camera to the scene, and attach AC's Basic Camera component to it.
- Assign this Camera as the scene's default, or use a Camera: Switch Action to switch to it
- Create a new C# script named PanZoomCamera, and copy/paste the code below.
- Attach the new Pan Zoom Camera component to your Camera, and fill in its Inspector.
PanZoomCamera.cs:
using UnityEngine;
public class PanZoomCamera : MonoBehaviour
{
public float ZoomSpeedTouch = 0.02f;
public float ZoomSpeedMouse = 0.5f;
public SpriteRenderer backgroundConstraint = null;
public float zoomMin = 1;
public float zoomMax = 5.41f;
public float zoomAcceleration = 7f;
public float panAcceleration = 12f;
private Camera cam;
private float zoomSpeed;
private Vector3 lastPanPosition;
private Vector3 panOffset;
private int panFingerId;
private bool wasZoomingLastFrame;
private Vector2[] lastZoomPositions;
void Awake ()
{
cam = GetComponent<Camera> ();
}
void Start ()
{
ZoomCamera (0f, 0f, Vector2.zero);
}
void Update ()
{
if (Input.touchSupported)
{
HandleTouch ();
}
else
{
HandleMouse ();
}
transform.position = ClampPosition (transform.position);
}
void HandleTouch ()
{
float targetZoomSpeed = 0f;
switch (Input.touchCount)
{
case 1:
{
wasZoomingLastFrame = false;
Touch touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began) {
lastPanPosition = touch.position;
panFingerId = touch.fingerId;
}
else if (touch.fingerId == panFingerId && touch.phase == TouchPhase.Moved)
{
SetPanOffset (touch.position);
lastPanPosition = touch.position;
}
else
{
panOffset = Vector3.Lerp (panOffset, Vector3.zero, Time.deltaTime * panAcceleration);
}
break;
}
case 2:
{
Touch touch0 = Input.GetTouch(0);
Touch touch1 = Input.GetTouch(1);
if (touch0.phase == TouchPhase.Ended || touch1.phase == TouchPhase.Ended)
{
if (touch0.phase != TouchPhase.Ended)
{
lastPanPosition = touch0.position;
}
else if (touch1.phase != TouchPhase.Ended)
{
lastPanPosition = touch1.position;
}
wasZoomingLastFrame = false;
}
else
{
Vector2[] newPositions = new Vector2[]{Input.GetTouch(0).position, Input.GetTouch(1).position};
if (!wasZoomingLastFrame)
{
lastZoomPositions = newPositions;
wasZoomingLastFrame = true;
}
else
{
float newDistance = Vector2.Distance(newPositions[0], newPositions[1]);
float oldDistance = Vector2.Distance(lastZoomPositions[0], lastZoomPositions[1]);
float offset = newDistance - oldDistance;
targetZoomSpeed = offset;
lastZoomPositions = newPositions;
}
}
panOffset = Vector3.Lerp (panOffset, Vector3.zero, Time.deltaTime * panAcceleration);
break;
}
default:
{
panOffset = Vector3.Lerp (panOffset, Vector3.zero, Time.deltaTime * panAcceleration);
wasZoomingLastFrame = false;
break;
}
}
PanCamera ();
zoomSpeed = Mathf.Lerp (zoomSpeed, targetZoomSpeed, zoomAcceleration * Time.deltaTime);
if (lastZoomPositions != null && lastZoomPositions.Length == 2)
{
ZoomCamera(zoomSpeed, ZoomSpeedTouch, (lastZoomPositions[0] + lastZoomPositions[1]) * 0.5f);
}
}
void HandleMouse ()
{
if (Input.GetMouseButtonDown (0))
{
lastPanPosition = Input.mousePosition;
}
else if (Input.GetMouseButton (0))
{
SetPanOffset (Input.mousePosition);
lastPanPosition = Input.mousePosition;
}
else
{
panOffset = Vector3.Lerp (panOffset, Vector3.zero, Time.deltaTime * panAcceleration);
}
PanCamera ();
float scroll = Input.GetAxis ("Mouse ScrollWheel");
if (scroll != 0f)
{
zoomSpeed = scroll;
}
else
{
zoomSpeed = Mathf.Lerp (zoomSpeed, 0f, zoomAcceleration * Time.deltaTime);
}
ZoomCamera (zoomSpeed, ZoomSpeedMouse, Input.mousePosition);
}
void SetPanOffset (Vector3 newPanPosition)
{
Vector3 targetOffset = cam.ScreenToWorldPoint (new Vector3 (lastPanPosition.x, lastPanPosition.y, 10f)) - cam.ScreenToWorldPoint (new Vector3 (newPanPosition.x, newPanPosition.y, 10f));
panOffset = Vector3.Lerp (panOffset, targetOffset, Time.deltaTime * panAcceleration);
}
void PanCamera ()
{
Vector3 move = new Vector3 (panOffset.x, panOffset.y, 0f);
transform.Translate (move, Space.World);
}
Vector3 ClampPosition (Vector3 position)
{
Vector3 bottomLeftWorldPosition = cam.ViewportToWorldPoint(new Vector3(0f, 0f, cam.nearClipPlane));
Vector3 topRightWorldPosition = cam.ViewportToWorldPoint(new Vector3(1f, 1f, cam.nearClipPlane));
Vector2 bottomLeftOffset = new Vector2(transform.position.x - bottomLeftWorldPosition.x, transform.position.y - bottomLeftWorldPosition.y);
Vector2 topRightOffset = new Vector2(transform.position.x - topRightWorldPosition.x, transform.position.y - topRightWorldPosition.y);
Vector2 hLimits = new Vector2(bottomLeftOffset.x + backgroundConstraint.bounds.min.x, topRightOffset.x + backgroundConstraint.bounds.max.x);
float minX = hLimits.x;
float maxX = hLimits.y;
float scaleFactorX = (topRightWorldPosition.x - bottomLeftWorldPosition.x) / backgroundConstraint.bounds.size.x;
if (scaleFactorX > 1f)
{
minX = maxX = backgroundConstraint.bounds.center.x;
cam.orthographicSize /= scaleFactorX;
}
position.x = Mathf.Clamp (position.x, minX, maxX);
Vector2 vLimits = new Vector2(bottomLeftOffset.y + backgroundConstraint.bounds.min.y, topRightOffset.y + backgroundConstraint.bounds.max.y);
float minY = vLimits.x;
float maxY = vLimits.y;
float scaleFactorY = (topRightWorldPosition.y - bottomLeftWorldPosition.y) / backgroundConstraint.bounds.size.y;
if (scaleFactorY > 1f)
{
minY = maxY = backgroundConstraint.bounds.center.y;
cam.orthographicSize /= scaleFactorY;
}
position.y = Mathf.Clamp (position.y, minY, maxY);
return position;
}
private void ZoomCamera (float offset, float speed, Vector2 screenPosition)
{
cam.orthographicSize = Mathf.Clamp(cam.orthographicSize - (offset * speed), zoomMin, zoomMax);
if (cam.orthographicSize == zoomMin || cam.orthographicSize == zoomMax)
{
zoomSpeed = 0f;
}
if (offset > 0f)
{
Vector2 worldOffset = cam.ScreenToWorldPoint (screenPosition) - transform.position;
transform.position = Vector3.Lerp (transform.position, transform.position + (Vector3) worldOffset, 0.1f * offset * Time.deltaTime);
}
}
}