Unity中Avatar换装实现 agile Posted on May 14 2021 > 本文由 [简悦 SimpRead](http://ksria.com/simpread/) 转码, 原文地址 [blog.uwa4d.com](https://blog.uwa4d.com/archives/avartar.html) Avatar 换装是 MMO 游戏不可缺少的一部分,一个人物模型通常可拆分为头、身体、手臂、腿、武器等部分,如何将这些部分组合到一起呢?本文将阐述如何将在 Unity 中实现人物模型的换装功能。 这是侑虎科技第 65 篇原创文章,感谢作者邹春毅(QQ:442319386)供稿。欢迎转发分享,未经作者授权请勿转载。同时如果您有任何独到的见解或者发现也欢迎联系我们,一起探讨。(QQ 群:793972859) 作者 Github:[https://github.com/zouchunyi](https://github.com/zouchunyi) 1. 每一套装备模型必须使用同一套骨骼,并单独将骨骼数据保存成一个 Prefab。红色部分为武器挂节点(也可以把武器做成一个 SkinnedMesh,不采用挂接点的形式),骨骼数据在 Unity 中的展示形式就是 Transform。  2. 将模型拆分成多个部分,将每一个部分单独保存成 Prefab,武器也单独保存为一个 Prefab。   每一个 Prefab 都含有自身的 SkinnedMeshRenderer。  1. 创建骨骼 GameObject,所有装备的蒙皮数据会最终合成到这个 Prefab 中。 2. 创建装备 GameObject,用于搜集其中蒙皮数据以生成新的 SkinnedMeshRenderer 到骨骼 Prefab 中。 3.public void CombineObject(GameObject skeleton, SkinnedMeshRenderer[] meshes, bool combine = false) 传入骨骼的 GameObject 和蒙皮数据。 4. 搜集装备蒙皮数据中的有效信息。 ``` // Collect information from meshes for (int i = 0; i < meshes.Length; i ++) { SkinnedMeshRenderer smr = meshes[i]; materials.AddRange(smr.materials); // Collect materials // Collect meshes for (int sub = 0; sub < smr.sharedMesh.subMeshCount; sub++) { CombineInstance ci = new CombineInstance(); ci.mesh = smr.sharedMesh; ci.subMeshIndex = sub; combineInstances.Add(ci); } // Collect bones for (int j = 0 ; j < smr.bones.Length; j ++) { int tBase = 0; for (tBase = 0; tBase < transforms.Count; tBase ++) { if (smr.bones[j].name.Equals(transforms[tBase].name)) { bones.Add(transforms[tBase]); break; } } } } ``` 5. 为骨骼 GameObject 生成新的 SkinnedMeshRenderer。 ``` // Create a new SkinnedMeshRenderer SkinnedMeshRenderer oldSKinned = skeleton.GetComponent<SkinnedMeshRenderer> (); if (oldSKinned != null) { GameObject.DestroyImmediate (oldSKinned); } SkinnedMeshRenderer r = skeleton.AddComponent<SkinnedMeshRenderer>(); r.sharedMesh = new Mesh(); r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);// Combine meshes r.bones = bones.ToArray();// Use new bones ``` 6. 挂接武器。 ``` Transform[] transforms = Instance.GetComponentsInChildren<Transform>(); foreach (Transform joint in transforms) { if (joint.name == "weapon_hand_r") {// find the joint (need the support of art designer) WeaponInstance.transform.parent = joint.gameObject.transform; break; } } ``` 其中 WeaponInstance 为武器实例 GameObject,Instance 为骨骼实例 GameObject。 **合成后的效果**   合成之后的模型拥有 4 个独立材质,加上独立的对象武器,也就是会产生 5 个 Draw Call;如果将在骨骼中的这 4 个材质合并成一个,那么就能将 Draw Call 减少到 2 个。 **其中实现过程如下:** 优化 CombineObject 方法,其中 Combine 为 bool 类型,用于标识是否合并材质。 ``` // merge materials if (combine) { newMaterial = new Material (Shader.Find ("Mobile/Diffuse")); oldUV = new List<Vector2[]>(); // merge the texture List<Texture2D> Textures = new List<Texture2D>(); for (int i = 0; i < materials.Count; i++) { Textures.Add(materials[i].GetTexture(COMBINE_DIFFUSE_TEXTURE) as Texture2D); } newDiffuseTex = new Texture2D(COMBINE_TEXTURE_MAX, COMBINE_TEXTURE_MAX, TextureFormat.RGBA32, true); Rect[] uvs = newDiffuseTex.PackTextures(Textures.ToArray(), 0); newMaterial.mainTexture = newDiffuseTex; // reset uv Vector2[] uva, uvb; for (int j = 0; j < combineInstances.Count; j++) { uva = (Vector2[])(combineInstances[j].mesh.uv); uvb = new Vector2[uva.Length]; for (int k = 0; k < uva.Length; k++) { uvb[k] = new Vector2((uva[k].x * uvs[j].width) + uvs[j].x, (uva[k].y * uvs[j].height) + uvs[j].y); } oldUV.Add(combineInstances[j].mesh.uv); combineInstances[j].mesh.uv = uvb; } } ``` 生成新的 SkinnedMeshRenderer 时略有区别:  **最终效果如下:**  可以看出,新的 SkinnedMeshRenderer 只有一个材质,Draw Call 自然也就降低了。 本人已将此示例工程分享到了 GitHub 中:[https://github.com/zouchunyi/UnityAvater](https://github.com/zouchunyi/UnityAvater) 感兴趣的朋友可以下载。工程中代码大家可以直接使用,但是美术资源不得用于任何商业用途。  Box Collider 2D盒子碰撞器 第9章 更复杂的光照