Procedural Shark Bones

During the development of The Living Reef an additive posing system was used for animating the fish. Each frame I would calculate the yaw and pitch deltas and then use this to change the weighting of two additive pose animation layers that were on top of a swimming layer.

This solution works well on most fish as it’s a subtle effect to begin with. However, the effect begins to break down when a fish is long and narrow, such as with a shark.

To overcome this, I realised I had to develop a new procedural system for the sharks that would work in conjunction with the existing swim animations.

After watching shark videos on youtube, it appeared that the tail followed through from the rest of the body when they swam, so I decided to track a bone in the head over time. When this trail was long enough it formed a spline of where the shark had been. I then best fit mapped the bones in the tail to their corresponding position on the spline while maintaining the bone structure. 

Additionally, the shark needed to look at where it was headed by rotating each bone to face the look at vector over a period of time. It also needed to add the existing head animations. This required converting from world space rotations to local and then adding the stored animations. Below is the code I wrote to achieve the head look at.

// Head look at
void Start()
    localBoneRotationSave = new List<Quaternion>();

    foreach(Transform bone in bones)

// Update is called once per frame
void LateUpdate()

    for (int i = 0; i < bones.Count; i++)
        Vector3 lookAtDir = (target.position - bones[i].position).normalized;

            lookAtDir.y = 0;
        else if(lookDirection==LookAtDirection.Vertical)
            lookAtDir.x = 0;

        Quaternion rot = Quaternion.LookRotation(lookAtDir) * Quaternion.Euler(rotationOffset);
        Quaternion local = Quaternion.Inverse(bones[i].transform.rotation) * rot;
        Quaternion maxRotation = Quaternion.RotateTowards(bones[i].localRotation, local, maxRotationRate[i]);//clamp rotation to a max degree

        //need to save changes over time
        localBoneRotationSave[i] = Quaternion.Slerp(localBoneRotationSave[i], maxRotation, Mathf.Clamp01(Time.deltaTime * speed)); //smoothly lerp to clamped position

        //additive with the animation
        bones[i].localRotation = localBoneRotationSave[i] * bones[i].localRotation; //profit


Leave a Reply

Your email address will not be published. Required fields are marked *