3D模型人物换装系统

3D模型人物换装系统

  • 介绍
  • 遇到的问题
  • 问题修复
  • 具体实现换装
    • 1.准备所有模型部位和模型骨骼
      • 部位准备
      • 材质准备
      • 模型根骨骼准备
      • 创建文件夹将上述模型拖成预制体
      • 创建一个动画状态机给他们附上待机动画
    • 2.脚本驱动
      • Mesh合并代码 UCombineSkinnedMgr.cs
      • 创建Mesh以及实例化对象的代码 UCharacterController.cs
      • 测试换装调用 UCharacterManager.cs
    • 3.测试
  • 总结

介绍

本文使用2018.4.4和2020.3.26进行的测试

人物换装系统

人物换装系统下

人物换装对模型的还是有一定要求的,首先换装是要有多套模型的,通用做法是将人物的身体(包含头),衣服、裤子、鞋子、头发分别作为一个部位,然后将这几个部位动态合批成一个人物模型。有的人身体和头也是分开的部位这个看做模型那边的情况具体定。还有的不要身体直接分成衣服、裤子、双手、双脚、头(包含头发),我这里介绍的也是这种方法这样也能避免很多蒙皮问题。

遇到的问题

之前我们在做换装人物合批的时候也遇到过几个影响比较大的问题这里我拿出来说一下。

  1. 蒙皮外露穿模情况:这个也比较好理解其实就是人物在做动作的时候可能把人身体的一部分露出来了,比如说肩膀衣服露肉了这种穿模情况。
  2. 人物合批材质也需要合批那么身上的贴图合批会乱(这种情况我会在下面代码讲一下这里不详细说了)。

问题修复

针对蒙皮穿模的这种情况其实有很多办法

  1. 协商模型制作去掉能够被衣服挡住的蒙皮(但是这里要考虑所有的衣服,但凡有一个衣服是需要露这块的蒙皮那么就不能使用这个方法)
  2. 针对不同的衣服使用不同程度的蒙皮,比如A衣服要用露胳膊但是B衣服不露胳膊,那么做一个是删除胳膊的身体蒙皮A1和一个是不删除胳膊的身体蒙皮B1,这样A-A1绑定 B-B1绑定,需要穿A衣服的时候绑定A1身体,需要穿B衣服的时候绑定B1身体。
  3. 不要身体,身体直接和衣服裤子绑定在一起做,去掉身体部位(这个也是本文的做法)。

具体实现换装

1.准备所有模型部位和模型骨骼

部位准备

如下所示把其中一个赋值好材质的模型预制体拖上来,红框是每个需要合批的部位。
3D模型人物换装系统_第1张图片

选中每一个部位然后Ctrl + D拷贝一份
3D模型人物换装系统_第2张图片

将上述拷贝的部位改名如下
3D模型人物换装系统_第3张图片
全部拷贝出来如下所示,这样的对象是包含根节点信息的
3D模型人物换装系统_第4张图片
改名如下
眉毛和脸模型都是一样的就不需要合批处理只用一个即可
3D模型人物换装系统_第5张图片

材质准备

模型材质赋值到上述对象上
3D模型人物换装系统_第6张图片
3D模型人物换装系统_第7张图片

模型根骨骼准备

使用刚才上面的那个白膜拖拽到场景上
3D模型人物换装系统_第8张图片
解除预制体绑定先
3D模型人物换装系统_第9张图片

删除下面其他的Mesh模型
3D模型人物换装系统_第10张图片
3D模型人物换装系统_第11张图片

修改名称如下
3D模型人物换装系统_第12张图片

这里我只是用了一个模型来讲,换装肯定有多个模型,其他模型也是这样处理,第一个模型名字我后缀都是1,后面的模型依次后缀是2、3、4…

创建文件夹将上述模型拖成预制体

创建文件如下,并且将上面的拷贝的对象分别放到对应文件夹下做预制体
3D模型人物换装系统_第13张图片

其他所有的需要换装的模型都这样处理放到对应文件夹下
3D模型人物换装系统_第14张图片
3D模型人物换装系统_第15张图片
3D模型人物换装系统_第16张图片
3D模型人物换装系统_第17张图片
3D模型人物换装系统_第18张图片
3D模型人物换装系统_第19张图片
3D模型人物换装系统_第20张图片

上图中脸和眉毛只有一个是因为都是一样的只保留了一份

创建一个动画状态机给他们附上待机动画

创建一个动画控制器并双击打开
3D模型人物换装系统_第21张图片
将待机动画拖拽上来
3D模型人物换装系统_第22张图片
将之前的所有模型根节点中的动画控制器换成刚才我们生成的动画控制器
3D模型人物换装系统_第23张图片
3D模型人物换装系统_第24张图片

2.脚本驱动

Mesh合并代码 UCombineSkinnedMgr.cs

using UnityEngine;
using System.Collections.Generic;

public class UCombineSkinnedMgr
{

    /// 
    /// Combine SkinnedMeshRenderers together and share one skeleton.
    /// Merge materials will reduce the drawcalls, but it will increase the size of memory. 
    /// 
    /// combine meshes to this skeleton(a gameobject)
    /// meshes need to be merged
    /// merge materials or not
    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();
    }
}

创建Mesh以及实例化对象的代码 UCharacterController.cs

using UnityEngine;

public class UCharacterController
{

    /// 
    /// GameObject reference
    /// 
	public GameObject Instance = null;

    /// 
    /// 换装总组装数量
    /// 
    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;

    /// 
    /// 创建对象
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    /// 
    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);
    }

    /// 
    /// 部位换装
    /// 
    /// 路径
    /// 切换部位
    /// 
    /// 
    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);
        }
    }


}

测试换装调用 UCharacterManager.cs

using UnityEngine;
using System.Collections.Generic;

/// 
/// 换装管理器
/// 
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
}

3.测试

随便创建一个场景然后在随便一个对象上面挂载UCharacterManager.cs脚本运行测试3D模型人物换装系统_第25张图片

总结

这个方案是将所有的Mesh合并成一个Mesh,材质球是叠加的并没有合批材质
3D模型人物换装系统_第26张图片

注意:如果要想将模型完全合批需要将所有图都设置可读可写,这里放到另一篇文章去讲,这里先不讲合批材质的方法
本文资源
如果文章对你有帮助的话留一个免费的关注和点赞吧

你可能感兴趣的:(Unity,3d,unity,人物换装,换装系统)