Using Splines to Animate in Unity
Basics of Bezier Curves
GL (left green point) is moving at a constant speed from P0 to P1
GR (right green point) is moving at the same constant speed from P1 to P2
a black dot is moving from GL to GR at the same constant speed.
P0 is starting position.
P2 is the ending position.
P1 is the handle.
Adding it to Unity with Custom Editor Script
*note that bezier curves often use 2 handles, but since I'm only going to use these splines for animations I combined the two to make it easier to work with.
If I want to get more granular control I will simply add another segment
SplinimatorEditor.cs
//if there is only one defined point, dont draw any curves
if (splinimator.segments.Count < 1) return;
//draw a curve between the starting position and the first point
Handles.DrawBezier(splinimator.transform.position,
splinimator.segments[0].endPoint, splinimator.segments[0].handle,
splinimator.segments[0].endPoint, Color.green, null, 5f);
//for each spline segment
for (int i = 1; i < splinimator.segments.Count; i++)
{
//define the points
ThisEndPoint = splinimator.segments[i].endPoint
LastEndPoint = splinimator.segments[i - 1].endPoint
ThisHandle = splinimator.segments[i].handle
//draw the curve
Handles.DrawBezier(ThisEndPoint, LastEndPoint, ThisHandle,
LastEndPoint, Color.green, null, 5f);
}
Animating Objects Along the Curve
Since the fundamentals of bezier curves are essentially just a few points lerping between each other, I treat it as such.
Note that although I could've just written this code in one long line, making it compact isn't a sustainable practice for code readability.
Splinimator.cs
// get the start, handle, and end positions
Vector3 p1;
if (currentSegment == 0) // if it is the first segment
p1 = originalPosition;
// if it is not the first segment, ake it the previous segments end point
else
p1 = segments[currentSegment - 1].endPoint;
Vector3 h = segments[currentSegment].handle; // handle position
Vector3 p2 = segments[currentSegment].endPoint; // point 2 position
// get the % of time through the segment
//(1 second of a 2 second segment = 50%)
float timePercent = time / segments[currentSegment].time;
// lerp bridges
Vector3 p1_h = Vector3.Lerp(p1, h, timePercent);
Vector3 h_p2 = Vector3.Lerp(h, p2, timePercent);
// lerp target
target.position = Vector3.Lerp(p1_h, h_p2, timePercent);
Changing Rotation
Lerping the rotation of the target object is probably the simplest part of all this.
I simply check if the spline wants to change rotation, if it does, I lerp from the starting rotation to the new rotation in the time span of that segment.
Splinimator.cs
// if the segment uses rotation, lerp the rotation
if (!segments[currentSegment].useRotation)
return;
Quaternion startRotation;
if (currentSegment == 0) // if it is the first segment
startRotation = transform.rotation;
else // if it is not the first segment, set start rotation to previous one
startRotation = Quaternion.Euler(segments[currentSegment - 1].rotation);
// define the end rotation
Quaternion endRotation = Quaternion.Euler(segments[currentSegment].rotation);
// lerp to rotation
target.rotation = Quaternion.Lerp(startRotation, endRotation, timePercent);
// [timePercent defined in about snippet]
Adding Extra Features
Linear Segments
Adding a bool that dictates whether or not the handle should be at the same position as the end point is a simple way of making the path linear
Method Invoking
Adding a couple of UnityEvents to each spline segment for methods to call during, and at the end of the segment makes calling external functions synchronized with animations easy.
Looping
Looping is also a super easy addition, as soon as the time is passed in the final segment I just reset the current time and segment number to reset the animation.
Download
Subscribe to my newsletter
Read articles from Lastered directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by