本文使用2018.4.4和2020.3.26进行的测试
人物换装系统
人物换装系统下
人物换装对模型的还是有一定要求的,首先换装是要有多套模型的,通用做法是将人物的身体(包含头),衣服、裤子、鞋子、头发分别作为一个部位,然后将这几个部位动态合批成一个人物模型。有的人身体和头也是分开的部位这个看做模型那边的情况具体定。还有的不要身体直接分成衣服、裤子、双手、双脚、头(包含头发),我这里介绍的也是这种方法这样也能避免很多蒙皮问题。
之前我们在做换装人物合批的时候也遇到过几个影响比较大的问题这里我拿出来说一下。
针对蒙皮穿模的这种情况其实有很多办法
如下所示把其中一个赋值好材质的模型预制体拖上来,红框是每个需要合批的部位。
选中每一个部位然后Ctrl + D拷贝一份
将上述拷贝的部位改名如下
全部拷贝出来如下所示,这样的对象是包含根节点信息的
改名如下
眉毛和脸模型都是一样的就不需要合批处理只用一个即可
模型材质赋值到上述对象上
使用刚才上面的那个白膜拖拽到场景上
解除预制体绑定先
删除下面其他的Mesh模型
修改名称如下
这里我只是用了一个模型来讲,换装肯定有多个模型,其他模型也是这样处理,第一个模型名字我后缀都是1,后面的模型依次后缀是2、3、4…
创建文件如下,并且将上面的拷贝的对象分别放到对应文件夹下做预制体
其他所有的需要换装的模型都这样处理放到对应文件夹下
上图中脸和眉毛只有一个是因为都是一样的只保留了一份
创建一个动画控制器并双击打开
将待机动画拖拽上来
将之前的所有模型根节点中的动画控制器换成刚才我们生成的动画控制器
using UnityEngine;
using System.Collections.Generic;
public class UCombineSkinnedMgr
{
/// <summary>
/// Combine SkinnedMeshRenderers together and share one skeleton.
/// Merge materials will reduce the drawcalls, but it will increase the size of memory.
/// </summary>
/// <param name="skeleton">combine meshes to this skeleton(a gameobject)</param>
/// <param name="meshes">meshes need to be merged</param>
/// <param name="combine">merge materials or not</param>
public void CombineObject(GameObject skeleton, SkinnedMeshRenderer[] meshes)
{
// Fetch all bones of the skeleton
List<Transform> transforms = new List<Transform>();
transforms.AddRange(skeleton.GetComponentsInChildren<Transform>(true));
List<Material> materials = new List<Material>();//the list of materials
List<CombineInstance> combineInstances = new List<CombineInstance>();//the list of meshes
List<Transform> bones = new List<Transform>();//the list of bones
// 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;
}
}
}
}
// 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
r.materials = materials.ToArray();
}
}
using UnityEngine;
public class UCharacterController
{
/// <summary>
/// GameObject reference
/// </summary>
public GameObject Instance = null;
/// <summary>
/// 换装总组装数量
/// </summary>
public int m_MeshCount = 9;
public string Role_Skeleton;
public string Role_Body;
public string Role_Cloak;
public string Role_Face;
public string Role_Hair;
public string Role_Hand;
public string Role_Leg;
public string Role_MainWeapon;
public string Role_Retina;
public string Role_SubWeapon;
/// <summary>
/// 创建对象
/// </summary>
/// <param name="job"></param>
/// <param name="skeleton"></param>
/// <param name="body"></param>
/// <param name="cloak"></param>
/// <param name="face"></param>
/// <param name="hair"></param>
/// <param name="hand"></param>
/// <param name="leg"></param>
/// <param name="mainweapon"></param>
/// <param name="retina"></param>
/// <param name="subweapon"></param>
public UCharacterController(string job, string skeleton, string body, string cloak, string face, string hair, string hand, string leg, string mainweapon, string retina, string subweapon)
{
Object res = Resources.Load("RoleMesh/" + job + "/" + job + "/" + skeleton);
this.Instance = GameObject.Instantiate(res) as GameObject;
this.Role_Skeleton = skeleton;
this.Role_Body = body;
this.Role_Cloak = cloak;
this.Role_Face = face;
this.Role_Hair = hair;
this.Role_Hand = hand;
this.Role_Leg = leg;
this.Role_MainWeapon = mainweapon;
this.Role_Retina = retina;
this.Role_SubWeapon = subweapon;
string[] equipments = new string[m_MeshCount];
equipments[0] = "Body/" + Role_Body;
equipments[1] = "Cloak/" + Role_Cloak;
equipments[2] = "Face/" + Role_Face;
equipments[3] = "Hair/" + Role_Hair;
equipments[4] = "Hand/" + Role_Hand;
equipments[5] = "Leg/" + Role_Leg;
equipments[6] = "Mainweapon/" + Role_MainWeapon;
equipments[7] = "Retina/" + Role_Retina;
equipments[8] = "Subweapon/" + Role_SubWeapon;
SkinnedMeshRenderer[] meshes = new SkinnedMeshRenderer[m_MeshCount];
GameObject[] objects = new GameObject[m_MeshCount];
for (int i = 0; i < equipments.Length; i++)
{
res = Resources.Load("RoleMesh/" + job + "/" + equipments[i]);
objects[i] = GameObject.Instantiate(res) as GameObject;
meshes[i] = objects[i].GetComponentInChildren<SkinnedMeshRenderer>();
}
UCharacterManager.Instance.CombineSkinnedMgr.CombineObject(Instance, meshes);
for (int i = 0; i < objects.Length; i++)
{
GameObject.DestroyImmediate(objects[i].gameObject);
}
}
public void Delete()
{
GameObject.Destroy(Instance);
}
/// <summary>
/// 部位换装
/// </summary>
/// <param name="path">路径</param>
/// <param name="index">切换部位</param>
/// <param name="equipment"></param>
/// <param name="combine"></param>
public void ChangeEquipments(string path, int index, int equipmentId)
{
switch (index)
{
case 0:
Role_Body = "Body" + equipmentId;
break;
case 1:
Role_Cloak = "Cloak" + equipmentId;
break;
case 2:
Delete();
this.Instance = GameObject.Instantiate(Resources.Load("RoleMesh/" + path + "/" + path + "/" + path + equipmentId)) as GameObject;
Role_Hair = "Hair" + equipmentId;
break;
case 3:
Role_Hand = "Hand" + equipmentId;
break;
case 4:
Role_Leg = "Leg" + equipmentId;
break;
case 5:
Role_MainWeapon = "Mainweapon" + equipmentId;
Role_SubWeapon = "Subweapon" + equipmentId;
break;
}
string[] equipments = new string[m_MeshCount];
equipments[0] = "Body/" + Role_Body;
equipments[1] = "Cloak/" + Role_Cloak;
equipments[2] = "Face/" + Role_Face;
equipments[3] = "Hair/" + Role_Hair;
equipments[4] = "Hand/" + Role_Hand;
equipments[5] = "Leg/" + Role_Leg;
equipments[6] = "Mainweapon/" + Role_MainWeapon;
equipments[7] = "Retina/" + Role_Retina;
equipments[8] = "Subweapon/" + Role_SubWeapon;
Object res = null;
SkinnedMeshRenderer[] meshes = new SkinnedMeshRenderer[m_MeshCount];
GameObject[] objects = new GameObject[m_MeshCount];
for (int i = 0; i < equipments.Length; i++)
{
res = Resources.Load("RoleMesh/" + path + "/" + equipments[i]);
objects[i] = GameObject.Instantiate(res) as GameObject;
meshes[i] = objects[i].GetComponentInChildren<SkinnedMeshRenderer>();
}
UCharacterManager.Instance.CombineSkinnedMgr.CombineObject(Instance, meshes);
for (int i = 0; i < objects.Length; i++)
{
GameObject.DestroyImmediate(objects[i].gameObject);
}
}
}
using UnityEngine;
using System.Collections.Generic;
/// <summary>
/// 换装管理器
/// </summary>
public class UCharacterManager : MonoBehaviour
{
public static UCharacterManager Instance;
private UCombineSkinnedMgr skinnedMgr = null;
public UCombineSkinnedMgr CombineSkinnedMgr { get { return skinnedMgr; } }
private int characterIndex = 0;
private Dictionary<int, UCharacterController> characterDic = new Dictionary<int, UCharacterController>();
public UCharacterManager()
{
skinnedMgr = new UCombineSkinnedMgr();
}
private void Awake()
{
Instance = this;
}
public UCharacterController mine;
private void Start()
{
mine = Generatecharacter("Axceler", "Axceler1", "Body5", "Cloak5", "Face", "Hair1", "Hand5", "Leg5", "Mainweapon5", "Retina", "Subweapon5");
}
private void Update()
{
if (Input.GetKeyDown(KeyCode.Space))
{
ChangeRole();
}
//衣服
if (Input.GetKeyDown(KeyCode.Q))
{
mine.ChangeEquipments("Axceler", 0, Random.Range(1, 8));
}
//飘带
if (Input.GetKeyDown(KeyCode.W))
{
mine.ChangeEquipments("Axceler", 1, Random.Range(1, 7));
}
//头发
if (Input.GetKeyDown(KeyCode.E))
{
mine.ChangeEquipments("Axceler", 2, Random.Range(1, 8));
}
//手套
if (Input.GetKeyDown(KeyCode.A))
{
mine.ChangeEquipments("Axceler", 3, Random.Range(1, 8));
}
//腿饰
if (Input.GetKeyDown(KeyCode.S))
{
mine.ChangeEquipments("Axceler", 4, Random.Range(1, 8));
}
//武器
if (Input.GetKeyDown(KeyCode.D))
{
mine.ChangeEquipments("Axceler", 5, Random.Range(1, 8));
}
}
public void ChangeRole()
{
if (mine != null)
{
mine.Delete();
}
int a = Random.Range(1, 8);
int b = Random.Range(1, 8);
int c = Random.Range(1, 7);
int d = Random.Range(1, 8);
int e = Random.Range(1, 8);
int f = Random.Range(1, 8);
int g = Random.Range(1, 8);
mine = Generatecharacter("Axceler", "Axceler" + a, "Body" + b, "Cloak" + c, "Face", "Hair" + a, "Hand" + d, "Leg" + e, "Mainweapon" + f, "Retina", "Subweapon" + g);
}
#region 创建人物模型骨骼
public UCharacterController Generatecharacter(string job, string skeleton, string body, string cloak, string face, string hair, string hand, string leg, string mainweapon, string retina, string subweapon)
{
UCharacterController instance = new UCharacterController(job, skeleton, body, cloak, face, hair, hand, leg, mainweapon, retina, subweapon);
characterDic.Add(characterIndex, instance);
characterIndex++;
return instance;
}
#endregion
}
随便创建一个场景然后在随便一个对象上面挂载UCharacterManager.cs脚本运行测试
这个方案是将所有的Mesh合并成一个Mesh,材质球是叠加的并没有合批材质
注意:如果要想将模型完全合批需要将所有图都设置可读可写,这里放到另一篇文章去讲,这里先不讲合批材质的方法
本文资源
如果文章对你有帮助的话留一个免费的关注和点赞吧