Unity换装系统(SkinnedMeshRenderer)

【SiKi学院Unity】Unity换装系统【上】
【SiKi学院Unity】Unity换装系统【中】
【SiKi学院Unity】Unity换装系统【下】
课程资料.zip (71.93MB)

unity游戏换装系统【该全集有杂音】

原理

一个隐藏的,”穿着所有衣服“的模型
一个只有模型的骨骼的Target
给Target创建了6个部位,换装就是到模型那里取Transform,SkinnedMeshRenderer,加组件到部位节点上
Unity换装系统(SkinnedMeshRenderer)_第1张图片

04 游戏场景搭建

//Assets/Assets/Sources/Character/characters/prefabs
//删除其他部位,只保留根节点
Unity换装系统(SkinnedMeshRenderer)_第2张图片
//跟模型(拖一份新的)一起放Resources
Unity换装系统(SkinnedMeshRenderer)_第3张图片

05 换装资源以及换装骨骼的加载

动画循环

//是下不是上
Unity换装系统(SkinnedMeshRenderer)_第4张图片

代码

public class AvatarSys : MonoBehaviour
{

    [Tooltip("模型")] private GameObject model;
    [Tooltip("模型位置")] private Transform modelTrans;
    //
    [Tooltip("目标骨架")] private GameObject target;
    [Tooltip("骨骼信息")] private Transform[] hipArr;

    [Tooltip("多个部位,多件装")] private Dictionary<string, Dictionary<string, SkinnedMeshRenderer>> dictionary = new Dictionary<string, Dictionary<string, SkinnedMeshRenderer>>();
    // Start is called before the first frame update
    void Start()
    {
        InstantiateModel("FemaleModel");
        InstantiateTarget("FemaleTarget");
    }
    /// 实例模型para /> 
    void InstantiateModel(string name)
    {
        GameObject go = Instantiate(Resources.Load(name)) as GameObject;
        modelTrans = go.transform;
        go.SetActive(false);
    }
    /// 实例骨骼     
    void InstantiateTarget(string name)
    { 
        target= Instantiate(Resources.Load(name)) as GameObject;
        hipArr = target.GetComponentsInChildren<Transform>();
    }
}

效果

//需要模型位置跟骨骼位置(在屋里),就赋值粘贴Transform
Unity换装系统(SkinnedMeshRenderer)_第5张图片

06 换装信息的数据存储

背景

//它的一个model是如下的命名
Unity换装系统(SkinnedMeshRenderer)_第6张图片

代码


public class AvatarSys : MonoBehaviour
{

    void Start()
    {
        SaveDictionary();
    }
  
    /// 存储服装信息
    void SaveDictionary()
    {
        SkinnedMeshRenderer[] clothes = modelTrans.GetComponentsInChildren<SkinnedMeshRenderer>();

        //比如eyes-1,取eyes(只取一次),拼成一套的部位部位     
        for (int i = 0; i < clothes.Length ; i++)
        {
            string[] nameArr=clothes[i].name.Split('-');
            string part = nameArr[0];
            string cloth = nameArr[1];

            if (fixedPartDictionary.ContainsKey(part) == false)//部位字典里没有该部位
            {
                GameObject go = new GameObject();
                go.name = part;
                go.transform.parent = target.transform;
                fixedPartDictionary.Add(part, go.AddComponent<SkinnedMeshRenderer>());
                partsDictionary.Add(part, new Dictionary<string, SkinnedMeshRenderer>());//声明一个部位可以穿衣服
            }
            partsDictionary[ part ].Add(cloth, clothes[i]);//衣服字典都Add
        }
    }
}

效果

Unity换装系统(SkinnedMeshRenderer)_第7张图片

07 换装代码的逻辑实现

骨骼的Transform

Unity换装系统(SkinnedMeshRenderer)_第8张图片

代码

	/// 换衣服
    void ChangeCloth(string part, string clothNum)
    {
        //选择一件衣服
        SkinnedMeshRenderer newCloth = partsDictionary[part][clothNum];

        //看衣服穿在哪里
        List<Transform> tmpList = new List<Transform>();
        foreach (var bone in newCloth.bones)//新衣服
        {
            foreach (var hip in hipArr)//目标骨骼。身上部位
            {
                if (bone.name == hip.name)
                {
                    tmpList.Add(bone);
                    break;
                }
            }
        }
        //穿上衣服。网格骨骼材质
        SkinnedMeshRenderer newClothed = fixedPartDictionary[part];

        newClothed.materials = newCloth.materials;       
        newClothed.bones = tmpList.ToArray();
        newClothed.sharedMesh = newCloth.sharedMesh;
    }

Unity换装系统(SkinnedMeshRenderer)_第9张图片

效果

网格骨骼材质,缺骨骼

    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ChangeCloth("hair", UnityEngine.Random.Range(1,4).ToString());
        }
    }

Unity换装系统(SkinnedMeshRenderer)_第10张图片

08 人物加载到场景中

代码

    [Tooltip("当前着装")] public string[,] clothedArr = { { "eyes", "1" } ,{ "hair", "1" },{ "top", "1" },
                                                          { "pants", "1" },{ "shoes", "1" },{ "face", "1" }};
    void Start()
    {
		......
        ChangeAvatar();
    }
    void Update()
    {
        if (Input.GetKeyDown(KeyCode.Space))
        {
            ChangeCloth("hair", UnityEngine.Random.Range(1,4).ToString());
            ChangeCloth("pants", UnityEngine.Random.Range(1,7).ToString());
            ChangeCloth("hair", UnityEngine.Random.Range(1,7).ToString());
            ChangeCloth("top", UnityEngine.Random.Range(1,7).ToString());
            ChangeCloth("shoes", UnityEngine.Random.Range(1,7).ToString());
        }
    }
    /// 初始骨骼
    void ChangeAvatar()
    {
        int length = clothedArr.GetLength(0);//0行1列
        for (int i = 0; i < length; i++)
        {
            string part = clothedArr[i, 0];//第一列
            string clotht = clothedArr[i, 1];//第二列
            ChangeCloth(part, clotht);
        }
     }

效果(动画静止)

Unity换装系统(SkinnedMeshRenderer)_第11张图片

(问题) 衣服动画

//视频如下改,就可以动,但没起作用

Unity换装系统(SkinnedMeshRenderer)_第12张图片
//视频的骨骼是空的
在这里插入图片描述

.//发现ChangCloth时tmpList为为空
Unity换装系统(SkinnedMeshRenderer)_第13张图片
//给tmpList赋值的hipArr不为空
Unity换装系统(SkinnedMeshRenderer)_第14张图片
//tmpList.Add(hip);而不是tmpList.Add(bone);

    void ChangeCloth(string part, string clothNum)
    {
        //选择一件衣服
        SkinnedMeshRenderer newCloth = partsDictionary[part][clothNum];
         
        //看衣服穿在哪里
        List<Transform> tmpList = new List<Transform>();
       
        foreach (var bone in newCloth.bones)//新衣服
        {
            foreach (var hip in hipArr)//目标骨骼。身上部位
            {
                if (bone.name == hip.name)
                {
                    tmpList.Add(hip);
                    break;
                }
            }
        }

效果

10 加入男孩角色

一份脚本的话认后面的Male

    void Start()
    {
        InstantiateModel("FemaleModel");
        InstantiateTarget("FemaleTarget");
        InstantiateModel("MaleModel");
        InstantiateTarget("MaleTarget");
        SaveDictionary();
        ChangeAvatar();
    }

UI脚本位置

//UI代码自己做的
Unity换装系统(SkinnedMeshRenderer)_第15张图片

01 表头(部位)

(问题) toggle不能一起设SetActive,对象会被变化,认最后一个

Unity换装系统(SkinnedMeshRenderer)_第16张图片

Tab

挂表头

public class Tab : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    /// 根据索引和isOn调用显示内容的方法
    public void SwitchTab()
    {
        bool isOn = GetComponent<Toggle>().isOn;
        int index = MyToggleGroup._instance.tabList.IndexOf(transform);
        print(index+","+isOn);
        MyToggleGroup._instance.SwitchTab( index ,isOn);
    }
}

MyToggleGroup

public class MyToggleGroup : MonoBehaviour
{

    [Tooltip("每个部位的列表")] public List<Transform> tabList;
    [Tooltip("(每个部位装衣服的节点的列表")] public List<Transform> contentList;

    public static MyToggleGroup _instance;

    void Awake()
    {
        _instance = this;
    }

    // Start is called before the first frame update
    void Start()
    {
        InitTab();
        InitContent();
    }

    /// 初始化表头(每个部位)
    void InitTab()
    {
        //tab命名
        string[] tabNameArr = { "发\n型", "眼\n睛", "上\n衣", "裤\n子", "鞋\n子","性\n别" };
        for (int i = 0; i < transform.childCount; i++)
        {
            transform.GetChild(i).GetChild(0).GetComponent<Text>().text = tabNameArr[i];
        }

        for (int i = 0; i < transform.childCount; i++)//不要性别
        {
            tabList.Add(transform.GetChild(i)) ;
        }
    }
    /// 初始化内容(每个部位的衣服)
    void InitContent()
    {
        //拿到content总节点
        for (int i = 0; i < tabList.Count; i++)
        {
            Transform contentTrans = tabList[i].GetChild(1);//0是文字
            contentList.Add(contentTrans);
        }


        //显示第一项,其余隐藏
        for (int i = 0; i < contentList.Count; i++)
        {
            contentList[i].gameObject.SetActive(false);                      
        }
        contentList[0].gameObject.SetActive(true);
        
    }

    /// 根据参数显示具体部位的衣服
    public void SwitchTab(int index,bool isOn)
    {
        contentList[index].gameObject.SetActive(isOn);      
    }
}

效果

02 表头里的item(部位里的衣服)

//hair-1是空节点
在这里插入图片描述

制作Item

填充中心

Unity换装系统(SkinnedMeshRenderer)_第17张图片

Item

public class Item : MonoBehaviour
{
    Transform ok;
    // Start is called before the first frame update
    void Start()
    {
        Init();
    }
    /// 设置OK,ToggleGroup
    private void Init()
    {
        GetComponent<Toggle>().group = transform.parent.parent.GetComponent<ToggleGroup>();
        ok = transform.GetChild(2);
        ok.gameObject.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
    }
    /// 点击item时Toggle会调用此方法
    public void SwitchToggle()
    {
        //打钩
        ok.gameObject.SetActive(GetComponent<Toggle>().isOn);
        string[] names = gameObject.name.Split('-');
        string part = names[0];
        string clothNum = names[1];
        print(part + "," + clothNum);
        AvatarSys._instance.ChangeCloth(part,clothNum);
    }

效果

Unity换装系统(SkinnedMeshRenderer)_第18张图片

03 填充item的UI

效果

04 制作性别

Item_Sex

public class Item_Sex : MonoBehaviour
{
    Transform ok;
    // Start is called before the first frame update
    void Start()
    {
        Init();
    }
    /// 设置OK,ToggleGroup
    private void Init()
    {
        GetComponent<Toggle>().group = transform.parent.parent.GetComponent<ToggleGroup>();
        ok = transform.GetChild(2);
        ok.gameObject.SetActive(false);
    }

    // Update is called once per frame
    void Update()
    {
        
    }
    public void SwitchToggle()
    {
        //打钩
        ok.gameObject.SetActive(GetComponent<Toggle>().isOn);
        //
        string sex = gameObject.name;
        AvatarSys._instance.Init(sex);
    }
}

MyItemGroup

//根据Male、Female切换,切换前销毁存在的模型(根据标签)

   void Start()
    {
        Init("Female");
    }
    private void Destroy()
    {
        GameObject[] goArr = GameObject.FindGameObjectsWithTag("Player");
        for (int i = 0; i < goArr.Length; i++)
        {
            Destroy(goArr[i]);
        }
    }
    public void Init(string sex)
    {
        Destroy();

        hipArr = null;
        partsDictionary.Clear();
        fixedPartDictionary.Clear();
        if (sex == "Female")
        {
            InstantiateModel("FemaleModel");
            InstantiateTarget("FemaleTarget");
        }
        else
        {
            InstantiateModel("MaleModel");
            InstantiateTarget("MaleTarget");
        }


        SaveDictionary();
        ChangeAvatar();

        target.GetComponent<Animation>().cullingType = AnimationCullingType.AlwaysAnimate;
    }

效果

//可以看到,Male的UI面板我没做

(问题) 不清楚这样是不是销毁了

Unity换装系统(SkinnedMeshRenderer)_第19张图片

16 添加人物穿衣动画

AvatarSys

    public void ChangeCloth(string part, string clothNum)
    {
		......
        PlayAnimationByChangeCloth(part);
    }
   /// 根据part播放相应动画
   void PlayAnimationByChangeCloth(string part)
   {
        switch ( part )
        {
            case "top" : PlayAnimation("item_shirt"); break;
            case "pants" : PlayAnimation("item_pants"); break;
            case "shoes" : PlayAnimation("item_boots"); break;
            default: break;
        }

    }
    /// 换衣动画后Idle
    void PlayAnimation(string name)
    {
        GameObject go = GameObject.FindGameObjectWithTag("Female");
        Animation animation = go.GetComponent<Animation>();


        if (animation.IsPlaying(name)==false)//相同不切播
        {
            animation.Play(name);
            animation.PlayQueued("idle1");
        }
    }

效果

初始衣服时最后是鞋子,所以一开始播放换鞋动画

17 鼠标控制人物旋转

鼠标点击需要添加碰撞体

Unity换装系统(SkinnedMeshRenderer)_第20张图片

代码 RotateTarget

//觉得oldPos,newPos的赋值放里面更好点

public class RotateTarget : MonoBehaviour
{ 
    public bool isClick = false;
    public Vector3 newPos;
    public Vector3 oldPos;
    public float length = 5f;

    // Update is called once per frame
    void Update()
    {             
        if (isClick==true)
        {
            newPos = Input.mousePosition;
            Vector3 offset = newPos - oldPos;
            if (Mathf.Abs(offset.x) > Mathf.Abs(offset.y) && Mathf.Abs(offset.x)>length)
            {
                transform.Rotate(Vector3.up, offset.x);
                print(offset.x);
            }
            oldPos = Input.mousePosition;
        }      
    }

    private void OnMouseDown()
    {
        isClick = true;
    }
    private void OnMouseUp()
    {
        isClick = false;
    }   
}

效果

Unity换装系统(SkinnedMeshRenderer)_第21张图片

18 保存换装信息

二位数组

    [Tooltip("当前着装")]
    public string[,] clothedArr = { { "eyes", "1" } ,{ "hair", "1" },{ "top", "1" },
                                                          { "pants", "1" },{ "shoes", "1" },{ "face", "1" }};
    // Start is called before the first frame update
    void Start()
    {

        print("总长度:"+clothedArr.Length);//总长度
        print("第一维长度:"+clothedArr.GetLength(0));//第一维长度,部位
        print("第二维长度:"+clothedArr.GetLength(1));//第二维长度,每个部位比如"eyes", "1" 

        for (int i = 0; i < clothedArr.Length; i++)
        {
            print(clothedArr[i, 0] + ":" + clothedArr[i, 1]);
        }
    }

Unity换装系统(SkinnedMeshRenderer)_第22张图片

存储 AvatarSys【DontDestroyOnLoad】

//DontDestroyOnLoad(gameObject);
Unity换装系统(SkinnedMeshRenderer)_第23张图片

    void Start()
    {
        Init("Female");
        DontDestroyOnLoad(gameObject);
    }
    public void ChangeCloth(string part, string clothNum)
    {
		......
        //更新数据
        UpdateClothedArr(part, clothNum);

        PlayAnimationByChangeCloth(part);
    }
    /// 更新数组
    void UpdateClothedArr(string part, string clothNum)
    {
        for (int i = 0; i < clothedArr.GetLength(0); i++)
        {
            if (part == clothedArr[i, 0])
            {
                clothedArr[i, 1] = clothNum;
            }
        }
        //打印测试
        for (int i = 0; i < clothedArr.GetLength(0); i++)
        {
            print(clothedArr[i, 0] + "," + clothedArr[i, 1]);
        }
    }

加载

public class SaveAndLoad : MonoBehaviour
{
    // Start is called before the first frame update
    void Start()
    {
        AvatarSys._instance.Init("Female");
    }
    ......

Unity换装系统(SkinnedMeshRenderer)_第24张图片

你可能感兴趣的:(Unity,C#,Siki)