Hoping someone has a quick answer for this one as I haven't been able to figure this out. Rather than have the image jumping to center itself on the mouse cursor, I'd like to be able to drag the image from any place on the image without the jump. I know it has something to do with referencing the mouse position against the image or reseting the origin point of the image to the mouse location, but I don't know how to code it. Has anyone done this already? Using C#.
Vector3 partsPanelScale;
public Vector3 buildPanelScale;
public Transform placeholderParent = null;
public Transform parentToReturnTo = null;
GameObject placeholder = null;
public GameObject animalPart;
public GameObject trashCan;
public GameObject partsPanel;
public GameObject partsWindow;
GameObject buildBoard;
GameObject dragLayer;
private float _mX; // holds current eventData.position.x
private float _mY; // holds current eventData.position.y
private float _pmX;// holds previous eventData.position.x
private float _pmY;// holds previous eventData.position.y
void Start ()
{
dragLayer = GameObject.FindGameObjectWithTag("DragLayer");
buildBoard = GameObject.FindGameObjectWithTag("Board");
partsPanel = GameObject.FindGameObjectWithTag("Parts");
partsWindow = GameObject.FindGameObjectWithTag("PartsWindow");
trashCan = GameObject.FindGameObjectWithTag("Trash");
}
#region IPointerClickHandler implementation
public void OnPointerClick (PointerEventData eventData)
{
if(transform.parent.gameObject == buildBoard)
{
transform.SetAsLastSibling();
}
}
#endregion
#region IBeginDragHandler implementation
public void OnBeginDrag (PointerEventData eventData)
{
// each frame updates the current position of the mouse.
_mX = eventData.position.x;
_mY = eventData.position.y;
// create placeholder gap and hold correct position in layout
placeholder = new GameObject();
placeholder.transform.SetParent(transform.parent);
placeholder.transform.SetSiblingIndex(transform.GetSiblingIndex());
if(transform.parent.gameObject == partsPanel)
{
partsPanelScale = transform.localScale;
}
parentToReturnTo = transform.parent; // store current parent location
placeholderParent = parentToReturnTo; // set placeholder gameobject transform
GetComponent<CanvasGroup>().blocksRaycasts = false; // turn off image raycasting when dragging image in order to see what's behind the image
}
#endregion
#region IDragHandler implementation
float distance = 0;
public void OnDrag (PointerEventData eventData)
{
// Divided the difference by 6 to reduce the speed of dragging.
transform.position = new Vector3
(
(_pmX - _mX)/6 + transform.position.x,
(_pmY - _mY)/6 + transform.position.y,
distance
);
// Vector3 mousePosition = new Vector3(eventData.position.x, eventData.position.y, distance);
// Vector3 objPosition = Camera.main.ViewportToScreenPoint(mousePosition);
// transform.position = mousePosition; // set object coordinates to mouse coordinates
if(transform.parent.gameObject == partsPanel)
{
transform.SetParent(dragLayer.transform); // pop object to draglayer to move object out of partsPnael
}
if(transform.parent.gameObject == buildBoard)
{
// Constrain drag to boundaries of buildBoard Code
}
}
#endregion
#region IEndDragHandler implementation
public void OnEndDrag (PointerEventData eventData)
{
// end of the drag. set the previous position.
_pmX = _mX;
_pmY = _mY;
transform.SetParent(parentToReturnTo); // Snaps object back to orginal parent if dropped outside of a dropzone
transform.SetSiblingIndex(placeholder.transform.GetSiblingIndex()); // Returns card back to placeholder location
GetComponent<CanvasGroup>().blocksRaycasts = true; // turn Raycast back on
Destroy(placeholder); // kill the placeholder if object hits a drop zone or returns to parts panel
if(transform.parent.gameObject == buildBoard)
{
// Debug.Log ("Your sprite is now on the " + transform.parent.name);
transform.localScale = buildPanelScale;
transform.SetAsLastSibling(); // always place last piece on top
}
if(transform.parent.gameObject == partsPanel)
{
transform.localScale = partsPanelScale;
}
}
#endregion
To drag correctly you need both Previous and Current position of the mouse.
Because each time you move mouse the Image should Move a bit (at x and y coordinate) from its previous position.
If you just use the current position and directly set it to image the resault will be jump over the screen.
You have to Transform the transformation of image by getting differences of previous and current position (x2 - x1 , y2 - y1).Then set the Final transformation to the Image transform.
You also need MouseOver event to update the positions. (orMouseMove)
private double _mX; // holds current eventData.position.x
private double _mY; // holds current eventData.position.y
private double _pmX;// holds previous eventData.position.x
private double _pmY;// holds previous eventData.position.y
// each frame updates the current position of the mouse.
private void MouseOver(PointerEventData eventData)
{
_mX = eventData.position.x;
_mY = eventData.position.y;
}
public void OnDrag (PointerEventData eventData)
{
transform.position = new Vector3D((_pmX - _mX)/6 + transform.position.x,
(_pmY - _mY)/6 + transform.position.y, distance);
// Divided the difference by 6 to reduce the speed of dragging.
//...
// end of the drag. set the previous position.
_pmX = _mX;
_pmY = _mY;
}
Note that class names and events i have used may differ from unity3d.
If you drag from right to left but image goes from left to right replace _pmX - mX with _mX - _pmX.
If you drag from up to down but image goes from down to up replace _pmY - mY with _mY - _pmY.
Related
I am trying to make Line Renderer's first point move along with Camera. When I do it with a Camera that is still, it works fine. But when camera changes position and in that position if I try to create new Line with Line Renderer it goes weird. I tried updating the position of first point by multiplying camera.tranform.position with dragBeginPos but it doesn't seem to help as it makes it even bad.
public class DragController : MonoBehaviour
{
[SerializeField] private float maxDrag = 3f; // Maximum drag distance
[SerializeField] private LineRenderer lr;
private Vector3 dragBeganPos; // Position of the mouse or touch when the drag began
private Vector3 dragEndPosPublic;
private Touch touch;
private PlayerBallMovement playerBallMovement;
[HideInInspector] public Vector3 dragDirection; // Pure drag direction without max drag distance
[HideInInspector] public Vector3 dragDirectionClamped; // Clamped drag direction with max drag distance
private enum ControlsType
{
Touch,
Mouse
}
private enum DirectionType
{
Normal,
Clamped
}
[SerializeField] private ControlsType controlsType = ControlsType.Mouse; // Default controls type are mouse
[SerializeField] private DirectionType directionType = DirectionType.Clamped; // Default direction type is clamped
void Update()
{
if (controlsType == ControlsType.Mouse)
{
DragWithMouse();
}
else if (controlsType == ControlsType.Touch)
{
DragWithTouch();
}
//UpdateLineRendererPointsPositions();
}
void DragWithMouse()
{
// Mouse button inputs
if (Input.GetMouseButtonDown(0)) DragBegan();
if (Input.GetMouseButton(0)) DragMoved();
if (Input.GetMouseButtonUp(0)) DragEnded();
}
void DragWithTouch()
{
// Touch inputs
if (Input.touchCount > 0)
{
touch = Input.GetTouch(0);
if (touch.phase == TouchPhase.Began) DragBegan();
if (touch.phase == TouchPhase.Moved) DragMoved();
if (touch.phase == TouchPhase.Ended) DragEnded();
}
}
// Drag begins defining start position of Line Rederer
void DragBegan()
{
dragBeganPos = Camera.main.ScreenToWorldPoint(
controlsType == ControlsType.Mouse ? Input.mousePosition : (Vector3)touch.position
);
dragBeganPos.z = 0f;
lr.positionCount = 1;
lr.SetPosition(0, dragBeganPos);
}
// Drag moved calculates the end point of the Line Rederer until mouse or touch is released
void DragMoved()
{
Vector3 dragMovedPos = Camera.main.ScreenToWorldPoint(
controlsType == ControlsType.Mouse ? Input.mousePosition : (Vector3)touch.position
);
dragMovedPos.z = 0f;
lr.positionCount = 2;
lr.SetPosition(1, dragMovedPos);
}
// Drag ended defines the end point of the Line Rederer and calculates the drag direction and clamped drag direction
void DragEnded()
{
lr.positionCount = 0;
Vector3 dragEndedPos = Camera.main.ScreenToWorldPoint(
controlsType == ControlsType.Mouse ? Input.mousePosition : (Vector3)touch.position
);
dragEndedPos.z = 0f;
dragEndPosPublic = dragEndedPos;
dragDirection = dragEndedPos - dragBeganPos; // It will apply force without any max speed defined (no clamp)
dragDirectionClamped = Vector3.ClampMagnitude(dragDirection, maxDrag); // It will apply force with max speed defined
Vector3 finalDirection = directionType == DirectionType.Clamped ? dragDirectionClamped : dragDirection;
// Here you can use dragDirection or dragDirectionClamped however you want
playerBallMovement = FindObjectOfType<PlayerBallMovement>();
playerBallMovement.ApplyVelocityInDirection(finalDirection);
}
}```
Im trying to instansiate a line where i click in the screen, the origin of the line is where i first clicked and the end of the line is where the mouse is at the moment all this happens while the mouse button is down. The problem is that i give give the origin of the line the exact same value of the mouse in that moment but is been instansiated with a offset and i dont understand why because im doing some prints of the transform.position of the line origin and of the mouse and both match.
The script is in the panel and he es contained by the canvas, the canvas Render mode is World Space.
Heres an Screenshoot where it shows exactly where i click and where it instansiates.
CODE:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
public class MovementController : MonoBehaviour
{
public float mousePos;
public Camera cam;
public GameObject dirLine;
public Vector2 origin;
GameObject tempLine;
Vector2 ending;
Vector2 dir;
float width;
bool spawned;
// Start is called before the first frame update
void Start()
{
cam = GameObject.Find("Main Camera").GetComponent<Camera>();
spawned = false;
}
// Update is called once per frame
void Update()
{
//Debug.Log(Input.mousePosition);
if (tempLine != null)
{
ending = Input.mousePosition;
dir = ending - origin;
width = dir.magnitude;
//tempLine.GetComponent<RectTransform>().localPosition = origin;
tempLine.GetComponent<RectTransform>().sizeDelta = new Vector2(width, tempLine.GetComponent<RectTransform>().sizeDelta.y);
float angle = Vector2.SignedAngle(dir, Vector2.up);
tempLine.transform.eulerAngles = new Vector3(0, 0, -(angle - 90));
Debug.Log("LocalPosition de la linea:"+tempLine.GetComponent<RectTransform>().localPosition);
}
}
private void OnMouseDown()
{
spawned = true;
Vector3 screenpoint = new Vector3(Input.mousePosition.x, Input.mousePosition.y, 1.0f);
//origin = GameObject.Find("Panel").transform.localPosition;
tempLine = Instantiate(dirLine,transform);
origin = screenpoint;
tempLine.transform.localPosition = origin;
tempLine.GetComponent<RectTransform>().localPosition = origin;
Debug.Log("Posicion del Mouse al ser clicado"+Input.mousePosition);
}
private void OnMouseUp()
{
spawned = false;
Destroy(tempLine);
}
}
Goal: Make a general component that supports pinching to zoom on device and scroll mouse to zoom in editor. While pinching, set pivot to the middle point of two fingers, so it scales around where you pinch.
I provided my scripts in the followings. However it does not work well in scroll view on devices with two fingers pinching, when it "jumps" or jitters a lot. It works with mouse scroll in the Editor though. I think the reason is perhaps related with how the scroll view updates the layout internally. In a single frame my scripts changed the pivot and position of the content however the scroll view can not handle these changes properly. That's why I also tested my scripts using 1 LateUpdate, 2 yield WaitForEndOfFrame, 3 register call back in scroll view's OnValueChanged event, but all failed.
Does anyone know how to fix the problem in my script or any other new solutions how to make a scroll view to support pinch to zoom? Thanks!
My codes are something like this:
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.EventSystems;
public class PinchZoom : MonoBehaviour {
public float zoomSpeedPinch = 0.001f;
public float zoomSpeedMouseScrollWheel = 0.05f;
public float zoomMin = 0.1f;
public float zoomMax = 1f;
RectTransform rectTransform;
public int type = 1; // for device testing type 1 use LateUpdate; type 2 use Update
private void Awake()
{
rectTransform = GetComponent<RectTransform>();
}
//public void OnValueChanged(Vector2 v) // test failed: called by scroll view event
//{
// //Zoom();
//}
void Update()
{
//Zoom();
if (type == 2)
{
if (Input.touchCount == 2)
StartCoroutine(ZoomInTheEndOfFrame(Input.mouseScrollDelta.y, Input.touchCount, Input.GetTouch(0), Input.GetTouch(1), Input.mousePosition));
else
StartCoroutine(ZoomInTheEndOfFrame(Input.mouseScrollDelta.y, Input.touchCount, default(Touch), default(Touch), Input.mousePosition));
}
}
private void LateUpdate()
{
if (type == 1) Zoom();
}
void Zoom()
{
var mouseScrollWheel = Input.mouseScrollDelta.y;
float scaleChange = 0f;
Vector2 midPoint = Vector2.zero;
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 prevTouchDeltaMag = (touchZeroPrevPos - touchOnePrevPos).magnitude;
float touchDeltaMag = (touchZero.position - touchOne.position).magnitude;
float deltaMagnitudeDiff = touchDeltaMag - prevTouchDeltaMag;
scaleChange = deltaMagnitudeDiff * zoomSpeedPinch;
midPoint = (touchOne.position + touchZero.position) / 2;
}
if (mouseScrollWheel != 0)
{
scaleChange = mouseScrollWheel * zoomSpeedMouseScrollWheel;
midPoint = Input.mousePosition;
}
if (scaleChange != 0)
{
var scaleX = transform.localScale.x;
scaleX += scaleChange;
scaleX = Mathf.Clamp(scaleX, zoomMin, zoomMax);
var size = rectTransform.rect.size;
size.Scale(rectTransform.localScale);
var parentRect = ((RectTransform)rectTransform.parent);
var parentSize = parentRect.rect.size;
parentSize.Scale(parentRect.localScale);
if (size.x > parentSize.x && size.y > parentSize.y)
{
var p1 = Camera.main.ScreenToWorldPoint(midPoint);
var p2 = transform.InverseTransformPoint(p1);
var pivotP = rectTransform.pivot * rectTransform.rect.size;
var p3 = (Vector2)p2 + pivotP;
var newPivot = p3 / rectTransform.rect.size;
newPivot = new Vector2(Mathf.Clamp01(newPivot.x), Mathf.Clamp01(newPivot.y));
rectTransform.SetPivot(newPivot);
}
else
{
rectTransform.SetPivot(new Vector2(0.5f, 0.5f));
}
transform.localScale = new Vector3(scaleX, scaleX, transform.localScale.z);
}
}
//private IEnumerator ZoomInTheEndOfFrame(float mouseScrollWheel, int touchCount, Touch touchZero, Touch touchOne, Vector3 mousePosition) // testing failed
//{
// yield return new WaitForEndOfFrame();
// ZoomWithData(mouseScrollWheel, touchCount, touchZero, touchOne, mousePosition);
//}
}
For changing pivot without get the image "jumped" I used an extension script:
using UnityEngine;
public static class RectTranformExtension
{
/// <summary>
/// Set pivot without changing the position of the element
/// </summary>
public static void SetPivot(this RectTransform rectTransform, Vector2 pivot)
{
Vector3 deltaPosition = rectTransform.pivot - pivot; // get change in pivot
deltaPosition.Scale(rectTransform.rect.size); // apply sizing
deltaPosition.Scale(rectTransform.localScale); // apply scaling
deltaPosition = rectTransform.rotation * deltaPosition; // apply rotation
rectTransform.pivot = pivot; // change the pivot
rectTransform.localPosition -= deltaPosition; // reverse the position change
}
}
After working on this for days, I finally solved it.
The solution it is to call Zoom() in OnDrag(), and block the scollView component to receive dragging related events while zooming.
If you want to use it, just copy my codes under, do not do any changes. This is what I got after tons of testing on device. There're some minor potential gotcha with too many details that I don't want to explain, just copy and use it. And also I suggest to use a speed of current content.scale.x * 0.001
Codes:
// please note that scrollRect is the component on the scroll view game object, not where this script is
public void OnDrag(PointerEventData eventData)
{
Zoom();
if (Input.touchCount <= 1) scrollRect.OnDrag(eventData);
}
public void OnEndDrag(PointerEventData eventData)
{
scrollRect.OnEndDrag(eventData);
}
public void OnBeginDrag(PointerEventData eventData)
{
if (Input.touchCount <= 1) scrollRect.OnBeginDrag(eventData);
}
P.S. In the original script, there's a part that if a check for boundary is not passed then set pivot to (0.5,0.5). This part should be commented out(do not need to change pivot if the check is not passed ).
One more thing, you can use whatever parent-children relations you want, but you must make sure the object that receive the onDrag events has a whole image to catch the events.
Ok, so I have a standard joystick downloaded from Unity asset store, script here (isn't that long):
// Implement IPointerDownHandler, IPointerUpHandler, IDragHandler to subscribe to pointer events
public class Joystick : MonoBehaviour, IPointerDownHandler, IPointerUpHandler, IDragHandler {
public delegate void JoystickAction(Vector2 joystickAxes);
public static event JoystickAction JoystickMoved;
public Vector2 joystickAxes;
public bool usingJoystick;
private Image bgImg;
private Image stickImg;
private RectTransform bgTransform;
private RectTransform stickTransform;
// The first tap is treated similar to any following detection of a drag
public virtual void OnPointerDown(PointerEventData ped) {
usingJoystick = true;
OnDrag (ped);
}
public virtual void OnPointerUp(PointerEventData ped) {
usingJoystick = false;
joystickAxes = stickImg.GetComponent<RectTransform> ().anchoredPosition = Vector2.zero;
//joystickAxes = new Vector2(Screen.height / 2, Screen.width/2);
if (JoystickMoved != null) JoystickMoved (Vector2.zero);
}
public virtual void OnDrag(PointerEventData ped) {
Vector2 rectPos;
if (RectTransformUtility.ScreenPointToLocalPointInRectangle(
bgImg.rectTransform,
ped.position,
ped.enterEventCamera,
out rectPos)) { // Check if the pointer is positioned on the joystick
// Clamp the position to be inside the image bounds, prevents dragging beyond the image to get higher values.
Vector2 clampedPos = GetClampedPosition (rectPos);
// Normalize the joystick axes to be between -1 and 1.
joystickAxes = new Vector2 (
clampedPos.x / (bgTransform.rect.width / 2f),
clampedPos.y / (bgTransform.rect.height / 2f));
// Set the position of the inner joystick.
if (joystickAxes.magnitude > 1f) { // Normalizing the clampedPos constrains the joystick positions to a circle
stickImg.GetComponent<RectTransform> ().anchoredPosition = clampedPos.normalized * stickTransform.rect.width;
} else {
stickImg.GetComponent<RectTransform> ().anchoredPosition = clampedPos;
}
}
}
private Vector2 GetClampedPosition(Vector2 pos) {
Vector2 bgMin = bgTransform.rect.min;
Vector2 bgMax = bgTransform.rect.max;
return new Vector2 (
Mathf.Clamp (pos.x, bgMin.x, bgMax.x),
Mathf.Clamp (pos.y, bgMin.y, bgMax.y));
}
// Use this for initialization
void Start () {
joystickAxes = new Vector2 ();
bgImg = GetComponent<Image> ();
stickImg = transform.GetChild (0).GetComponent<Image> ();
bgTransform = gameObject.GetComponent<RectTransform> ();
stickTransform = transform.GetChild (0).GetComponent<RectTransform> ();
}
void Update() {
if (usingJoystick && JoystickMoved != null) {
JoystickMoved (joystickAxes);
}
}
}
This relies on an image object on a canvas and another image for the knob. as pictured:
This all works well, however I want the joystick to BE the screen, meaning the outside bounds for the knob aren't the background image but the screen bounds itself.
Ive tried scaling the background image to the screen using rh canvas, etc but this makes the inputAxes values way off. How can I do this?
I want to keep this script, just tweak it so the background "image"/bounds are the screen, whatever size that is.
I have created a project where a cube is rotated when we touch on it. I want the cube to return back to its original position when the user stops touching the cube. Below I have added the source code of rotating a cube:
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshRenderer))]
public class dr : MonoBehaviour
{
#region ROTATE
private float _sensitivity = 1f;
private Vector3 _mouseReference;
private Vector3 _mouseOffset;
private Vector3 _rotation = Vector3.zero;
private bool _isRotating;
#endregion
void Update()
{
if(_isRotating)
{
// offset
_mouseOffset = (Input.mousePosition - _mouseReference); // apply rotation
_rotation.y = -(_mouseOffset.x + _mouseOffset.y) * _sensitivity; // rotate
gameObject.transform.Rotate(_rotation); // store new mouse position
_mouseReference = Input.mousePosition;
}
}
void OnMouseDown()
{
// rotating flag
_isRotating = true;
// store mouse position
_mouseReference = Input.mousePosition;
}
void OnMouseUp()
{
// rotating flag
_isRotating = false;
}
}
edited code:-
using UnityEngine;
using System.Collections;
[RequireComponent(typeof(MeshRenderer))]
public class pt : MonoBehaviour
{
#region ROTATE
private float _sensitivity = 1f;
private Vector3 _mouseReference;
private Vector3 _mouseOffset;
private Vector3 _rotation = Vector3.zero;
private bool _isRotating;
private Quaternion original;
#endregion
void start(){
original = transform.rotation;
}
void Update()
{
if(_isRotating)
{
// offset
_mouseOffset = (Input.mousePosition - _mouseReference); // apply rotation
_rotation.y = -(_mouseOffset.x + _mouseOffset.y) * _sensitivity; // rotate
gameObject.transform.Rotate(_rotation); // store new mouse position
_mouseReference = Input.mousePosition;
}
}
public void OnMouseDown()
{
// rotating flag
_isRotating = true;
// store mouse position
_mouseReference = Input.mousePosition;
}
public void OnMouseUp()
{
// rotating flag
_isRotating = false;
transform.rotation = original;
}
}
"im trying to rotate a 3d model of a sofa and return to its starting rotation.but if i used this code " whenever if i stopped touching the sofa , it turns to **backside of sofa"**
i want it to return to initial rotation.u can see initally this is how the sofa looks like and if i stopped touching it returns to its backside of sofa. i want it to return to its front side again if i stopped rotation
I want the cube to return back to its original position when the user
stopped touching the cube
I can't exactly tell which part of this you are struggling with but you can simply get the position of the GameObject in the Start or Awake function then set the transform.position to that value when OnMouseUp is called.
private Vector3 originalPos;
void Start()
{
//Get the original position
originalPos = transform.position;
}
void OnMouseUp()
{
_isRotating = false;
//Reset GameObject to the original position
transform.position = originalPos;
}
EDIT:
For rotation, it is also the-same thing. Just use Quaternion and transform.rotation instead of Vector3 and transform.position.
private Quaternion originalPos;
void Start()
{
//Get the original rotation
originalPos = transform.rotation;
}
void OnMouseUp()
{
_isRotating = false;
//Reset GameObject to the original rotation
transform.rotation = originalPos;
}
You still have to incorporate that into the original code from your answer. If this is something you can't do then consider watching Unity's scripting tutorial here.