Unity换装效果

Unity换装效果


前言:在我们游戏的角色中,我们点击按钮可以为自己的角色改变外观。然而我们建好的3D模型,如果要将其中的一个部位换成另一个形状。最直接的就是将该物件部位的Mesh替换掉。那么我们的外观改变了,但是这种方法如果运用到需要动作的模型上,将会发现被置换的部位不会正常的工作。所以直接改变Mesh的方法之适用于静态模型物件。为此,我们必须找到更好的方法。

一:换装的原理:                                                    

首先我们需要了解模型的结构:当我们把人物的预设拖到场景中,在Hierarchy视窗将物件展开来,会发现几个名称相同并使用数字来区别的物件。它们分别代表着人物的模型。所以,整个人物的模型当中包含着多个相同部位的模型。而Female_Bip01则是整个人物的骨架结构。人物的动作则是设置在FemaleAvatar的Aniamtion,所以这个模型只是一个模型资源,而不是实际上要放到场景中的目标物件。

原理:说白了就是我们拿到原始的模型,然后再按照需求将各个部位重新组合成一个新的目标模型。首先我们需要原模型和目标骨架。我们设置为私有(进行动态的加载)我们将进行一步步的编码。

    private Transform _source;                      //模型资源的位置
    private Transform _target;                      //目标骨架的位置

我们声明一个方法进行动态的加载,当我们把原模型在Hierarchy面板中声明出来后就可以进行隐藏了,因为我们需要进行模型的重组。把位置信息进行赋值。

    /// <summary>
    /// 动态加载原模型资源
    /// </summary>
    private void LoadSourceModel()
    {
        GameObject femaleAvatar = Instantiate(Resources.Load("FemaleAvatar") as GameObject);
        GameObject targetModel = Instantiate(Resources.Load("targetmodel") as GameObject);
        if (femaleAvatar != null)
        {
            _source = femaleAvatar.transform;
            femaleAvatar.SetActive(false);
        }
        if (targetModel != null)
        {
            _target = targetModel.transform;
        }
    }

这时候我们需要想两个问题,我们把原模型中的各个部位的信息存放在哪里呢?我们重组的之后模型的信息放在哪里呢?这时候我们需要声明两个集合来存储各个部位的信息。

    /** 
     * 目标位置各部件的 SkinnedMeshRenderer
     * */
    private readonly Dictionary<string, SkinnedMeshRenderer> _targetSkinnedMeshRenderers = new Dictionary<string, SkinnedMeshRenderer>();
    /**
     *  模型资源资料 Key:string:对应部位  Value: string:对应编号  Transform:具体的皮肤位置
     * */
    private readonly Dictionary<string, Dictionary<string, Transform>> _datas = new Dictionary<string, Dictionary<string, Transform>>();

我们把目标骨架的位置也复制一份存放在数组中:

    private Transform[] _hips;                      //目标物件的骨架
    void Start()
    {
        //从目标物体取得骨架资料
        _hips = _target.GetComponentsInChildren<Transform>();
    }

 

接下来需要得到模型皮肤的各种信息,并且存储到集合中。

    /// <summary>
    /// 得到模型皮肤并且存储到集合中
    /// </summary>
    /// <param name="source"></param>
    private void GetModelSkinnedMeshRenderer(Transform source)
    {
        //参数的检查
        if (source == null) return;
        //取出原模型资源的各部位的SkinnedMeshRenderer并且放在数组中进行存储
        SkinnedMeshRenderer[] skinnedMeshRenderers = source.GetComponentsInChildren<SkinnedMeshRenderer>(true);
        foreach (SkinnedMeshRenderer skinnedMeshRenderer in skinnedMeshRenderers)
        {
            //利用string.Split('')进行字符串的分割
            string[] partName = skinnedMeshRenderer.name.Split('-');
            //存储在集合中
            //如果当前的集合中没有这个键时
            if (!_datas.ContainsKey(partName[0]))
            {
                //添加部位的名称
                _datas.Add(partName[0], new Dictionary<string, Transform>());
                //创建新的GameObject并使用部位名称来命名,指定为目标物件的子物体
                GameObject partObj = new GameObject();
                //新建的物体进行重新的命名
                partObj.name = partName[0];
                //设置父节点
                partObj.transform.parent = _target;
                //为新建立的GameObejct加入SkinnedMeshRenderer 并放入到集合中
                //我们新创建的各个部位的节点并添加皮肤组件,但是这时候并没有进行赋值
                _targetSkinnedMeshRenderers.Add(partName[0], partObj.AddComponent<SkinnedMeshRenderer>());
            }
            //如果存在键的话进行值的添加
            _datas[partName[0]].Add(partName[1], skinnedMeshRenderer.transform);
        }
    }

进行换装的方法,首先我们需要得到要替换的皮肤的信息,我们需要取得相对用名称的骨架列表来建立新的骨架列表。不能直接把骨骼赋值过去。我们需要一个集合去存储当前要替换的皮肤的骨架列表。然后进行指定部位的更新——1.更新网格  2.更新骨架  3.更新材质。

    /// <summary>
    /// 进行换装
    /// </summary>
    /// <param name="part">要改变的部位</param>
    /// <param name="item"></param>
    private void ChangePart(string part, string item)
    {
        //取得当前需要替换的皮肤
        SkinnedMeshRenderer skinned = _datas[part][item].GetComponent<SkinnedMeshRenderer>();

        //取得相对应名称的骨架物件来建立新的骨架列表 不能直接把骨骼赋值过去
        List<Transform> bones = new List<Transform>();
        //bones:用于蒙皮网格的骨骼列表
        foreach (Transform bone in skinned.bones)
        {
            foreach (Transform hip in _hips)
            {
                if (hip.name != bone.name) continue;
                bones.Add(hip);
                break;
            }
        }

        //更新指定的部位

        //更新网格
        _targetSkinnedMeshRenderers[part].sharedMesh = skinned.sharedMesh;
        //更新骨架
        _targetSkinnedMeshRenderers[part].bones = bones.ToArray();
        //更新材质
        _targetSkinnedMeshRenderers[part].materials = skinned.materials;
    }

 

初始化皮肤:

 

    /// <summary>
    /// 模型的重组(初始化皮肤)
    /// </summary>
    private void StartRestructuringModel()
    {
        foreach (KeyValuePair<string, Dictionary<string, Transform>> data in _datas)
        {
            switch (data.Key)
            {
                case "coat":
                    ChangePart("coat", "001");
                    break;
                case "foot":
                    ChangePart("foot", "001");
                    break;
                case "hair":
                    ChangePart("hair", "001");
                    break;
                case "hand":
                    ChangePart("hand", "001");
                    break;
                case "pant":
                    ChangePart("pant", "001");
                    break;
                case "head":
                    ChangePart("head", "001");
                    break;
                default:
                    break;
            }
        }
    }

 

我们也可以写一个方法实现的单个物件的换装:

 

    /// <summary>
    /// 单件衣服的换装
    /// </summary>
    private void CoatChange()
    {
        if (Count == 1)
        {
            ChangePart("coat", "003");
            Count = 0;
        }
        else if (Count == 0)
        {
            ChangePart("coat", "001");
            Count = 1;
        }
    }

 


最终效果如下图:

 百度网盘地址(项目工程源码和脚本流程分析图):http://pan.baidu.com/s/1kUHiIqV

 

posted @ 2017-11-23 15:14  丢了蜡笔小新会哭〆  阅读(639)  评论(0编辑  收藏  举报