I've written this small code chunk to implement a very basic spline system for object motions. As I've only used C# on Unity3D so far I would appreciate feedbacks on this, especially on access levels, naming and the use of C# API. Most of this code is executed before runtime so I'm not really concerned about performance here. Feel free to comment on anything though, every advice/tips will be very appreciated.
Note: The tooltips are for designers with no programming and poor mathematical background that'll use these spline in the editor.
Thanks a lot for taking the time to read this! I hope there isn't too much code here, I decided to not put the class that handle the object movement on the spline to reduce the volume.
BezierCurve.cs
/*
* This class is used to store the informations of every point on the spline.
* These informations are calcultated at editor time and serialized.
*/
[System.Serializable]
public class BezierTickInformation
{
public Vector2 WorldPosition;
public Vector3 Rotation;
public BezierRotationMode RotationMode;
public float LinearSpeedFactor;
public float RotationSpeedFactor;
public BezierTickInformation()
{
WorldPosition = Vector2.zero;
Rotation = Vector3.zero;
RotationMode = BezierRotationMode.FollowSpline;
LinearSpeedFactor = 1;
RotationSpeedFactor = 1;
}
public BezierTickInformation(Vector3 position, Vector3 rotation,
BezierRotationMode rotationMode, float linearSpeed,
float rotationSpeed)
{
WorldPosition = position;
Rotation = rotation;
RotationMode = rotationMode;
LinearSpeedFactor = linearSpeed;
RotationSpeedFactor = rotationSpeed;
}
}
[System.Serializable]
public class BezierCurve : MonoBehaviour
{
//The length of the curve calculated at editor time
[HideInInspector]
[SerializeField]
private float length;
[HideInInspector]
[SerializeField]
private List<BezierTickInformation> TickInfosList;
[Tooltip("The time in seconds that an object at speed=1 will need to travel the whole spline")]
[SerializeField]
private float travelTime;
public float TravelTime { get { return travelTime; } }
#if UNITY_EDITOR
public List<BezierTickInformation> TickInfosList { get { return tickInfosList; } }
//For a better editor experience, the designer can hide the handles
public bool hideHandles;
[Tooltip("The number of generated points between two spline points.")]
[SerializeField]
private int precision = 30;
[SerializeField]
public List<BezierPoint> Points = new List<BezierPoint>();
//Calculate the position of a point between point1 and point2 with t in unit interval
Vector3 calculateBezierPoint(float t, Vector3 point1, Vector3 handlePoint1, Vector3 point2, Vector3 handlePoint2)
{
float u = 1.0f - t;
float tt = t * t;
float uu = u * u;
float uuu = uu * u;
float ttt = tt * t;
Vector3 p = uuu * point1;
p += 3f * uu * t * handlePoint1;
p += 3f * u * tt * handlePoint2;
p += ttt * point2;
return p;
}
public void calculateTickInformations(bool withPositionOffset = true)
{
Vector3 worldPositionOffset = (withPositionOffset ? transform.position : Vector3.zero);
TickInfosList = new List<BezierTickInformation>();
length = 0;
for (int p = 0; p < Points.Count - 1; ++p)
{
BezierPoint p1 = Points[p];
BezierPoint p2 = Points[p + 1];
Vector2 previousPosition = p1.Position;
float dt = (1f / precision);
for (int t = 0; t < precision; t++)
{
float proportion = dt * t;
//Calculate the position of this point in the bezier curve and add the offset
Vector2 tPos = calculateBezierPoint(proportion, p1.Position, p1.Handle2Position, p2.Position, p2.Handle1Position) + worldPositionOffset;
//Calculate the speed gained by acceleration (if any) and finaly store all these informations in a BezierTickInformation
float cumulSpeed = (p1.AccelerationMode == BezierAccelerationMode.Linear ? p1.Acceleraction * proportion : 0);
TickInfosList.Add(new BezierTickInformation(tPos, p2.Rotation, p1.RotationMode, p1.LinearSpeedFactor + cumulSpeed, p1.RotationSpeedFactor));
//Calcultate the length of the spline
length += Vector2.Distance(previousPosition, tPos);
previousPosition = tPos;
}
}
}
#endif
public BezierTickInformation getPointAtPercent(float unitInterval)
{
float distance = length * unitInterval;
float curLength = 0;
for (int p = 0; p < TickInfosList.Count - 1; ++p)
{
if (curLength > distance)
return TickInfosList[p];
curLength += Vector2.Distance(TickInfosList[p].WorldPosition, TickInfosList[p + 1].WorldPosition);
}
return new BezierTickInformation();
}
}
BezierPoint.cs
public enum BezierRotationMode
{
FollowSpline,
GoalRotation,
Static
}
public enum BezierAccelerationMode
{
None,
Linear
}
#if UNITY_EDITOR
//This class is used in the editor to draw the spline and keep all the necessary informations
//Informations used at runtime are stored in the BezierTickInformation class
[System.Serializable]
public class BezierPoint
{
//Spline parameters
[Tooltip("World position")]
public Vector3 Position = Vector3.one;
[Tooltip("First handle position")]
public Vector3 Handle1Position = Vector3.one;
[Tooltip("Second handle position")]
public Vector3 Handle2Position = Vector3.one;
//Target behaviour parameters
[Tooltip("Defines how the object will rotate on this portion of the spline.")]
public BezierRotationMode RotationMode;
[Tooltip("Defines how the object's speed will change on this portion of the spline.")]
public BezierAccelerationMode AccelerationMode;
[Tooltip("Target world roation, only considered if Rotation Mode = GoalRotation. By default, the object rotation follows the spline curve.")]
public Vector3 Rotation = Vector3.one;
[Tooltip("This factor will multiply the linear speed of the target on this portion of the spline.")]
public float LinearSpeedFactor;
[Tooltip("This factor will multiply the rotation speed of the target on this portion of the spline.")]
public float RotationSpeedFactor;
[Tooltip("Acceleration, only used if accelerationMode != None")]
public float Acceleraction;
public bool isEqual(BezierPoint p)
{
return Position == p.Position && Handle1Position == p.Handle1Position &&Handle2Position == p.Handle2Position;
}
public BezierPoint clone()
{
BezierPoint b = new BezierPoint();
b.Position = Position;
b.Handle1Position = Handle1Position;
b.Handle2Position = Handle2Position;
b.Rotation = Rotation;
b.AccelerationMode = AccelerationMode;
b.Acceleraction = Acceleraction;
return b;
}
}
#endif