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?
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.
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;
}
}
}
}
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”.
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.
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