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.
staticclassLerpHumanoid {publicstaticAvatarLerpAvatars(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;#elsethrownewInvalidOperationException("This function requires Unity 2019.1 or newer.");#endifvar humanDescription =newHumanDescription { 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 =newSkeletonBone[aHSkeleton.Length];var humanBones =newHumanBone[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 =newSkeletonBone();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 =newHumanBone { boneName =aHHuman[i].boneName, humanName =aHHuman[i].humanName, limit =newHumanLimit { 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;returnAvatarBuilder.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()