0

I am trying to move an object with mouse or touch but on particular fixed curve path after selecting an object.

I created a fixed path using bezier curve, and movement of object in path is working fine if i am using keyboard inputs using Input.GetAxisRaw("Horizontal"), but i want to based on mouse drag or touch.

Drag with Mouse or touch


using System;
using UnityEngine;

public class Collector : MonoBehaviour
{
    public Transform startPoint;
    public Transform middlePoint;
    public Transform endPoint;

    public float curveSpeed = 0.5f;
    //public float speed = 0f;
    private int _direction = 1;

    private bool _isObjectSelected;
    private Vector3 _mouseLastPosition;
    private float _journeyLength;
    private Vector3 _offsetPos;

    private float _currentTime = 0;

    private void Start()
    {
        _journeyLength = Vector3.Distance(startPoint.position,
                                            endPoint.position);

        UpdateJourney(0);
    }

    private void OnMouseDown()
    {
        if (_isObjectSelected)
            return;

        _offsetPos = Vector3.zero;
        _mouseLastPosition = Input.mousePosition;
        _isObjectSelected = true;
    }

    private void OnMouseUp()
    {
        _isObjectSelected = false;
    }

    private void OnMouseExit()
    {
        _isObjectSelected = false;
    }

    private void OnMouseDrag()
    {
        if (_isObjectSelected)
        {
            Debug.LogError("Mouse drag");
            Vector3 currentPosition = Input.mousePosition;
            _offsetPos += currentPosition - _mouseLastPosition;

            float distCovered = _offsetPos.y / _journeyLength;
            UpdateJourney(distCovered);
            _mouseLastPosition = currentPosition;
        }
    }

    private void UpdateJourney(float time)
    {
        if (time < 0)
            time = 0;
        else if (time > 1)
            time = 1;

        _currentTime = time;

        transform.position = 
            QuadraticCurve(startPoint.position, 
                            middlePoint.position, 
                            endPoint.position, 
                            _currentTime);

        transform.rotation = Quaternion.Euler(
                            new Vector3(0, 0, 
                                QuadraticCurve(0, 45, 90, _currentTime)));
    }

    private void Update()
    {
        // moving on path using keyboard input
        float direction = Input.GetAxisRaw("Horizontal");
        if (Math.Abs(direction) > 0.1f)
        {
            _currentTime += Time.deltaTime * curveSpeed * direction;
            UpdateJourney(_currentTime);
        }
    }

    private static Vector3 Lerp(Vector3 start, Vector3 end, float time)
    {
        return start + (end - start) * time;
    }

    private static Vector3 QuadraticCurve(Vector3 start, Vector3 middle, Vector3 end, float time)
    {
        Vector3 point0 = Lerp(start, middle, time);
        Vector3 point1 = Lerp(middle, end, time);
        return Lerp(point0, point1, time);
    }

    private static float QuadraticCurve(float start, float middle, float end, float time)
    {
        float point0 = Mathf.Lerp(start, middle, time);
        float point1 = Mathf.Lerp(middle, end, time);
        return Mathf.Lerp(point0, point1, time);
    }
}

derHugo
  • 83,094
  • 9
  • 75
  • 115

2 Answers2

0

There are a few issues:

  1. You have a Debug.LogError. You shouldn't do this since usually this interrupts the execution. By default the ErrorPause option in the Unity console is enabled. You should either remove this entirely (since Debug.Log in Update in general are very perfomance intense).

  2. One of the main issues here will be that the dragged object does not stay under the mouse but ruther follows the given curve. Therefore, OnMouseExit will get called and OnMouseDrag is interrupted.

    You can also simply keep track of the traveled distance using the mouse start position and compare it to the current one. No need for additionally increase a third variable.

    I would therefore rather change the code and use

    private Vector3 _mouseStartPosition;
    
    // remove OnMouseExit
    // remove OnMouseUp
    // remove OnMouseDrag
    
    private void OnMouseDown()
    {
        if (_isObjectSelected) return;
    
        _mouseStartPosition = Input.mousePosition;
        _isObjectSelected = true;
    }
    
    private void WhileDragging()
    {
        if (!_isObjectSelected) return;
    
        //Debug.LogError("Mouse drag");
        var currentPosition = Input.mousePosition;
        var offsetPos = currentPosition - _mouseStartPosition;
    
        // You might want to use the curveSpeed here as well
        // like kind of sensitivity
        var distCovered = offsetPos.y * curveSpeed / _journeyLength;
        UpdateJourney(distCovered);
    }
    
    // instead of using OnMouseExit, OnMouseUp and OnMouseDrag rather 
    // do it in Update
    private void Update()
    {
        if (_isObjectSelected)
        {
            // use this to detect mouse up instead
            if (Input.GetMouseButtonUp(0))
            {
                _isObjectSelected = false;
            }
        }
    
        // call it here instead of using OnMouseDrag
        WhileDragging();
    
        ...
    }
    

enter image description here


Note: This of course still only uses the traveled distance in Y direction. If you rather wanted to get the closest point on the bezier curves this gets a lot more complex and you might want to look it up (Nearest point on cubic bezier curve to a given point, Nearest point on a quadratic bezier curve, Closest point on Bezier spline, etc ...)

derHugo
  • 83,094
  • 9
  • 75
  • 115
0

Thanks @derHugo, I will try to resolve the problem with this (Nearest point on cubic bezier curve to a given point, Nearest point on a quadratic bezier curve, Closest point on Bezier spline, etc ...) although i want a simple solution instead of complex code.

        if (Input.GetMouseButtonDown(0))
        {
            RaycastHit hit;
            Ray ray = _mainCamera.ScreenPointToRay(Input.mousePosition);

            if (Physics.Raycast(ray, out hit, Mathf.Infinity))
            {
                if (hit.transform.gameObject == this.gameObject)
                {
                    _mouseStartPosition = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
                    _isObjectSelected = true;
                }
            }
        }

        if (Input.GetMouseButtonUp(0))
        {
            if (_isObjectSelected)
            {
                _isObjectSelected = false;
                //Debug.LogError("Gettting false");
            }
        }

        if (_isObjectSelected)
        {
            Vector3 mousePosition = _mainCamera.ScreenToWorldPoint(Input.mousePosition);
            mousePosition.z = 0f;
            //Debug.LogError(mousePosition + " , " + transform.position);

            Vector3 distanceVector = mousePosition - _mouseStartPosition;
            float distance = distanceVector.magnitude;
            Vector3 normalDistance = distanceVector.normalized;

            //Debug.LogError(distance + " " + normalDistance);
            if (distance > 0.1f)
            {
                if (normalDistance.y > 0.1f)
                {
                    _currentTime -= Time.deltaTime * distance * 3f;
                    _shieldMovement.UpdateJourney(_currentTime);
                }
                else if (normalDistance.x > 0.1f)
                {
                    _currentTime += Time.deltaTime * distance * 3f;
                    _shieldMovement.UpdateJourney(_currentTime);
                }
            }

            _mouseStartPosition = mousePosition;
        }

so far, i did this in update method, its not perfect but working ok.