Using Splines to Animate in Unity

Using Splines to Animate in Unity

Because I despise the default editor

·

3 min read

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