Skip to content

Spline Tools

A novel system for crating animations and pathing using Splines/bezier curves in the Unity 3D engine. Originally developed to compliment the FOE MMO/MOBA combat systems. A version of the complete project is available here

Huge thanks to Freya Holmer for their incredible articles and presentations on bezier curves which heavily inspired this system.

Dummy image

Editor view

Motivation

Inspired by this video-essay: How the fundamentals of animation apply to game design with League of Legends

Warning

Warning: depicts some anime violence - skip to 1:30 to bypass.

Goals

  1. Reduce difficulty prototyping complex ability interraction chains

  2. getting meaningful logging data

  3. Create tools to implement essential principles of animation in the movement of non-ambulatory actors

  4. Ability to randomly seed/generate abilities and animations to kick-start the creative process

Examples

  • "Justice rains from above..."

Recreating Pharah from Overwatch's Ultimate using random spline reflection
  • loopy loops

object showing the "orbit" option to rotate around the spline path, then switching to a direct flight path, then adding a status effect to the target
  • Editor and Game views

    Example

    Switching between the game and Editor views while an animation plays. This shows the custom editor gizmos created to help customize spline paths and animations

  • Spline Manager Editor

    Example

    Dummy image

  • Spline Profile Editor

    Example

    Dummy image

  • Editor Gizmos in-scene

    Example

    Dummy image

Maths

Heres what the math for all that looks like in code.

Basic Spline
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
    // Basic
    // returns a vector3 point at <t> position on a spline
    public static Vector3 GetPoint(Vector3 p0, Vector3 p1, Vector3 p2, Vector3 p3, float t)
    {
        t = Mathf.Clamp01(t);
        float oneMinusT = 1f - t;
        return
            oneMinusT * oneMinusT * oneMinusT * p0 +
            3f * oneMinusT * oneMinusT * t * p1 +
            3f * oneMinusT * t * t * p2 +
            t * t * t * p3;
    }
Advanced Spline
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
    // Advanced
    // return a point on a given spline at <t> based on several modfied behavior patterns.

    public static void TraverseSpline(GameObject Source, GameObject Target, CreateSplineProfile Spline, GameObject ObjectToMove, float T)
    {

        if (Spline.Orbit) // for oribiting the spline
        {
            Vector3 Center = GetPoint(Spline.Points[0], Spline.Points[1], Spline.Points[2], Spline.Points[3], T);
            Vector3 TangentVector = GetDirection(T, Spline, Source.transform);
            Vector3 Binormal = GetBiNormal(TangentVector);
            Vector3 NewPoint = GetOrbitPoint(Center, TangentVector, Binormal, Spline, T);
            ObjectToMove.transform.position = NewPoint;
        }

        if (Spline.OscillateH) // horizontal Oscillation
        {
            Vector3 Center = GetPoint(Spline.Points[0], Spline.Points[1], Spline.Points[2], Spline.Points[3], T);
            Vector3 TangentVector = GetDirection(T, Spline, Source.transform);
            Vector3 Binormal = GetBiNormal(TangentVector);
            Vector3 NewPoint = OscillateH(Center, Binormal, Spline.OscillationRangeH, Spline, T);
            ObjectToMove.transform.position = NewPoint;
        }

        if (Spline.OscillateV) // Vertical Oscillation
        {
            Vector3 Center = GetPoint(Spline.Points[0], Spline.Points[1], Spline.Points[2], Spline.Points[3], T);
            Vector3 TangentVector = GetDirection(T, Spline, Source.transform);
            Vector3 Binormal = GetBiNormal(TangentVector);
            Vector3 Normal = GetNormal(TangentVector, Binormal);
            Vector3 NewPoint = OscillateV(Center, Normal, Spline.OscillationRangeV, Spline, T);
            ObjectToMove.transform.position = NewPoint;
        }

        if (Spline.FollowSpline) // follows the Spline Exactly
        {
            Vector3 NewPoint = GetPoint(Spline.Points[0], Spline.Points[1], Spline.Points[2], Spline.Points[3], T);
            ObjectToMove.transform.position = NewPoint;
        }

        UpdateSpline(Spline, Source, Target);

    }
Moving an object
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
    // moves an object along a spline
    public IEnumerator<float> MoveObject( float duration)
    {
        float startTime = Time.time;

        float Distance1 = Spline.SplineLength / duration;
        float Distance2 = Vector3.Distance(gameObject.transform.position, Spline.Points[3]);

        while (Time.time < startTime + (duration * Spline.SplineScale))
        {
            Distance2 = Vector3.Distance(gameObject.transform.position, Spline.Points[3]);
            float DistanceFraction = Distance2 / Distance1;
            T = Mathf.Lerp(0, 1, (Time.time - startTime) / (duration * Spline.SplineScale));
            SplineMaker2.TraverseSpline(Spline.Caster, Spline.Victim, Spline, Mover, T);
            yield return 0f;
        }
        Destroy(Mover);
        AttackChecklist.Callback();
    }