Zoom and Pan 2d Mobile Game

I have got the pinch to zoom in and out working but I am having trouble with the pan. I want to detect the bounds of a sprite “Background” and make sure the camera is clamped so that the background never leaves the field of view of the camera. Here is what I have come up with.

using UnityEngine;
using System.Collections;

public class PinchZoom2 : MonoBehaviour {

    private float orthoZoomSpeed = 0.25f;        // The rate of change of the orthographic size in orthographic mode.
    private float orthoCamSize;
    private Vector3 camPos;
    private float maxZoom;
    private float minZoom = 2;
    private float panSpeed = -0.1f;
    public static bool isPanning;        // Is the camera being panned?
   
    Vector3 bottomLeft;
    Vector3 topRight;
   
    float cameraMaxY;
    float cameraMinY;
    float cameraMaxX;
    float cameraMinX;
   
    void Start()
    { 
        isPanning = false;
        maxZoom = Camera.main.orthographicSize;
        orthoCamSize = maxZoom;
        camPos = Camera.main.transform.position;

        //set max camera bounds (assumes camera is max zoom and centered on Start)
        topRight = camera.ScreenToWorldPoint(new Vector3(camera.pixelWidth, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));
        cameraMaxX = topRight.x;
        cameraMaxY = topRight.y;
        cameraMinX = bottomLeft.x;
        cameraMinY = bottomLeft.y;
    }
   
    void Update ()
    {
        #if UNITY_EDITOR
        //click and drag
        if(Input.GetMouseButton(0)){
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);
        }
        #endif
       
       
        // One Finger Pan
        if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved){
            isPanning = true;
//            Touch touchZero = Input.GetTouch(0);
//            float x = touchZero.position.x * panSpeed;
//            float y = touchZero.position.y * panSpeed;
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);
            isPanning = false;

        }

        #if UNITY_EDITOR
        //zoom
        if((Input.GetAxis("Mouse ScrollWheel") > 0) && Camera.main.orthographicSize > minZoom ) // forward
        {
            Camera.main.orthographicSize = Camera.main.orthographicSize - orthoZoomSpeed;
        }
       
        if ((Input.GetAxis("Mouse ScrollWheel") < 0) && Camera.main.orthographicSize < maxZoom) // back           
        {
            Camera.main.orthographicSize = Camera.main.orthographicSize + orthoZoomSpeed;
        }
        #endif

        // 2 finger Zoom
        if (Input.touchCount == 2){
            // Store both touches.
            Touch touchZero = Input.GetTouch(0);
            Touch touchOne = Input.GetTouch(1);
           
            // Find the position in the previous frame of each touch.
            Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
            Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
           
            // Find the magnitude of the vector (the distance) between the touches in each frame.
            float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
            float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
           
            // Find the difference in the distances between each frame.
            float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
           
            // If the camera is orthographic...
            if (camera.isOrthoGraphic)
            {
                // ... change the orthographic size based on the change in distance between the touches.
                camera.orthographicSize += deltaMagnitudeDiff * orthoZoomSpeed;
               
                // Make sure the orthographic size never drops below zero.
                camera.orthographicSize = Mathf.Max(camera.orthographicSize, minZoom);
               
                // Make sure the orthographic size never goes above original size.
                camera.orthographicSize = Mathf.Min(camera.orthographicSize, maxZoom);
            }
        }


        // On double tap image will be set at original position and scale
        else if(Input.touchCount==1 && Input.GetTouch(0).phase == TouchPhase.Began && Input.GetTouch(0).tapCount==2)
        {
            camera.orthographicSize = orthoCamSize;
            Camera.main.transform.position = camPos;
        }

       
        //check if camera is out-of-bounds, if so, move back in-bounds
        topRight = camera.ScreenToWorldPoint(new Vector3(camera.pixelWidth, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));
       
        if(topRight.x > cameraMaxX)
        {
            transform.position = new Vector3(transform.position.x - (topRight.x - cameraMaxX), transform.position.y, transform.position.z);
        }
       
        if(topRight.y > cameraMaxY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y - (topRight.y - cameraMaxY), transform.position.z);
        }
       
        if(bottomLeft.x < cameraMinX)
        {
            transform.position = new Vector3(transform.position.x + (cameraMinX - bottomLeft.x), transform.position.y, transform.position.z);
        }
       
        if(bottomLeft.y < cameraMinY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y + (cameraMinY - bottomLeft.y), transform.position.z);
        }

        if (Input.GetKeyDown(KeyCode.Escape)) {
            Application.Quit();
        }
       

    }
}

I have got everything working in the editor but on android the panning isn’t working as intended.

#if UNITY_EDITOR
        //click and drag
        if(Input.GetMouseButton(0)){
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);
        }
        #endif
       
       
        // One Finger Pan
        if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Began){
//            Touch touchZero = Input.GetTouch(0);
//            float x = touchZero.position.x * panSpeed;
//            float y = touchZero.position.y * panSpeed;
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);

        }

On android when you touch any spot on the screen while zoomed in it jumps to that spot. How do you detect when a finger is down and has moved? I wanted to be able to tap and drag my finger to pan up, down, left, right.

I might be mistaken, but isn’t the “touch” action similar to a key press?

By this, I mean that it should have a “down”, “up” and “pressed”, to account for both a continued click and a normal fast click.

I got it work by using this

if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved){

Now my only problem is on calculating camera bounds. It calculates correctly but it does not take into account my side menu which takes up 19.53% of the width. I am calculating camera bounds with this

//set max camera bounds (assumes camera is max zoom and centered on Start)
        topRight = camera.ScreenToWorldPoint(new Vector3(camera.pixelWidth, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));
        cameraMaxX = topRight.x;
        cameraMaxY = topRight.y;
        cameraMinX = bottomLeft.x;
        cameraMinY = bottomLeft.y;

Then on Update

//check if camera is out-of-bounds, if so, move back in-bounds
        topRight = camera.ScreenToWorldPoint(new Vector3(camera.pixelWidth, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));
       
        if(topRight.x > cameraMaxX)
        {
            transform.position = new Vector3(transform.position.x - (topRight.x - cameraMaxX), transform.position.y, transform.position.z);
        }
       
        if(topRight.y > cameraMaxY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y - (topRight.y - cameraMaxY), transform.position.z);
        }
       
        if(bottomLeft.x < cameraMinX)
        {
            transform.position = new Vector3(transform.position.x + (cameraMinX - bottomLeft.x), transform.position.y, transform.position.z);
        }
       
        if(bottomLeft.y < cameraMinY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y + (cameraMinY - bottomLeft.y), transform.position.z);
        }

Do you mean that the side the menu is on (left or right, by the way?) hides a part of your map?

If so, all you need to do is to add/substract (depending on which side it is) the menu size from your minX/maxX (still depending on the side).

If your menu if top or bottom, it’s the same, but with the Y coordinates.

As a note, you might want to calculate the menu size in pixels for this, it will make the calculations a bit smoother (rather than taking the window’s size and dividing it every time).

Right side, full height of screen and no there is nothing under the side menu so when you pan there it is just a blue screen.

When I try to subtract from topright x pos the screen flashes rapidly.
Same happens when I try to subtract from cameraMaxX, or topRight.x

I think I don’t totally understand your problem, (even though I see potential other problems in your Update code), so I’ll either let someone else have a try at it, or ask for a screen shot of the problem.

Screen shot before I press play. Notice the right hand side of the screen is blue. Once I hit play the side menu gets drawn. I just need to find a way to subtract the width of the side menu from cameraMaxX bounds. Let me know if you have any more questions.

Also the side menu script is not attached to the main camera unlike the pinch to zoom script above.

What if I detect the bounds of the background sprite

GameObject backGround = GameObject.Find("Background");
        backGround.GetComponent<SpriteRenderer>();
        cameraMaxX = backGround.renderer.bounds.max.x;
        cameraMaxY = backGround.renderer.bounds.max.y;
        cameraMinX = backGround.renderer.bounds.min.x;
        cameraMinY = backGround.renderer.bounds.min.y;

then set the camera bounds to sprite bounds. I tried this but it didn’t work I think I am doing something wrong.

Never mind I got it working perfectly. Here is the final version

using UnityEngine;
using System.Collections;

public class PinchZoom2 : MonoBehaviour {

    private float orthoZoomSpeed = 0.25f;        // The rate of change of the orthographic size in orthographic mode.
    private float orthoCamSize;
    private Vector3 camPos;
    private float maxZoom;
    private float minZoom = 2;
    private float panSpeed = -0.1f;
    public float ScreenWidth;
    public float SideMenuWidth;
    public float topRightX;
    public static bool isPanning;        // Is the camera being panned?
   
    Vector3 bottomLeft;
    Vector3 topRight;
   
    float cameraMaxY;
    float cameraMinY;
    float cameraMaxX;
    float cameraMinX;
   
    void Start()
    { 
        ScreenWidth = Screen.width;
        SideMenuWidth = Screen.width * 0.1953f;
        topRightX = ScreenWidth - SideMenuWidth;
        isPanning = false;
        maxZoom = Camera.main.orthographicSize;
        orthoCamSize = maxZoom;
        camPos = Camera.main.transform.position;

        //set max camera bounds (assumes camera is max zoom and centered on Start)
        topRight = camera.ScreenToWorldPoint(new Vector3(topRightX, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));
        cameraMaxX = topRight.x;
        cameraMaxY = topRight.y;
        cameraMinX = bottomLeft.x;
        cameraMinY = bottomLeft.y;
    }
   
    void Update ()
    {
        #if UNITY_EDITOR
        //click and drag
        if(Input.GetMouseButton(0)){
            isPanning = true;
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);
            isPanning = false;
        }
        #endif
       
       
        // One Finger Pan
        if (Input.touchCount == 1 && Input.GetTouch(0).phase == TouchPhase.Moved){
            isPanning = true;
//            Touch touchZero = Input.GetTouch(0);
//            float x = touchZero.position.x * panSpeed;
//            float y = touchZero.position.y * panSpeed;
            float x = Input.GetAxis("Mouse X") * panSpeed;
            float y = Input.GetAxis("Mouse Y") * panSpeed;
            transform.Translate(x,y,0);
            isPanning = false;

        }

        #if UNITY_EDITOR
        //zoom
        if((Input.GetAxis("Mouse ScrollWheel") > 0) && Camera.main.orthographicSize > minZoom ) // forward
        {
            Camera.main.orthographicSize = Camera.main.orthographicSize - orthoZoomSpeed;
        }
       
        if ((Input.GetAxis("Mouse ScrollWheel") < 0) && Camera.main.orthographicSize < maxZoom) // back           
        {
            Camera.main.orthographicSize = Camera.main.orthographicSize + orthoZoomSpeed;
        }
        #endif

        // 2 finger Zoom
        if (Input.touchCount == 2){
            // Store both touches.
            Touch touchZero = Input.GetTouch(0);
            Touch touchOne = Input.GetTouch(1);
           
            // Find the position in the previous frame of each touch.
            Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
            Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
           
            // Find the magnitude of the vector (the distance) between the touches in each frame.
            float prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
            float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
           
            // Find the difference in the distances between each frame.
            float deltaMagnitudeDiff = prevTouchDeltaMag - touchDeltaMag;
           
            // If the camera is orthographic...
            if (camera.isOrthoGraphic)
            {
                // ... change the orthographic size based on the change in distance between the touches.
                camera.orthographicSize += deltaMagnitudeDiff * orthoZoomSpeed;
               
                // Make sure the orthographic size never drops below zero.
                camera.orthographicSize = Mathf.Max(camera.orthographicSize, minZoom);
               
                // Make sure the orthographic size never goes above original size.
                camera.orthographicSize = Mathf.Min(camera.orthographicSize, maxZoom);
            }
        }


        // On double tap image will be set at original position and scale
        else if(Input.touchCount==1 && Input.GetTouch(0).phase == TouchPhase.Began && Input.GetTouch(0).tapCount==2)
        {
            camera.orthographicSize = orthoCamSize;
            Camera.main.transform.position = camPos;
        }

       
        //check if camera is out-of-bounds, if so, move back in-bounds
        topRight = camera.ScreenToWorldPoint(new Vector3(topRightX, camera.pixelHeight, -transform.position.z));
        bottomLeft = camera.ScreenToWorldPoint(new Vector3(0,0,-transform.position.z));

        if(topRight.x > cameraMaxX)
        {
            transform.position = new Vector3(transform.position.x - (topRight.x - cameraMaxX), transform.position.y, transform.position.z);
        }
       
        if(topRight.y > cameraMaxY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y - (topRight.y - cameraMaxY), transform.position.z);
        }
       
        if(bottomLeft.x < cameraMinX)
        {
            transform.position = new Vector3(transform.position.x + (cameraMinX - bottomLeft.x), transform.position.y, transform.position.z);
        }
       
        if(bottomLeft.y < cameraMinY)
        {
            transform.position = new Vector3(transform.position.x, transform.position.y + (cameraMinY - bottomLeft.y), transform.position.z);
        }


        // If back button press andriod
        if (Input.GetKeyDown(KeyCode.Escape)) {
            Application.Quit();
        }

    }
}

There is still a small flaw, in your update.

You should not create a complete Vector3 in each IF, but only modify the relevant value (‘.x’ or ‘.y’).
It doesn’t make much difference, but when both X and Y are out of bounds, you will take 2 frames to correct it instead of 1.

Apart from this, it’s nice that you managed to solve it.