Unity realizes the dress-up function in the form of shared bones

1. Another way of thinking

 Another way to change the character clothing model (with Mesh), see this blog: https://blog.csdn.net/f_957995490/article/details/108985716

2. Model

 The display of the character model relies on the skeleton and the skin on it. It is similar to the components of our human body, with bones and skin (of course, organs, nerves, etc.).
 If we need to make an action, we must control each part of our body to act accordingly. The same is true for the model. The character animation system will control the movement of each key bone, and the engine will complete the movement trajectory of the middle bone and the skin deformation process on the bone for us.
Model space for prefabs
 As you can see, all models can be divided into two parts, the skeleton tree and the model. Our model is tied to its own bone tree, in other words, the bone tree contains all the bone nodes needed on the current model.

三、SkinnedMeshRenderer

 In unity, the skeleton and skin are integrated into one component - SkinnedMeshRenderer.
SkinnedMeshRenderer

 Of course, the function of SkinnedMeshRenderer is very powerful. This article only introduces a few items needed for skinning:
1. RootBone
 This is the root bone of the bone corresponding to the model binding, which can be seen intuitively in the Inspector window. Then it is also allowed to change the parameters by dragging
2. Bones
 This is all the bones bound to the model, which cannot be seen in the Inspector window, and can only be obtained through code. Of course, it is allowed through code modified bones.

gameObject.GetComponent<SkinnedMeshRenderer>().bones

 What you get here is an array of all bound bones. The array data takes RootBone as the root node, and is traversed depth-first.

4. The idea of ​​changing clothes (this article splits the characters into five parts, hair, head, upper body, lower body, feet)

 Bind all the used clothing models to a set of skeleton trees like building blocks. If any part needs to be replaced, remove the corresponding part and tie it up for replacement.
 First load six parts in the scene, the total bone tree, hair model, head model, upper body model, lower body model and foot model. Traverse the five part models to obtain the root bone (RootBone) and all bound bone nodes (bones) to be bound, and replace them with the bone nodes with corresponding names on the total bone tree. Finally, destroy the useless bone trees in each part. Such a character model is completed. Since the character generated in this form is composed of sub-objects of five parts and a bone tree, when changing clothes, that is, when changing the clothing model, you only need to find the corresponding parts Just destroy the model and replace the bone binding of the model.

5. Model preparation

Prepare a bone tree (hereinafter referred to as the most  complete bone tree) including all the bone nodes required by the model and all replacement clothing models.
 Because we use the most complete skeleton tree as the parent node of the entire character model, we hang the animator animation component on it. If you need to set the animation mode to CullUpdateTransforms, you also need to mount an empty MeshRenderer. For specific reasons, please refer to: https://blog.csdn.net/xinzhilinger/article/details/110673046 , so I won’t go into details here.
The most complete skeleton tree

6. Implementation process

 Generate all six prefabs (full skeletal tree, hair, head, upper body, lower body, and feet), and parent the five clothing parts that need to be replaced into the skeletal tree.
 Call the script to splice the character model (pass in the most complete skeleton tree):
Note : the root node of the most complete skeleton tree needs to be named "DM_ACTOR_BIND"

public class UCombineSkinnedMgr
    {
    
    
        //骨骼树下所有骨骼节点
        Dictionary<string, Transform> transforms = new Dictionary<string, Transform>();

        /// <summary>
        /// 使用共享骨骼的方法
        /// 只合骨骼信息
        /// </summary>
        /// <param name="skeleton">传入的骨骼</param>
        public void CombineObject(GameObject skeleton)
        {
    
    
            //遍历获取骨骼树下所有骨骼节点
            Transform tran = skeleton.transform;
            Transform rootBone = tran.Find("DM_ACTOR_BIND");
            Transform[] allbones = rootBone.GetComponentsInChildren<Transform>(true);
            for (int i = 0; i < allbones.Length; i++)
            {
    
    
                transforms[allbones[i].name] = allbones[i];
            }

            //共享骨骼
            SetBonesSharing(tran);
        }

        /// <summary>
        /// 设置Mesh骨骼节点
        /// 允许每个部件的骨骼根节点不同
        /// </summary>
        /// <param name="target">目标对象</param>
        private void SetBonesSharing(Transform target)
        {
    
    
            SkinnedMeshRenderer[] targetSkin = target.GetComponentsInChildren<SkinnedMeshRenderer>(true);
            for (int i = 0; i < targetSkin.Length; i++)
            {
    
    
                targetSkin[i].receiveShadows = false;
                if (null != targetSkin[i].rootBone)
                {
    
    
                    //替换骨骼根节点
                    Transform tf = GetBoneObject(targetSkin[i].rootBone.name);
                    if (targetSkin[i].rootBone == tf)
                    {
    
    
                        //增加效率,如果已经替换过骨骼节点,就不需要再次替换
                        continue;
                    }
                    else
                    {
    
    
                        targetSkin[i].rootBone = tf;
                    }
                }
                Transform[] tfs = new Transform[targetSkin[i].bones.Length];
                for (int j = 0; j < targetSkin[i].bones.Length; j++)
                {
    
    
                    //替换所有骨骼节点
                    if (null == targetSkin[i].bones[j])
                    {
    
    
                        //如果骨骼节点为null
                        Debug.LogWarning(targetSkin[i].name + "  " + j);
                    }
                    else
                    {
    
    
                        tfs[j] = GetBoneObject(targetSkin[i].bones[j].name);
                    }
                }
                targetSkin[i].bones = tfs;
            }

            //删除加载的原有的骨骼节点
            Transform tran;
            for (int i = 0; i < targetSkin.Length; i++)
            {
    
    
                tran = FindChildInTransfromAllChild(targetSkin[i].transform.parent, "DM_ACTOR_BIND");
                if (tran != null)
                {
    
    
                    Object.Destroy(tran.gameObject);
                }
            }
        }

        /// <summary>
        /// 获取共享骨骼节点
        /// </summary>
        /// <param name="name"></param>
        /// <returns></returns>
        private Transform GetBoneObject(string name)
        {
    
    
            if (transforms.ContainsKey(name))
            {
    
    
                return transforms[name];
            }

            Debug.LogWarning("bone: " + name + " is missing");
            return null;
        }

        /// <summary>
        /// 查找所有父物体下物体
        /// </summary>
        /// <param name="transform"></param>
        /// <param name="name"></param>
        /// <returns></returns>
        private Transform FindChildInTransfromAllChild(Transform transform, string name)
        {
    
    
            if (transform == null)
            {
    
    
                return null;
            }
            Transform[] childArray = transform.GetComponentsInChildren<Transform>(true);
            List<Transform> childList = childArray.ToList();
            return childList.Find(value => value.name == name);
        }
    }

7. Example project

 Subsequent uploads

Guess you like

Origin blog.csdn.net/weixin_47819574/article/details/130953535