Forum rules - please read before posting.

Pinch Zoom and Panning on Mobiles

13

Comments

  • Replace the script's ProcessClickUp function with the following:

    void ProcessClickUp(Vector2 mousePosition)
    {
        if (KickStarter.playerMenus.IsMouseOverMenu()) return;
        if (!KickStarter.stateHandler.IsInGameplay ()) return;
    
        Hotspot hotspot = KickStarter.playerInteraction.GetActiveHotspot();
        if (hotspot)
        {
            if (InvInstance.IsValid(KickStarter.runtimeInventory.SelectedInstance))
            {
                hotspot.RunInventoryInteraction(KickStarter.runtimeInventory.SelectedInstance);
            }
            hotspot.RunUseInteraction();
            return;
        }
    
        if (KickStarter.playerInteraction.GetHotspotMovingTo ())
        {
            KickStarter.playerInteraction.StopMovingToHotspot ();
        }
    
        KickStarter.playerMovement.ProcessPointAndClick(mousePosition, false);
    }
    
  • This solves the issue, thank you!

  • Would it be possible to implement acceleration at the beginning and declaration at the end of the pinch zoom, so the zooming would feel a bit smoother?

  • Thank you. Unfortunately, with this version, the camera again tends to shake and fly away completely into a black screen when moving around, zooming, and panning. Zooming in now seems to trigger the character to walk to the point of touch. Panning now randomly stops working when entering a new scene, but when I zoom in, it starts working again.

    I also noticed that with the previous version of the script (and this one as well), when I touched the screen with one finger and kept holding it in place and afterward touched the screen with another finger, the camera jumped. The pinch zoom probably causes this? Could this behavior be repaired by setting that once I hold the finger on the screen for a certain small amount of time, all other input is ignored until the finger is released? (set so that the pinch zoom would still work if the two fingers touched the screen in approximately the same moment)

  • Thanks Chris, it is a bit better now, but the problem still persists in some instances. When i zoom in/out and let the acceleration/decelaration finish the zooming movement, the camera sometimes goes black again. Especially when i hold one finger on the screen and slide another finger over it (or when i slide one finger over, keep holding, and then tap the screen with another one).

    Wouldnt it really be better to just completely turn off any other tocuhcreen input untill the first tap is released?
  • edited March 20

    I think this is a case of trouble caused by trying to extend AC's drag camera with additional features, while also looking to add yet more once all were working together.

    IMO, it's best to start over with a fresh camera script - and focus purely on getting the input behaviour exactly right. Once it works as intended, it can then be added to with additional features (following the Player etc).

    Duplicate your scene, remove the SuperScript, and GameCamera2D Drag, and instead add a new Camera to the scene. To this, add AC's Basic Camera component, as well as the following. Then assign it in the Scene Manager as the default:

    using UnityEngine;
    
    public class New : 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);
            }
        }
    
    }
    

    (Code adapted from here.)

    This works decently in my own testing, but you may want to tweak it further to get your exact intented behaviour.

    Once you have a script that has the exact motion you are looking for, let me know and I can see about re-introducing the other features related to Player movement etc.

  • Just to be sure I am doing the right thing - I should replace the current camera with GameCamera2D in the Scene Manager, is that right? (then added the new script to it).

    Or should I create an Empty and add components: Camera, Basic Camera, and the new script?

  • I am using the second option now.

    When I Zoom in/out and keep one of my fingers on the screen, the camera jumps slightly.
    The orthographic size does not fit the screen initially, but once I zoom in, it fixes itself.

    Otherwise, the zoom and panning seem to work nicely!

  • Let's be sure it's perfect before moving on.

    I've tweaked the script above - is it better now?

  • Still does it. After zooming in/out, when i remove just one finger from the screen, the camera jumps slightly elsewhere.
  • Now it works flawlessly! :-)

  • The link doesn't work.

  • The site can be intermittent. Try in a different browser.

  • Link works now :-)

    The navigation now works, as well as item collecting. There are a few issues tho:

    1) Zooming now sometimes triggers Character movement.
    2) When panning, the camera should stay on the spot I dragged it to and not focus back on the character once the tap is released, as it is doing now. It should refocus on the character only after I tap on the Navmesh or a hotspot (see also 4).
    3) Moving items from inventory now also pans the camera.

    4) Could we modify character movement so it only walks to a spot when the tap is on a navmesh or hotspot? So f.e. when I click the sky, the character does not move at all (now it moves to the closest point on navmesh).

    *5) While using the SuperScript, we modified PlayeMovement script by adding a public void ProcessPointAndClick. I assume it should stay there, right?

  • Try this:
    https://paste.ofcode.org/3a577tTRcGQQWv8LRdb8EqQ

    Could we modify character movement so it only walks to a spot when the tap is on a navmesh or hotspot? So f.e. when I click the sky, the character does not move at all (now it moves to the closest point on navmesh).

    Set your Movement method to Point And Click, then under "Movement" settings set NavMesh search direction to Straight Down From Cursor, NavMesh search % to 0, and then Movement method back to None (all in that order).

    While using the SuperScript, we modified PlayeMovement script by adding a public void ProcessPointAndClick. I assume it should stay there, right?

    Yes - though the latest release makes this change as well.

  • Thank you, all seems to be working fine! :-)

    There is only one slight issue - I have a scene that is bigger than the screen size, both vertically and horizontally, and the character starts at the bottom left corner of it. At the beginning of the scene, the camera should be focused on the character - so basically placed in the lower-most bottom-most corner. But now it is above it, more upwards for some reason. I have a different scene that is larger than the screen size horizontally only, and the character starts on the left, and it works fine.

    Could we add an option for locking the camera in the X and Y axes (separately), please?

    And I assume the Orthographic size is set to fit automatically, right?

Sign In or Register to comment.

Howdy, Stranger!

It looks like you're new here. If you want to get involved, click one of these buttons!

Welcome to the official forum for Adventure Creator.