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?
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);
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…
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.
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
);
}
}
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…
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.
@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.
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);
}