Drag a 2D object along a path.

Hey everyone, I’ve stumbled along a question I can’t seem to solve. Is there a way of dragging an object (like a 2D sprite) along some lines? The thing I want to achieve is me only being able to drag the object along a specific track. If my question is not clear there is a picture down below illustrating what I mean (I’m not an artist). Thanks in advance.

Now that can get really complicated really fast depending on what your requirements are.
What kind of path is it? A sequence of straight lines? If so, my example code could get you started.
If instead you want bezier curves or some other fancy nice and smooth curves, you have to wait for a mathemagician to come around because that requires some seriously complicated algebra or other kinds of math (just google “find closest point on bezier curve” to see what I mean).

Here is my partial unoptimized solution for a sequence of straight lines (if you put enough small straight lines together it can look like a curve!).

So here is the idea:
Loop through all line segments and for each one, try to find the point on that line segment that is closest to your mouse poiner (or touch pointer whatever).
And of all of those, keep the one thats closest overall.

My code only demonstrates how you can find the closest point on a path made up of line segments.
Implementing dragging, making sure you can’t skip line segment etc is way too much work and will be up to you.

Hopefully it will help you get started. You can just copy the code and test it out. You just need to assign some kind of object to “circleSprite” to visualize the closest point.

using UnityEngine;

public class PointFollowPath : MonoBehaviour {
	public Transform circleSprite;
	private LineRenderer _lr;

	void Start () {
		_lr = gameObject.AddComponent<LineRenderer>();
		_lr.startWidth = 0.1f;
		_lr.endWidth = 0.1f;
		int waveResolution = 20;
		_lr.positionCount = waveResolution;
		// Construct a sine wave out of lines, the more lines (waveResolution) the smoother it gets
		for(int i = 0; i < waveResolution; i++) {
			float p = i / (float)waveResolution;
			_lr.SetPosition(i, new Vector2(-2.5f + p * 5.0f,  Mathf.Sin(p *  Mathf.PI * 4)));
		}
	}
	
	void Update () {
		Vector2 mousePos = Camera.main.ScreenToWorldPoint(Input.mousePosition);
		Vector2 closestPoint = Vector2.zero;
		float closestDistance = float.MaxValue;
		for(int i = 0; i < _lr.positionCount - 1; i++) {
			Vector2 p1 = _lr.GetPosition(i);
			Vector2 p2 = _lr.GetPosition(i + 1);
			var closestPointBetweenP1P2 = GetClosestPoint(p1, p2, mousePos);
			float distanceToClosestPoint = Vector2.Distance(closestPointBetweenP1P2, mousePos);
			if(distanceToClosestPoint < closestDistance) {
				closestDistance = distanceToClosestPoint;
				closestPoint = closestPointBetweenP1P2;
			}
		}
		circleSprite.position = closestPoint;
	}

	private Vector2 GetClosestPoint(Vector2 p1, Vector2 p2, Vector2 p3) {
		Vector2 from_p1_to_p3 = p3 - p1;
		Vector2 from_p1_to_p2 = p2 - p1;
		float dot = Vector2.Dot(from_p1_to_p3, from_p1_to_p2.normalized);
		dot /= from_p1_to_p2.magnitude;
		float t = Mathf.Clamp01(dot);
		return p1 + from_p1_to_p2 * t;
	}
}