一、另一种思路

 另一种更换人物服饰模型的方式(合Mesh),参看此篇博客:

二、模型

 人物模型的显示所依靠的是骨骼与其上的蒙皮。就类似于我们人体组成部分,有骨头,有皮肤(当然还有器官、神经等等)。

 如果我们需要做出动作的话,就要控制自身各部位做出相应行为。那么模型也是如此,人物动画系统将会控制各关键骨骼的运动,引擎会为我们补全中间骨骼的运动轨迹,及骨骼上的蒙皮形变过程。

unity 查看骨骼绑定插件 unity查看人物骨骼_DM


 可以看到,所有的模型可以分为两个部分,骨骼树和模型。我们模型就是绑在自身的骨骼树上的,换句话说,骨骼树中包含了当前模型上所需要的所有骨骼结点。

三、SkinnedMeshRenderer

 在unity中将这骨骼与蒙皮整合成了一个组件——SkinnedMeshRenderer。

unity 查看骨骼绑定插件 unity查看人物骨骼_unity 查看骨骼绑定插件_02

 当然,SkinnedMeshRenderer的功能是非常强大的,本文只介绍换装所需用到的几项:
1、RootBone
 这是该模型绑定所对应骨骼的根骨骼这在Inspector视窗下是可以直观看到的,到然后也是允许通过拖动来改变参数
2、bones
 这是该模型所绑定的所有骨骼,这在Inspector视窗下是无法看见的,只能通过代码的形式进行获得,当然,它是允许通过代码对骨骼进行修改的。

gameObject.GetComponent<SkinnedMeshRenderer>().bones

 这里你所获得的就是由所有绑定的骨骼所组成的数组。数组数据以RootBone为根结点,深度优先进行遍历所得。

四、换装的实现思路(本文将人物拆分为了五个部位,头发,头,上身,下身,脚)

 把所有用到的服饰模型像拼积木一样全部绑到一套骨骼树上,哪个部位需要更换,就把对应的部位拆下来绑上替换的。
 首先在场景中加载六个部分,总骨骼树、头发模型、头模型、上身模型、下身模型和脚模型。分别遍历五个部位模型获取需要绑定的根骨骼(RootBone)及所有绑定的骨骼结点(bones),将之替换成总骨骼树上对应名称的骨骼结点。最后将各部位中已经没有用的骨骼树销毁。这样一个人物模型就拼好了,由于以这种形式生成的人物是由五个部位的子物体及骨骼树组成的,所以在换装,也就是替换服饰模型的时候,只需要找到对应的部位模型进行销毁和替换模型的骨骼绑定就行了。

五、模型准备

 准备一个包括了所有模型所需骨骼结点的骨骼树(下文称最全骨骼树)和所有替换用服饰模型。

 因为我们将最全骨骼树作为整个人物模型的父节点,所以在其上挂animator动画组件。如果你需要将动画模式设置为CullUpdateTransforms,那么还需要挂载一个空的MeshRenderer。具体原因可以参看:,此处不多赘述。

unity 查看骨骼绑定插件 unity查看人物骨骼_结点_03

六、实现过程

 将所有六个预制体生成(最全骨骼树、头发、头、上身、下身和脚),并将五个需要替换的服饰部件父物体设置成骨骼树。
 调用脚本拼接人物模型(传入最全骨骼树):
注意:最全骨骼树的根结点命名需要为“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);
        }
    }

七、示例工程

 后续上传