Synchronizing Character & Clothing Morphs

This page covers how to synchronize morphs between both clothing and character models in the Linteum Character Tool.

The following script can be attached to somewhere on your character hierarchy to help synchronize blend shapes across a range of objects automatically. You simply need to drag the "source" SkinnedMeshRenderer (the one you will be actually animating), and all the "target" Skinned Mesh Renderers into the component fields, and it will automatically copy the source values to the target each and every frame, ensuring they stay in sync.

[ExecuteInEditMode]
    public class BlendShapeSynchroniser : MonoBehaviour
    {
        public SkinnedMeshRenderer BaseRenderer;
        public SkinnedMeshRenderer[] Targets;

        private int[,] _targets;
        private bool _setup;

        private void OnEnable()
        {
            RebuildTargets();
        }

        private void OnValidate()
        {
            RebuildTargets();
        }

        public void RebuildTargets(bool force = false)
        {
            if (_setup && !force)
                return;

            if (BaseRenderer != null && BaseRenderer.sharedMesh != null)
            {
                var blendShapeCount = BaseRenderer.sharedMesh.blendShapeCount;

                _targets = new int[Targets.Length, blendShapeCount];
                
                for (int i = 0; i < Targets.Length; i++)
                {
                    for (int j = 0; j < blendShapeCount; j++)
                    {
                        _targets[i, j] = -1;
                        if (Targets[i] != null && Targets[i].sharedMesh != null)
                        {
                            for (int k = 0; k < Targets[i].sharedMesh.blendShapeCount; k++)
                            {
                                if (Targets[i].sharedMesh.GetBlendShapeName(k) ==
                                    BaseRenderer.sharedMesh.GetBlendShapeName(j))
                                {
                                    _targets[i, j] = k;
                                    break;
                                }
                            }
                        }
                    }
                }

                _setup = true;
            }
        }

        private void LateUpdate() // Animation updates generally should go into LateUpdate to allow other things to affect the item first.
        {
            var targetCount = _targets.GetLength(0);
            var blendShapeCount = _targets.GetLength(1);

            for (int i = 0; i < targetCount; i++)
            {
                for (int j = 0; j < blendShapeCount; j++)
                {
                    var targetBlendShapeIdx = _targets[i,j];
                    if (targetBlendShapeIdx >= 0)
                    {
                        Targets[i].SetBlendShapeWeight(targetBlendShapeIdx, BaseRenderer.GetBlendShapeWeight(j));
                    }
                }
            }
        }
    }

Last updated