Scale gameobject with Input.touchCount

Hi,

I’ve been searching for a couple of days how to drag, rotate and scale a gameobject. Below is the Javascript code that’s working for drag and rotate :

#pragma strict
private var baseAngle = 0.0;
private var dist : Vector3;
private var posX : float;
private var posY : float;
   
function OnMouseDown(){
   
    if (Input.touchCount == 1) {
       
      dist = Camera.main.WorldToScreenPoint(transform.position);
      posX = Input.mousePosition.x - dist.x;
      posY = Input.mousePosition.y - dist.y;

       }
      
     if (Input.touchCount == 2) {
       
       var pos = Camera.main.WorldToScreenPoint(transform.position);
       pos = Input.mousePosition - pos;
       baseAngle = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
       baseAngle -= Mathf.Atan2(transform.right.y, transform.right.x) *Mathf.Rad2Deg; 
      
       }
    }

function OnMouseDrag(){
   
     if (Input.touchCount == 1) {
       
     var curPos = new Vector3(Input.mousePosition.x - posX,
     Input.mousePosition.y - posY, dist.z);
     var worldPos = Camera.main.ScreenToWorldPoint(curPos);
     transform.position = worldPos;

     }
     
     if (Input.touchCount == 2) {
       
     var pos = Camera.main.WorldToScreenPoint(transform.position);
     pos = Input.mousePosition - pos;
     var ang = Mathf.Atan2(pos.y, pos.x) *Mathf.Rad2Deg - baseAngle;
     transform.rotation = Quaternion.AngleAxis(ang, Vector3.forward);
     }
      
}

I’ve then tried to add the scaling part to work along with the rotating one (so in Input.touchCount == 2) but no luck. How can I manage to rotate and scale my gameobject?

Thanks!

What code did you try to use to scale it? Is this a pinch and zoom type gesture for the two touches? For what are you using the second touch?

  1. In OnMouseDown, store the distance between touch 1 and 2.
  2. In OnMouseDrag, determine the current distance between touch 1 and 2, and find what percentage of the original distance the current distance is.
  3. Apply this percentage to the scale of the object.

Have you tried this already, if so, can you show what you tried? Or do you not know how to do it?

I do not know JavaScript, and I don’t really want to write it. But here is a rough implementation in C# which I have not tested, but should show you the different functions you can use to get the information you need.

Mainly the Input.GetTouch to find the touch positions and states etc.

private float initialDistance;
private float newDistance;

private void OnMouseDown() {
    // if the second touch just started
    if(Input.touchCount == 2 && Input.GetTouch(1).phase == TouchPhase.Began) {
        Vector2 touch1 = Input.GetTouch(0).position;
        Vector2 touch2 = Input.GetTouch(1).position;

        // save the initial distance
        initialDistance = (touch1 - touch2).sqrMagnitude;

    }
}

private void OnMouseDrag() {
    // if there are still 2 touches and one of the first two touches moved
    if(Input.touchCount >= 2 && (Input.GetTouch(0).phase == TouchPhase.Moved || Input.GetTouch(1).phase == TouchPhase.Moved)) {
        Vector2 touch1 = Input.GetTouch(0).position;
        Vector2 touch2 = Input.GetTouch(1).position;

        newDistance = (touch1 - touch2).sqrMagnitude;
        float changeInDistance = newDistance - initialDistance;
        float percentageChange = changeInDistance / initialDistance;

        Vector3 newScale = transform.localScale;
        newScale += percentageChange * transform.localScale;

        transform.localScale = newScale;
    }
}

private void OnMouseUp() {
    // less than two touches, reset the initial distance
    if(Input.touchCount < 2) {
        initialDistance = 0;
    }
}

As far as I know you can change all the types to var/colon syntax and change all the functions to start with “function” and you’ve got JavaScript.

I’m gone for the night, so best of luck. I’ll check tomorrow if you still need help.

Thanks for your replies! I’ve tried your code jeffreyschoch but whenever I touch a gameobject it disappeared from the screen.

Below is the latest thing I’ve tried :

#pragma strict
private var baseAngle = 0.0;
private var dist : Vector3;
private var posX : float;
private var posY : float;

private var initialFingersDistance : float;
private var initialScale : Vector3;
private var currentFingersDistance : float;
private var scaleFactor : float;
   
function OnMouseDown(){
   
    if (Input.touchCount == 1) {
       
      dist = Camera.main.WorldToScreenPoint(transform.position);
      posX = Input.mousePosition.x - dist.x;
      posY = Input.mousePosition.y - dist.y;

       }
      
     if (Input.touchCount == 2) {
       
       var pos = Camera.main.WorldToScreenPoint(transform.position);
       pos = Input.mousePosition - pos;
       baseAngle = Mathf.Atan2(pos.y, pos.x) * Mathf.Rad2Deg;
       baseAngle -= Mathf.Atan2(transform.right.y, transform.right.x) *Mathf.Rad2Deg;
      
       initialFingersDistance = Vector3.Distance(Input.touches[0].position, Input.touches[1].position);
       initialScale = transform.localScale;    
       }
    }

function OnMouseDrag(){
   
     if (Input.touchCount == 1) {
       
     var curPos = new Vector3(Input.mousePosition.x - posX,
     Input.mousePosition.y - posY, dist.z);
     var worldPos = Camera.main.ScreenToWorldPoint(curPos);
     transform.position = worldPos;

     }
     
     if (Input.touchCount == 2) {
       
     var pos = Camera.main.WorldToScreenPoint(transform.position);
     pos = Input.mousePosition - pos;
     var ang = Mathf.Atan2(pos.y, pos.x) *Mathf.Rad2Deg - baseAngle;
     transform.rotation = Quaternion.AngleAxis(ang, Vector3.forward);
    
     currentFingersDistance = Vector3.Distance(Input.touches[0].position, Input.touches[1].position);
     scaleFactor = currentFingersDistance / initialFingersDistance;
       transform.localScale = initialScale * scaleFactor;
     }
      
}

Basically, I have four gameobject in my scene and I need to drag, rotate and scale them individually. With one finger touch, the gameobject should drag. With two fingers touch, it should rotate and scale. That code partially work actually, but it crashes randomly and I can’t drag/rotate/scale anything.

I think the functions OnMouseDown and OnMouseDrag are probably not the best way to handle this this. You can get much more control if you do it yourself in the Update loop.

These types of multi-touch interactions are fairly complicated, but you’ve got a good start. I will try to code something in more detail for you to look at when I find some free time, hopefully tonight.

1 Like

Thanks jeffreyschoch!

Below is the original code I used for the scaling part, which I’ve found on this forum btw :

var initialFingersDistance : float;
var initialScale : Vector3;
function Update(){
    var fingersOnScreen : int = 0;
    for (var touch : Touch in Input.touches) {
        fingersOnScreen++; //Count fingers (or rather touches) on screen as you iterate through all screen touches.
        //You need two fingers on screen to pinch.
        if(fingersOnScreen == 2){
            //First set the initial distance between fingers so you can compare.
            if(touch.phase == TouchPhase.Began){
                initialFingersDistance = Vector2.Distance(Input.touches[0].position, Input.touches[1].position);
                initialScale = transform.localScale;
            }
            else{
                var currentFingersDistance : float = Vector2.Distance(Input.touches[0].position, Input.touches[1].position);
        
               var scaleFactor : float = currentFingersDistance / initialFingersDistance;
        
                transform.localScale = initialScale * scaleFactor;
            }
        }
    }
}

The code is in the update function as you suggested but the gameobject aren’t scaling individually now.

Hey there, sorry for the delay. I moved into a new apartment over the weekend. I’m still not fully setup.

Basically you can either have all objects watching the touches and transforming themselves, or you can have one object watching the touches, which transforms any object with a collider, or on a certain layer or tag etc.

I would suggest the latter.

I decided to do some research and attempt to code this. It’s not perfect, but hopefully you can learn from this and adapt it to your own needs. Or maybe it will be perfect for you.

Make an empty gameobject and put this script on it, and it will work on any object that is assigned a Layer listed in the LayerMask and has a collider. Only one of these scripts needs to be in the scene for it to work.

I don’t know if this is the best way to do this, or the most efficient, but it gave me more or less the results I was looking for.

If this isn’t a 2D project, you can replace the Physics2D.OverlapPoint with a Physics Raycast forward using the same touch position.

using UnityEngine;
using System.Collections;
using System;

public class TouchTransformationManager : MonoBehaviour {

    public LayerMask interactableObjects;
    public float minScale = .3f;
    public float maxScale = 3f;

    private Transform selectedTransform;

    private Touch firstTouch;
    private Vector3 firstTouchPosition;
    private Vector3 firstTouchOffset;

    private Touch secondTouch;
    private Vector3 secondTouchPosition;

    private float initialDistance;
    private float currentDistance;
    private Vector3 initialScale;

    private Vector3 lastDirection;
    private Vector3 currentDirection;

    private void Update() {
        HandleTouches();
    }

    private void HandleTouches() {
        if(Input.touchCount > 0) {
            HandleFirstTouch();
            if(Input.touchCount > 1) {
                HandleSecondTouch();
            }
        }
    }

    private void HandleFirstTouch() {
        // get the first touch's info and world-position
        firstTouch = Input.GetTouch(0);
        firstTouchPosition = Camera.main.ScreenToWorldPoint(firstTouch.position);

        // if there's no object selected
        if(selectedTransform == null) {
            // check if a touch just began
            if(firstTouch.phase == TouchPhase.Began) {
                // see what is under the touch
                Collider2D hitCollider = Physics2D.OverlapPoint(firstTouchPosition, interactableObjects);
                if(hitCollider != null) {
                    // if an object was hit, save it and the distance between touch and object center
                    selectedTransform = hitCollider.transform;
                    firstTouchOffset = selectedTransform.position - firstTouchPosition;
                }
            }
        } else {
            // if there's already an object selected, see if the touch has moved or ended
            switch(firstTouch.phase) {
                case TouchPhase.Moved:
                    // if the touch moved, have the object follow if there are no other touches
                    if(Input.touchCount == 1) {
                        SetPosition(firstTouchPosition);
                    }
                    break;
                case TouchPhase.Canceled:
                case TouchPhase.Ended:
                    // deselect the object if the touch is lifted
                    selectedTransform = null;
                    break;
            }
        }
    }

    private void HandleSecondTouch() {
        // if there is currently a selected object
        if(selectedTransform != null) {
            // get the touch info and world position
            secondTouch = Input.GetTouch(1);
            secondTouchPosition = Camera.main.ScreenToWorldPoint(secondTouch.position);

            // if the second touch just began
            if(secondTouch.phase == TouchPhase.Began) {
                // save the direction between first and second touch
                currentDirection = secondTouchPosition - firstTouchPosition;
                // initialize the 'last' direction for comparisons
                lastDirection = currentDirection;

                // get the distance between the touches, saving the initial distance for comparison
                currentDistance = (lastDirection).sqrMagnitude;
                initialDistance = currentDistance;

                // save the object's starting scale
                initialScale = selectedTransform.localScale;
            } else if(secondTouch.phase == TouchPhase.Moved || firstTouch.phase == TouchPhase.Moved) {
                //
                // if either touch moved, update the rotation and scale
                //
           
                // get the current direction between the touches
                currentDirection = secondTouchPosition - firstTouchPosition;

                // find the angle difference between the last direction and the current
                float angle = Vector3.Angle(currentDirection, lastDirection);

                // Vector3.Angle only outputs positives, so check if it should be a negative angle
                Vector3 cross = Vector3.Cross(currentDirection, lastDirection);
                if(cross.z > 0) {
                    angle = -angle;
                }

                // update rotation
                SetRotation(angle);
                // save this direction for next frame's comparison
                lastDirection = currentDirection;

                // get the current distance between touches
                currentDistance = (currentDirection).sqrMagnitude;
                // get what % of the intial distance this new distance is
                float difference = currentDistance / initialDistance;
                // scale by that percentage
                SetScale(difference);
            }

            // if the second touch ended
            if(secondTouch.phase == TouchPhase.Ended || secondTouch.phase == TouchPhase.Canceled) {
                // update the first touch offset so dragging will start again from wherever the first touch is now
                firstTouchOffset = selectedTransform.position - firstTouchPosition;
            }
        }
    }

    // update object position without changing Z
    private void SetPosition(Vector3 position) {
        if(selectedTransform != null) {
            Vector3 newPosition = position + firstTouchOffset;
            newPosition.z = selectedTransform.position.z;
            selectedTransform.position = newPosition;
        }
    }

    // rotate the object by the angle about the Z axis
    private void SetRotation(float angle) {
        if(selectedTransform != null) {
            selectedTransform.Rotate(Vector3.forward, angle);
        }
    }

    // scale the object by the percentage difference
    // taking into account min/max scale value
    private void SetScale(float percentDifference) {
        if(selectedTransform != null) {
            Vector3 newScale = initialScale * percentDifference;
            if(newScale.x > minScale && newScale.y > minScale && newScale.x < maxScale && newScale.y < maxScale) {
                newScale.z = 1f;
                selectedTransform.localScale = newScale;
            }
        }
    }
}
1 Like

Hi jeffreyschoch, no worries at all for the delay!

This is exactly what I wanted to achieve, I’ll try to find out how to adapt your code using Physics Raycast now :slight_smile:

Thank you so much for your time, have a nice day!

1 Like

No problem, it was a learning experience for me as well.

1 Like

Good afternoon!

So, I’ve tried to understand how Physics Raycast works and below is the most successful code I’ve managed to end up with :

private void HandleFirstTouch() {
        // get the first touch's info and world-position
        firstTouch = Input.GetTouch(0);
        firstTouchPosition = Camera.main.ScreenToWorldPoint(firstTouch.position);
       
        // if there's no object selected
        if(selectedTransform == null) {
            // check if a touch just began
            if(firstTouch.phase == TouchPhase.Began) {
                // see what is under the touch
                RaycastHit hit;
                Ray ray = Camera.main.ScreenPointToRay(firstTouch.position);
                if (Physics.Raycast(ray, out hit, Mathf.Infinity, interactableObjects))
                    if (hit.collider != null)
                        selectedTransform = hit.transform;
                firstTouchOffset = selectedTransform.position - firstTouchPosition;
            }
        } else {
            // if there's already an object selected, see if the touch has moved or ended
            switch(firstTouch.phase) {
            case TouchPhase.Moved:
                // if the touch moved, have the object follow if there are no other touches
                if(Input.touchCount == 1) {
                    SetPosition(firstTouchPosition);
                }
                break;
            case TouchPhase.Canceled:
            case TouchPhase.Ended:
                // deselect the object if the touch is lifted
                selectedTransform = null;
                break;
            }
        }
    }

As far as I understand, it seems that the Raycast is working fine since I can move and rotate all of the gameobject assigned to the LayerMask and scale the ones whom are in 2D. I’ve then read a lot of examples using Vector3.forward but I have no idea how to use it without having any errors…

Vector3.forward is the same as “new Vector(0, 0, 1)”. It means the direction of the World-Space Z axis, the blue arrow on the Transform gizmo. “ScreenPointToRay” takes the position you give it on screen, and creates a ray “forward” in world space based on the direction the camera is looking, or the camera’s local “forward”.

Hi!

I’ve deleted the “ScreenPointToRay” and added Vector3.forward to the Physics Raycast like this :

RaycastHit hit;
                if (Physics.Raycast(firstTouchPosition, Vector3.forward, out hit, Mathf.Infinity, interactableObjects))
                    if (hit.collider != null)
                        selectedTransform = hit.transform;
                firstTouchOffset = selectedTransform.position - firstTouchPosition;

But it still didn’t work, only the 2D game are scaling :confused:

Is your camera always pointing down the Z axis?

Also, be careful using “if” statements with no curly brackets. That will only run the very next line if the condition is true, so as you have it right now “selectedTransform” may be null and you will still be trying to get “position” from it, giving you a null reference exception.

Physics.Raycast will hit 3D objects, Physics2D.Raycast will hit 2D objects. If you need to hit both, you’ll have to do both raycasts.

1 Like

Thanks, I did not noticed those missing brackets!

I’ve actually managed to find why I wasn’t able to scale my 3D gameobject… It was simply because it was rotated. Move it has a child in an empty gameobject solved the issue!

Have a nice day, and again, thank you very much for your help :slight_smile:

1 Like