How to move an object along a Spline (SpriteShape)

Is there a way to get detailed information about the shape of a Spline in a SpriteShapeController? Not just info about the control points, but the specifics of the curved parts that run in-between the control points?

I have some objects that need to follow a fixed, curving path. I’ve drawn this curved path, in the Editor, using an open-ended SpriteShape. I had thought to write a script that would read path info from the Spline, and then move my objects along that path. While I understand how to get data on the control points (GetPosition, GetLeftTangent, GetRightTangent), I don’t see a way to get info on the parts of the spline in-between the control points. Are there hidden, interpolated points that sit between the control points? Can I get the positions of those interpolated points as a Vector3 array? If not, is there some other way to get the full path info out of a Spline, so that I can procedurally make my objects follow a SpriteShape path?

5 Likes

bump

@Venkify can you offer any insight?

@Venkify , I would very very much like to find out as well.

Please take a look at Sprinker.cs from the Extras available with the package. Here is a quick sample code :

// The following code instantiates a prefab in a random location between control points 1 and 2.
SpriteShapeController ssc = GetComponent<SpriteShapeController>();
Spline spl = ssc.spline;
var i = 2;
var go = GameObject.Instantiate(m_Prefab);

float2 _p0 = new float2( spl.GetPosition(i - 1).x, spl.GetPosition(i - 1).y );
float2 _p1 = new float2( spl.GetPosition(i).x, spl.GetPosition(i).y );
float2 _rt = _p0 + new float2( spl.GetRightTangent(i - 1).x, spl.GetRightTangent(i - 1).y );
float2 _lt = _p1 + new float2( spl.GetLeftTangent(i).x, spl.GetLeftTangent(i).y );

float r = Random.Range(0.5f, 0.95f);
float2 bp = BezierPoint(_rt, _p0, _p1, _lt, r);
go.transform.position = new Vector3( bp.x, bp.y, 0);
2 Likes

Oh. Ok,… but my goodness. I think there should br a simple api that accepts either relative distance of 0 to 1 or absolute distance and return position…

2 Likes

I second that notion, interpolation would be great!

+10
That example is super helpful. Still, the point of a Spline class is to let us do spline stuff without having to write our own math.

1 Like

I’m having a bit of a struggle getting access to “float2” and the BezierPoint, is there something I need to import?

Unity.Mathematics for float2
UnityEngine.U2D for BezierUtility.BezierPoint

BezierUtility takes Vector3, so it becomes:

 go.transform.position = BezierUtility.BezierPoint(
           new Vector3(_p0.x, _p0.y, 0),
           new Vector3(_rt.x, _rt.y, 0),
           new Vector3(_lt.x, _lt.y, 0),
           new Vector3(_p1.x, _p1.y, 0),
           r
);

I made an extension method that may help. It allows you to get the local position along the spline based on a value of 0 - 1. It could probably be a bit cleaner but it works for me, you can see it working here. The circle sprite is a child of the spline.

7747068--974436--cableanimation.gif

using UnityEngine;
using UnityEngine.U2D;

public static class SplineEx
{
    /// <summary>
    /// Returns the local position along the spline based on progress 0 - 1.
    /// Good for lerping an object along the spline.
    /// <para></para>
    /// Example: transform.localPosition = spline.GetPoint(0.5f)
    /// </summary>
    /// <param name="spline"></param>
    /// <param name="progress">Value from 0 - 1</param>
    /// <returns></returns>
    public static Vector2 GetPoint(this Spline spline, float progress)
    {
        var length = spline.GetPointCount();
        var i = Mathf.Clamp(Mathf.CeilToInt((length - 1) * progress), 0, length - 1);
  
        var t = progress * (length - 1) % 1f;
        if (i == length - 1 && progress >= 1f)
            t = 1;

        var prevIndex = Mathf.Max(i - 1, 0);
  
        var _p0 = new Vector2(spline.GetPosition(prevIndex).x, spline.GetPosition(prevIndex).y);
        var _p1 = new Vector2(spline.GetPosition(i).x, spline.GetPosition(i).y);
        var _rt = _p0 + new Vector2(spline.GetRightTangent(prevIndex).x, spline.GetRightTangent(prevIndex).y);
        var _lt = _p1 + new Vector2(spline.GetLeftTangent(i).x, spline.GetLeftTangent(i).y);

return BezierUtility.BezierPoint(
   new Vector2(_rt.x, _rt.y),
   new Vector2(_p0.x, _p0.y),
   new Vector2(_p1.x, _p1.y),
   new Vector2(_lt.x, _lt.y),
   t
);
    }
}
9 Likes

Thank you very much for this! Really helpful.

1 Like

FYI, in com.unity.2d.spriteshape: 7.0.7 they’ve swapped the params in BezierUtility.BezierPoint so it now needs to be

return BezierUtility.BezierPoint(
   new Vector2(_rt.x, _rt.y),
   new Vector2(_p0.x, _p0.y),
   new Vector2(_p1.x, _p1.y),
   new Vector2(_lt.x, _lt.y),
   t
);

The rest of the code is the same. I will edit the initial script for anyone in future who come across this thread.

2 Likes

Is there a way to approximate the time of an object along the Spline path?

I guess getting the length of spline divide by the speed?

1 Like

Unfortunately, none of those methods take into account the Height parameter of control points. It’s really puzzling why this isn’t an integral part of the package, but instead we have to collect pieces of code from examples and forums for such basic functionality…

1 Like

In recent version (10.0.0) we have added a script to make placement of objects easier on the SpriteShape through SpriteShapeObjectPlacement component. Please find it under com.unity.2d.spriteshape\Runtime\SpriteShapeObjectPlacement.cs

Hopefully this also helps as a reference for object movement / placement etc…

We will consider adding support for this in an upcoming version. Thanks for posting it.

2 Likes

@Venkify Can you please consider adding general purpose methods for querying the spline? Position along the spline at any distance (not just control points), normal and tangent vectors, closest point on the spline? That would be much more useful in practice than the rigid implementation in SpriteShapeObjectPlacement. Main problem of SpriteShape, for me at least, is that it is very hard to customize and build things on top of it, since you practically have no data to operate on, it’s a black box. I have to write all the spline calculations on my own.

On that subject, Unity already has a Spline package that does everything I’ve mentioned and beyond:
https://docs.unity3d.com/Packages/com.unity.splines@2.2

Is there a possibility that SpriteShape will utilize it at some point? That would literally solve all the problems…

1 Like

Is it possible to back-port this to LTS?
We are using 2021.3 and the version can’t be changed because of other factors.

This would be very handy in LTS! :slight_smile:

Thanks for all your answers!
Maybe a better way (where progress is a more accureate linear % along the path’s streightened length) can be done with converting U2D.Spline to UnityEngine.Splines.Spline ( Namespace UnityEngine.Splines | Splines | 2.0.0 )

        _spline = new UnityEngine.Splines.Spline();
        for (int i = 0; i < spline.GetPointCount(); i++)
        {
            _spline.Add(new BezierKnot(spline.GetPosition(i), spline.GetLeftTangent(i), spline.GetRightTangent(i)));
        }

and then evaluating _spline at progress:

 public static Vector2 GetPoint(Spline spline, float progress)
    {
        var eval = spline.EvaluatePosition(progress);
        return new Vector2(eval.x, eval.y);
}
2 Likes