Forum rules - please read before posting.

Pinch Zoom and Panning on Mobiles

I am trying to implement pinch zoom and panning on mobile phones, but cannot find any instructions whatsoever on how to do it when using AC.

I tried to follow this tutorial, but it does not work.
I assume some settings in AC are overriding it?

Can someone please help?

«134

Comments

  • Welcome to the community, @Dev_Martas.

    From the looks of it, the script involved works by controlling the scene's Camera.main, or Main Camera. In an AC scene, this is AC's MainCamera, which is typically controlled instead with GameCameras acting as reference.

    It's similar to Cinemachine, and its Brain / Virtual Camera system, if you're familiar with it.

    If you have the Default Camera field assigned in the Scene Manager, or have set a camera with the Camera: Switch Action, temporarily unset this and try again - does the MainCamera then allow for manual control through script?

    You may want to adapt the script to instead have it control the current AC GameCamera (which you can get with AC.KickStarter.mainCamera.attachedCamera, but you may need to do so in LateUpdate or rely on the Basic Camera component instead of a GameCamera. Try the above first, however, and we'll take things from there.

    If the MainCamera still can't be controlled, share the exact script you're using and I'll try to spot what's wrong.

  • Dear @ChrisIceBox,

    I did unset the AC camera in the Scene Manager and added the script as a component to the Main Camera and it works!

    However, now I'm afraid that not using the AC camera will cause problems. What would be the next step, please? :-)

  • If you want to rely entirely on your custom camera control, you should be fine as you are - you'll only need to make changes if you want to also make use of AC's camera system at the same time.

    Assuming so, the next step will be to adapt your script to instead control your active GameCamera, instead of the AC MainCamera.

    While the MainCamera is the one that performs runtime rendering, it'll use the position / rotation / FOV etc values of whichever GameCamera it's currently attached to - so its the GameCamera you'll want to affect through script.

    You can do this by first adding a public Camera variable at the top of your script, which you can then set in its Inspector:

    public AC.GameCamera gameCamera;
    

    Then, replace each instance of Camera.main with gameCamera.Camera.

    Technically, this will then cause the GameCamera's Camera to be affected - but you may need more work depending on what exactly it is you're controlling, because the GameCamera may also be trying to update its position, FOV etc values depending on its type and Inspector values.

    After this step, check to see what's not working - and then share details, along with the script, and screenshots of the GameCamera Inspector it's referencing, and I can look into it further.

  • edited January 14

    Should this modified script be attached to the Main Camera and in the newly created public gameCamera field, should I select the camera crated in AC Game Editor (NavCam)?

    I did modify the script, but I am unable to drag to- or select any camera in the Inspector of the "public AC.GameCamera gameCamera;". There is nothing.

    public class PanZoom : MonoBehaviour
    {
        Vector3 touchStart;
        public AC.GameCamera gameCamera;
        public float zoomOutMin = 1;
        public float zoomOutMax = 5.41f;
    
        // Update is called once per frame
        void Update()
        {
            if (Input.GetMouseButtonDown(0))
            {
                touchStart = gameCamera.Camera.ScreenToWorldPoint(Input.mousePosition);
            }
            if (Input.touchCount == 2)
            {
                Touch touchZero = Input.GetTouch(0);
                Touch touchOne = Input.GetTouch(1);
    
                Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
                Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
    
                float prevMagnitude = (touchZeroPrevPos - touchOnePrevPos).magnitude;
                float currentMagnitude = (touchZero.position - touchOne.position).magnitude;
    
                float difference = currentMagnitude - prevMagnitude;
    
                zoom(difference * 0.01f);
            }
    
            zoom(Input.GetAxis("Mouse ScrollWheel"));
        }
    
        void zoom(float increment)
        {
            gameCamera.Camera.orthographicSize = Mathf.Clamp(gameCamera.Camera.orthographicSize - increment, zoomOutMin, zoomOutMax);
        }
    }
    
  • Attach this modified script to the Camera you want to affect, rather than the MainCamera, as the MainCamera isn't directly affected by it.

    What kind of GameCamera are you using, exactly? If you replace AC.GameCamera with AC._Camera, it will work for all types.

  • Thank you. The Zoom is working fine now! However, the panning stopped working for some reason.

    In AC, the movement of my camera is set to be constrained by the background image of the scene. The Player is set to be the Target of the camera movement. Can some of these settings interfere with the panning?

    I would like to be able to pan within the limits of the background image when the camera is zoomed in. And I would like the camera to follow/focus on the player when it performs some action only. But when standing idle, I'd like to be able to zoom in and scroll around the scene to inspect details.

    Is there any way to deal with all this?

  • If your camera is constrained by a background, and uses the Player as its Target, is that to say you're using a GameCamera 2D?

    A camera with such settings will be under its own positional control - it'll be positioning itself automatically to show the Player on-screen.

    If you don't need the camera to follow the player outside of certain actions, switch instead to a "GameCamera 2D Drag" camera type, available in the Scene Manager's "Scene prefabs" panel.

    This camera automatically provides panning features, as well as background constraint options. Its orthographic size is not controlled, however, allowing it to be set through custom script without interference.

  • edited January 22

    The Drag Camera works and it also seems to work nicely with the Zoom script, thank you! But it causes another issue - I started a separate thread as it is not related to this topic specifically https://www.adventurecreator.org/forum/discussion/14539/drag-camera-2d-positioning-issue#latest.

    Would it also be possible to have the non-drag GameCamera 2D set and still use panning to a certain level? Meaning you could scroll around the scene with a mouse button/finger down but once you release it, the camera would bounce back to refocus on the player?

  • Through script, you can toggle the GameCamera 2D's "Lock" checkboxes, which will prevent AC from taking control over it - but this'll also mean it'll no longer be bound by the constraint settings.

    Rather than having your script control the Transform, and compete with the GameCamera, what you could try is to instead have the script modify the camera's Offset values. This would allow the camera to remain bound by the constraints at all times, and you could lerp them back to their original values upon releasing input.

    You can modify the camera's offset via its afterOffset variable.

    The other approach would be to have two separate cameras - one that's a regular GameCamera2D that follows the player, another that's a GameCamera2DDrag with your attached script, and then switch between them when custom input is detected/lost. You can switch between GameCameras with the SetGameCamera function:

    AC.KickStarter.mainCamera.SetGameCamera (myNewCamera);
    
  • The other approach would be to have two separate cameras - one that's a regular GameCamera2D that follows the player, another that's a GameCamera2DDrag with your attached script, and then switch between them when custom input is detected/lost. You can switch between GameCameras with the SetGameCamera function:

    I did some reading and tried to implement the two cameras switching - the default one being the GameCamera2D and GameCamera2DDrag that would be triggered by mouse/finger dragging over the scene. Default GameCamera2D would be triggered back again by a simple click/tap without any drag detected.

    I tried to recycle the script you gave me at the beginning of this topic and modify it using AI to switch between the cameras. I then put the script into empty object on the scene. But it is not working. I tried to add some Log lines to see how far it works. It seemed to work all the way to the "drag", but it seems the "NOdrag" was never detected. Please help me tweak this. Here is the script:

    using UnityEngine;
    using AC;
    
    public class ClickUpToMove : MonoBehaviour
    {
        public _Camera defaultCamera; // Define the default camera
        public _Camera dragCamera;    // Define the draggable camera
    
        Vector3 startPosition;
        bool isDragging;
    
        private void Start()
        {
            KickStarter.playerInput.InputGetMouseButtonDownDelegate = MyGetMouseButtonDown;
            KickStarter.playerInput.InputGetMouseButtonDelegate = MyGetMouseButton;
        }
    
        private bool MyGetMouseButtonDown(int button)
        {
            if (button == 0 && Input.GetMouseButtonDown(0))
            {
                startPosition = Input.mousePosition;
                return false;
            }
            return Input.GetMouseButtonUp(button);
        }
    
        private bool MyGetMouseButton(int button)
        {
            if (button == 0 && Input.GetMouseButton(0) && Input.mousePosition != startPosition)
            {
                // Check if there is movement
                if (Input.mousePosition != startPosition)
                {
                    // Set the dragging flag to true
                    isDragging = true;
                    Debug.Log("drag");
                }
                else if (button == 0 && Input.GetMouseButtonDown(0))
                {
                    // Set the dragging flag to false
                    isDragging = false;
                    Debug.Log("NOdrag");
                }
                return true;
            }
            return false;
        }
    
        private void Update()
        {
            // Check if dragging
            if (isDragging)
            {
                // Switch to the draggable camera
                SwitchCamera(dragCamera);
            }
            else if (!isDragging)
            {
                // Switch to the default camera
                SwitchCamera(defaultCamera);
            }
        }
    
        private void SwitchCamera(_Camera newCamera)
        {
            KickStarter.mainCamera.SetGameCamera(newCamera);
        }
    }
    
  • It doesn't get called because the code block will only run if Input.mousePosition != startPosition is true, in which case the first sub-block will run.

    You can replace:

    if (button == 0 && Input.GetMouseButton(0) && Input.mousePosition != startPosition)
    

    with:

    if (button == 0 && Input.GetMouseButton(0))
    

    but coupled with the input reading in the script you're modifying, it's likely best to avoid switching camera after all.

    Either way, it's cleaner to use a separate script for the separate behaviour. Try this: it's a separate script that relies on a single camera - just moving its own position back to the Player when not being dragged.

    using UnityEngine;
    using AC;
    
    public class ResetDragCamera : MonoBehaviour
    {
        public GameCamera2DDrag dragCamera;
        public Vector2 playerOffset = new Vector3 (0f, 2f);
        [Range (0f, 1f)] public float moveSpeed = 0.8f;
    
        void Update ()
        {
            if (KickStarter.playerInput.GetDragState () != DragState._Camera)
            {
                Vector3 targetPosition = (Vector2) KickStarter.player.transform.position + playerOffset;
                float lerpSpeed = (1f - Mathf.Pow (1f - Mathf.Clamp01 (moveSpeed), Time.deltaTime));
                Vector3 position = Vector3.Lerp (dragCamera.GetPosition (), targetPosition, lerpSpeed);
                dragCamera.SetPosition (position);
            }
        }
    
    }
    
  • I tried to fix the camera switching script, but it seemed to have caused more issues and the standard camera didn't follow the player anyway. Your suggestion of a separate script works and is more elegant :-)

    Unfortunately, the way this script works, I cannot zoom in on a specific area - I always have to zoom in on the player and then move the camera around.

    Would it be possible to be able to drag the camera first and then be able to zoom on that area? Or zoom on a corner of a scene with the zoom centering on that specific corner and not in the middle of the screen? (I tried to set that before, but failed as it seems to somehow fight with the constraint of movement by scene background). I would ideally love to be able to do both.
    And for both cases, the camera would reset to follow to player automatically only after you click on the NavMesh or interactive hotspot.

  • The script snaps back to the Player, but you could feasibly have it snap to the nearest other camera. I'm not 100% clear on your meaning - is that what you're geting at?

    And for both cases, the camera would reset to follow to player automatically only after you click on the NavMesh or interactive hotspot.

    Sounds best to tackle this afterwards.

  • The camera should by default follow the player. But at any time, you can drag it - then it will stop following the player and stay where you leave it. It will reset back to default and follow the player again only after you click/tap somewhere on the screen (either to send the player to position or interact with an object).

    In other words, dragging across the screen would activate the drag camera, and tap/click would activate the basic camera that automatically focuses on the player.

  • This should do it, when combined with the first ClickUpToMove script:

    using UnityEngine;
    using AC;
    
    public class ResetDragCamera : MonoBehaviour
    {
    
        public GameCamera2DDrag dragCamera;
        public Vector2 playerOffset = new Vector3 (0f, 2f);
        [Range (0f, 1f)] public float followPlayerSpeed = 0.8f;
        private bool followPlayer = true;
    
        void OnEnable ()
        {
            EventManager.OnHotspotInteract += OnHotspotInteract;
            EventManager.OnCharacterSetPath += OnCharacterSetPath;
        }
    
        void OnDisable ()
        {
            EventManager.OnHotspotInteract -= OnHotspotInteract;
            EventManager.OnCharacterSetPath -= OnCharacterSetPath;
        }
    
        void OnHotspotInteract (Hotspot hotspot, AC.Button button)
        {
            followPlayer = true;
        }
    
        void OnCharacterSetPath (Char character, Paths path)
        {
            if (character.IsActivePlayer ())
            {
                followPlayer = true;
            }
        }
    
        void Update ()
        {
            if (KickStarter.playerInput.GetDragState () == DragState._Camera)
            {
                followPlayer = false;
            }
            else if (followPlayer)
            {
                Vector3 targetPosition = (Vector2) KickStarter.player.transform.position + playerOffset;
                float lerpSpeed = (1f - Mathf.Pow (1f - Mathf.Clamp01 (followPlayerSpeed), Time.deltaTime));
                Vector3 position = Vector3.Lerp (dragCamera.GetPosition (), targetPosition, lerpSpeed);
                dragCamera.SetPosition (position);
            }
        }
    
    }
    
  • It works on desktop with Mouse and Keyboard Input! :-)

    On Android phone seems to work properly only when the Mouse and Keyboard are set. However, pinch zoom breaks the camera when used. When I build the game with Touch input, the player still follows any tap when panning or zooming. Maybe the best way would be to modify the pinch zoom script? But can that be even done if the input method is Mouse and Keyboard?

  • The PanZoom script reads Unity input directly - it'll have the same behaviour regardless of AC's "Input method".

    To prevent the player from moving when zooming due to PanZoom, you'll need to create a dependency - whereby ClickUpToMove checks if PanZoom is currently zooming, and ignores input if so.

    Try these:

    using UnityEngine;
    using AC;
    
    public class ClickUpToMove : MonoBehaviour
    {
    
        Vector3 startPosition;
        public PanZoom panZoom;
    
        private void Start ()
        {
            KickStarter.playerInput.InputGetMouseButtonDownDelegate = MyGetMouseButtonDown;
            KickStarter.playerInput.InputGetMouseButtonDelegate = MyGetMouseButton;
        }
    
        private bool MyGetMouseButtonDown (int button)
        {
            if (panZoom.IsZooming)
            {
                return false;
            }
            if (button == 0 && Input.GetMouseButtonDown (0))
            {
                startPosition = Input.mousePosition;
                return false;
            }
            return Input.GetMouseButtonUp (button);
        }
    
        private bool MyGetMouseButton (int button)
        {
            if (panZoom.IsZooming)
            {
                return false;
            }
            if (button == 0 && Input.GetMouseButton (0) && Input.mousePosition != startPosition)
            {
                return true;
            }
            return false;
        }
    
    }
    
    
    using UnityEngine;
    
    public class PanZoom : MonoBehaviour
    {
    
        public AC._Camera gameCamera;
        public float zoomOutMin = 1;
        public float zoomOutMax = 5.41f;
        public bool IsZooming { get; private set; }
    
        void Update ()
        {
            if (Input.touchCount == 2)
            {
                Touch touchZero = Input.GetTouch(0);
                Touch touchOne = Input.GetTouch(1);
    
                Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
                Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
    
                float prevMagnitude = (touchZeroPrevPos - touchOnePrevPos).magnitude;
                float currentMagnitude = (touchZero.position - touchOne.position).magnitude;
    
                float difference = currentMagnitude - prevMagnitude;
    
                Zoom (difference * Time.deltaTime);
                IsZooming = true;
                return;
            }
    
            float mouseInput = Input.GetAxis ("Mouse ScrollWheel");
            if (mouseInput != 0f)
            {
                Zoom (mouseInput);
                IsZooming = true;
                return;
            }
    
            IsZooming = false;
        }
    
        void Zoom (float increment)
        {
            gameCamera.Camera.orthographicSize = Mathf.Clamp(gameCamera.Camera.orthographicSize - increment, zoomOutMin, zoomOutMax);
        }
    
    }
    
  • edited February 14

    These scripts work, thank you! I just have a few details regarding the Zoom I'd need help with:

    1. I have multiple cameras on several scenes. How to adjust the ClickUpToMove to accommodate all of them?
    2. Is it possible to zoom on the current specific spot in between the two fingers that do the pinch zoom instead of on the character or the center of the camera?
    3. When I zoom out of the scene, the camera bounces a bit too much out for a split second revealing black borders around the scene background. It immediately bounces back the the max zoomout size set. Is it possible to make the zoom not zoom out beyond the max orthographic size set to fit the device's resolution/screen ratio?
  • I have multiple cameras on several scenes. How to adjust the ClickUpToMove to accommodate all of them?

    ClickUpToMove is camera-independent. You'll only need one instance of it present the scene, and it'll work regardless of which camera is active.

    Is it possible to zoom on the current specific spot in between the two fingers that do the pinch zoom instead of on the character or the center of the camera?

    You can use the GameCamera2DDrag's SetPosition function to re-centre the camera based on the input (either the mouse position, or the combined average of the two touch positions).

    Is it possible to make the zoom not zoom out beyond the max orthographic size set to fit the device's resolution/screen ratio?

    This is down to the order in which the game calls the Update functions. You'll want PanZoom to run before AC's main loop, which can be done with the DefaultExecutionOrder attribute.

    Changes to 2) and 3) below.

    using UnityEngine;
    
    [DefaultExecutionOrder (-1)]
    public class PanZoom : MonoBehaviour
    {
    
        public AC._Camera gameCamera;
        public float zoomOutMin = 1;
        public float zoomOutMax = 5.41f;
        public bool IsZooming { get; private set; }
    
        void Update ()
        {
            if (Input.touchCount == 2)
            {
                Touch touchZero = Input.GetTouch(0);
                Touch touchOne = Input.GetTouch(1);
    
                Vector2 touchZeroPrevPos = touchZero.position - touchZero.deltaPosition;
                Vector2 touchOnePrevPos = touchOne.position - touchOne.deltaPosition;
    
                float prevMagnitude = (touchZeroPrevPos - touchOnePrevPos).magnitude;
                float currentMagnitude = (touchZero.position - touchOne.position).magnitude;
    
                float difference = currentMagnitude - prevMagnitude;
    
                Vector2 averagePosition = (touchZero.position + touchOne.position) * 0.5f;
                Zoom (averagePosition, difference * Time.deltaTime);
                IsZooming = true;
                return;
            }
    
            float mouseInput = Input.GetAxis ("Mouse ScrollWheel");
            if (mouseInput != 0f)
            {
                Zoom (Input.mousePosition, mouseInput);
                IsZooming = true;
                return;
            }
    
            IsZooming = false;
        }
    
        void Zoom (Vector2 screenPosition, float increment)
        {
            gameCamera.Camera.orthographicSize = Mathf.Clamp(gameCamera.Camera.orthographicSize - increment, zoomOutMin, zoomOutMax);
            if (gameCamera is AC.GameCamera2DDrag)
            {
                Vector2 position = gameCamera.Camera.ScreenToWorldPoint (screenPosition);
                (gameCamera as AC.GameCamera2DDrag).SetPosition (position);
            }
        }
    
    }
    
  • ClickUpToMove is camera-independent. You'll only need one instance of it present the scene, and it'll work regardless of which camera is active.

    Just to be sure: it has a public field where I need to put the camera in Editor. So it does not matter which one I put there, even though a different camera may be used during gameplay?

    The zooming out (3) is not leaving the borders of the scene anymore, thank you!

    Unfortunately, the zooming to a spot between the fingers (2) kind of works, but sometimes the camera just slides somewhere else on the scene, especially when you leave the zooming fingers on the screen for too long or move one of the fingers more than the other. It also sometimes triggers the character to move to that location (he does not move during panning). Could that be somehow tweaked?

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.