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