👘
Linteum Character Kit
  • Linteum Character Kit
  • For Users
    • Quick Start Guide
    • Building a Character
    • Animating a Character
  • Advanced Topics
    • Working with Animations
    • Texture Atlasing In Depth
    • Advanced Features
    • Roadmap
  • Extending The System
    • Creating Compatible Clothing
    • Creating a Morph
    • Creating a Character Pack
    • Creating a Base Character
  • Scripting
    • Blending Characters At Runtime
    • Synchronizing Character & Clothing Morphs
  • Troubleshooting
    • Crumpled Meshes & Vertex Soup
    • Skin Surfacing Through Clothing
    • Shader Warnings
    • HDRP 2019 - Black Materials
    • Package Manager Dependencies
    • General Errata
Powered by GitBook
On this page

Was this helpful?

  1. Scripting

Blending Characters At Runtime

Here's a script guide designed to help you blend between multiple characters at runtime.

Blending Between Avatars (at Runtime)

If you are exposing the ability for users of your application to morph between two (or more) characters at runtime, it is essential to understand how to blend between avatars. Each avatar contains a set of definitions, such as how long each bone is, and the maximum extents allowed on each muscle group.

Creating new avatars at runtime is complex, and blending between two existing ones can be a substantially simpler and more reliable process. It is worth noting, however, that Unity generally does not like modifying Mecanim internals at runtime, and here be dragons.

This sample code only works in Unity 2019.1 or newer. Earlier versions lack the required Avatar.humanDescription property.

    static class LerpHumanoid
    {
        public static Avatar LerpAvatars(GameObject targetHierarchy, Avatar a, Avatar b, float t)
        {
            HumanDescription aH = default, bH = default;
#if UNITY_2019_1_OR_NEWER
            aH = a.humanDescription;
            bH = b.humanDescription;
#else
            throw new InvalidOperationException("This function requires Unity 2019.1 or newer.");
#endif
            var humanDescription = new HumanDescription
            {
                armStretch = Mathf.Lerp(aH.armStretch, bH.armStretch, t),
                feetSpacing = Mathf.Lerp(aH.feetSpacing, bH.feetSpacing, t),
                hasTranslationDoF = t > 0.5f ? bH.hasTranslationDoF : aH.hasTranslationDoF,
                legStretch = Mathf.Lerp(aH.legStretch, bH.legStretch, t),
                lowerArmTwist = Mathf.Lerp(aH.lowerArmTwist, bH.lowerArmTwist, t),
                upperArmTwist = Mathf.Lerp(aH.upperArmTwist, bH.upperArmTwist, t),
                lowerLegTwist = Mathf.Lerp(aH.lowerLegTwist, bH.lowerLegTwist, t),
                upperLegTwist = Mathf.Lerp(aH.upperLegTwist, bH.upperLegTwist, t)
            };

            var aHSkeleton = aH.skeleton;
            var aHHuman = aH.human;
            var bHSkeleton = bH.skeleton;
            var bHHuman = bH.human;

            var skeletonBones = new SkeletonBone[aHSkeleton.Length];
            var humanBones = new HumanBone[aHHuman.Length];

            for (int i = 0; i < humanDescription.skeleton.Length; i++)
            {
                if (aHSkeleton[i].name != bHSkeleton[i].name)
                {
                    throw new InvalidOperationException("Skeleton arrays do not match, were these produced from the same original skeleton?");
                }

                var skelement = new SkeletonBone();
                skelement.rotation = Quaternion.Lerp(aHSkeleton[i].rotation, bHSkeleton[i].rotation, t);
                skelement.position = Vector3.Lerp(aHSkeleton[i].position, bHSkeleton[i].position, t);
                skelement.scale = Vector3.Lerp(aHSkeleton[i].scale, bHSkeleton[i].scale, t);
                skelement.name = aHSkeleton[i].name;
                
                skeletonBones[i] = skelement;
            }

            for(int i = 0; i < humanDescription.human.Length; i++)
            {
                if (aHHuman[i].boneName != bHHuman[i].boneName)
                {
                    throw new InvalidOperationException("Skeleton arrays do not match, were these produced from the same original skeleton?");
                }

                var bone = new HumanBone
                {
                    boneName = aHHuman[i].boneName,
                    humanName = aHHuman[i].humanName,
                    limit = new HumanLimit
                    {
                        max = Vector3.Lerp(aHHuman[i].limit.max, bHHuman[i].limit.max, t),
                        axisLength = Mathf.Lerp(aHHuman[i].limit.axisLength, bHHuman[i].limit.axisLength, t),
                        center = Vector3.Lerp(aHHuman[i].limit.center, bHHuman[i].limit.center, t),
                        min = Vector3.Lerp(aHHuman[i].limit.min, bHHuman[i].limit.min, t),
                        useDefaultValues = t > 0.5f ? aHHuman[i].limit.useDefaultValues : bHHuman[i].limit.useDefaultValues
                    }
                };

                humanBones[i] = bone;
            }

            humanDescription.skeleton = skeletonBones;
            humanDescription.human = humanBones;

            return AvatarBuilder.BuildHumanAvatar(targetHierarchy, humanDescription);
        }
    }

To use this code, simply pass your two avatars into the function and use the generated result on your Animator.

Important note: The Animator needs to be reset each time you replace the avatar. The easiest way to do this is simply to delete and regenerate the component, but if this is not possible, callingAnimator.Rebind()will work too.

When you lerp the avatar, it is also important that you modify all the blend shapes for the character by equal measure. You can find the names of the blend shapes you want to edit using the Index visible in the SkinnedMeshRenderer property, or alternatively via skinnedMeshRenderer.sharedMesh.GetBlendShapeName()

PreviousCreating a Base CharacterNextSynchronizing Character & Clothing Morphs

Last updated 4 years ago

Was this helpful?

See for a script that you can possibly use for that task.

Synchronizing Character & Clothing Morphs