Unity3D换装系统

时间:2024-06-11 22:36:26

转载请注明出处:http://www.cnblogs.com/shamoyuu/p/6505561.html

一、换装原理

  游戏角色换装分为以下几步:

    1.替换蒙皮网格

    2.刷新骨骼

    3.替换材质

  上面这种是比较简单的换装,可以实现,但是一般我们为了降低游戏的Draw Call会合并模型的网格,这就需要我们重新计算UV,还要合并贴图和材质。这种复杂的实现分为以下几步:

    1.替换蒙皮网格(或者直接替换模型换装部位的GameObject,因为合并的时候会合并所有的蒙皮网格,而不会关心它是否属于原来角色身体的一部分,而且如果需要替换的部位有多个配件拥有独立的网格和贴图,那这种方式都可以正常执行。我下面的代码就是直接替换了换装部位的GameObject)

    2.合并所有蒙皮网格

    3.刷新骨骼

    4.附加材质(我下面是获取第一个材质作为默认材质)

    5.合并贴图(贴图的宽高最好是2的N次方的值)

    6.重新计算UV

二、换装实现

using System;
using System.Collections;
using UnityEngine;
using System.Collections.Generic; public class CharacterCombine : MonoBehaviour
{
// 目标物体(必须是骨骼的父物体,不然蒙皮失效)
public GameObject target; // 最终材质(合并所有模型后使用的材质)
public Material material; // 物体所有的部分
private GameObject[] targetParts = new GameObject[]; private string[] defaultEquipPartPaths = new string[]; void Start()
{
// 把FBX的模型按部件分别放入Resources下对应的文件夹里,可以留空,模型需要蒙皮,而且所有模型使用同一骨骼
// 最后的M是Fbx的模型,需要的Unity3D里设置好材质和贴图,部件贴图要勾选Read/Write Enabled
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Head/Head0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Face/Face0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Hair/Hair0000/M";
defaultEquipPartPaths[] = "";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Body/Body0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Leg/Leg0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Hand/Hand0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Foot/Foot0000/M";
defaultEquipPartPaths[] = "Model/Player/GirlPlayer/Wing/Wing0001/M"; Destroy(target.GetComponent<SkinnedMeshRenderer>());
for (int i = ; i < defaultEquipPartPaths.Length; i++)
{
UnityEngine.Object o = Resources.Load(defaultEquipPartPaths[i]);
if (o)
{
GameObject go = Instantiate(o) as GameObject;
go.transform.parent = target.transform;
go.transform.localPosition = new Vector3(, -, );
go.transform.localRotation = new Quaternion();
targetParts[i] = go;
}
} StartCoroutine(DoCombine());
} /// <summary>
/// 使用延时,不然某些GameObject还没有创建
/// </summary>
/// <returns></returns>
IEnumerator DoCombine()
{
yield return null;
Combine(target.transform);
} /// <summary>
/// 合并蒙皮网格,刷新骨骼
/// 注意:合并后的网格会使用同一个Material
/// </summary>
/// <param name="root">角色根物体</param>
private void Combine(Transform root)
{
float startTime = Time.realtimeSinceStartup; List<CombineInstance> combineInstances = new List<CombineInstance>();
List<Transform> boneList = new List<Transform>();
Transform[] transforms = root.GetComponentsInChildren<Transform>();
List<Texture2D> textures = new List<Texture2D>(); int width = ;
int height = ; int uvCount = ; List<Vector2[]> uvList = new List<Vector2[]>(); // 遍历所有蒙皮网格渲染器,以计算出所有需要合并的网格、UV、骨骼的信息
foreach (SkinnedMeshRenderer smr in root.GetComponentsInChildren<SkinnedMeshRenderer>())
{
for (int sub = ; sub < smr.sharedMesh.subMeshCount; sub++)
{
CombineInstance ci = new CombineInstance();
ci.mesh = smr.sharedMesh;
ci.subMeshIndex = sub;
combineInstances.Add(ci);
} uvList.Add(smr.sharedMesh.uv);
uvCount += smr.sharedMesh.uv.Length; if (smr.material.mainTexture != null)
{
textures.Add(smr.GetComponent<Renderer>().material.mainTexture as Texture2D);
width += smr.GetComponent<Renderer>().material.mainTexture.width;
height += smr.GetComponent<Renderer>().material.mainTexture.height;
} foreach (Transform bone in smr.bones)
{
foreach (Transform item in transforms)
{
if (item.name != bone.name) continue;
boneList.Add(item);
break;
}
}
} // 获取并配置角色所有的SkinnedMeshRenderer
SkinnedMeshRenderer tempRenderer = root.gameObject.GetComponent<SkinnedMeshRenderer>();
if (!tempRenderer)
{
tempRenderer = root.gameObject.AddComponent<SkinnedMeshRenderer>();
} tempRenderer.sharedMesh = new Mesh(); // 合并网格,刷新骨骼,附加材质
tempRenderer.sharedMesh.CombineMeshes(combineInstances.ToArray(), true, false);
tempRenderer.bones = boneList.ToArray();
tempRenderer.material = material; Texture2D skinnedMeshAtlas = new Texture2D(get2Pow(width), get2Pow(height));
Rect[] packingResult = skinnedMeshAtlas.PackTextures(textures.ToArray(), );
Vector2[] atlasUVs = new Vector2[uvCount]; // 因为将贴图都整合到了一张图片上,所以需要重新计算UV
int j = ;
for (int i = ; i < uvList.Count; i++)
{
foreach (Vector2 uv in uvList[i])
{
atlasUVs[j].x = Mathf.Lerp(packingResult[i].xMin, packingResult[i].xMax, uv.x);
atlasUVs[j].y = Mathf.Lerp(packingResult[i].yMin, packingResult[i].yMax, uv.y);
j++;
}
} // 设置贴图和UV
tempRenderer.material.mainTexture = skinnedMeshAtlas;
tempRenderer.sharedMesh.uv = atlasUVs; // 销毁所有部件
foreach (GameObject goTemp in targetParts)
{
if (goTemp)
{
Destroy(goTemp);
}
} Debug.Log("合并耗时 : " + (Time.realtimeSinceStartup - startTime) * + " ms");
} /// <summary>
/// 获取最接近输入值的2的N次方的数,最大不会超过1024,例如输入320会得到512
/// </summary>
private int get2Pow(int into)
{
int outo = ;
for (int i = ; i < ; i++)
{
outo *= ;
if (outo > into)
{
break;
}
} return outo;
}
}

三、效果展示

在执行上面的代码前,角色的每个部分都是单独的,并且是激活的状态。

执行后,角色所有的部位都会删除,因为它们的网格都合并到了Player_Girl这个角色根物体上。

Unity3D换装系统

Unity3D换装系统

模型就不放出来了~