Unity换装系统(SkinnedMeshRenderer)

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

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

原理

一个隐藏的,”穿着所有衣服“的模型
一个只有模型的骨骼的Target
给Target创建了6个部位,换装就是到模型那里取Transform,SkinnedMeshRenderer,加组件到部位节点上
在这里插入图片描述

04 游戏场景搭建

//Assets/Assets/Sources/Character/characters/prefabs
//删除其他部位,只保留根节点
在这里插入图片描述
//跟模型(拖一份新的)一起放Resources
在这里插入图片描述

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

动画循环

//是下不是上
在这里插入图片描述

代码

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");
    }
    /// <summary>实例模型para /> </summary>
    void InstantiateModel(string name)
    {
    
    
        GameObject go = Instantiate(Resources.Load(name)) as GameObject;
        modelTrans = go.transform;
        go.SetActive(false);
    }
    /// <summary>实例骨骼<para /> </summary>    
    void InstantiateTarget(string name)
    {
    
     
        target= Instantiate(Resources.Load(name)) as GameObject;
        hipArr = target.GetComponentsInChildren<Transform>();
    }
}

效果

//需要模型位置跟骨骼位置(在屋里),就赋值粘贴Transform
在这里插入图片描述

06 换装信息的数据存储

背景

//它的一个model是如下的命名
在这里插入图片描述

代码


public class AvatarSys : MonoBehaviour
{
    
    

    void Start()
    {
    
    
        SaveDictionary();
    }
  
    /// <summary>存储服装信息<para /></summary>
    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
        }
    }
}

效果

在这里插入图片描述

07 换装代码的逻辑实现

骨骼的Transform

在这里插入图片描述

代码

	/// <summary>换衣服<para /></summary>
    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;
    }

在这里插入图片描述

效果

网格骨骼材质,缺骨骼

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

在这里插入图片描述

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());
        }
    }
    /// <summary>初始骨骼<para /></summary>
    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);
        }
     }

效果(动画静止)

在这里插入图片描述

(问题) 衣服动画

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

在这里插入图片描述
//视频的骨骼是空的
在这里插入图片描述

.//发现ChangCloth时tmpList为为空
在这里插入图片描述
//给tmpList赋值的hipArr不为空
在这里插入图片描述
//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代码自己做的
在这里插入图片描述

01 表头(部位)

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

在这里插入图片描述

Tab

挂表头

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

    // Update is called once per frame
    void Update()
    {
    
    
        
    }
    /// <summary>根据索引和isOn调用显示内容的方法<para /></summary>
    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();
    }

    /// <summary>初始化表头(每个部位)<para /></summary>
    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)) ;
        }
    }
    /// <summary>初始化内容(每个部位的衣服)<para /></summary>
    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);
        
    }

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

效果

在这里插入图片描述

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

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

制作Item

填充中心

在这里插入图片描述

Item

public class Item : MonoBehaviour
{
    
    
    Transform ok;
    // Start is called before the first frame update
    void Start()
    {
    
    
        Init();
    }
    /// <summary>设置OK,ToggleGroup<para /></summary>
    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()
    {
    
    
    }
    /// <summary>点击item时Toggle会调用此方法<para /></summary>
    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);
    }

效果

在这里插入图片描述

03 填充item的UI

效果

在这里插入图片描述

04 制作性别

Item_Sex

public class Item_Sex : MonoBehaviour
{
    
    
    Transform ok;
    // Start is called before the first frame update
    void Start()
    {
    
    
        Init();
    }
    /// <summary>设置OK,ToggleGroup<para /></summary>
    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面板我没做
在这里插入图片描述

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

在这里插入图片描述

16 添加人物穿衣动画

AvatarSys

    public void ChangeCloth(string part, string clothNum)
    {
    
    
		......
        PlayAnimationByChangeCloth(part);
    }
   /// <summary>根据part播放相应动画<para /></summary>
   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;
        }

    }
    /// <summary>换衣动画后Idle<para /></summary>
    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 鼠标控制人物旋转

鼠标点击需要添加碰撞体

在这里插入图片描述

代码 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;
    }   
}

效果

在这里插入图片描述

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]);
        }
    }

在这里插入图片描述

存储 AvatarSys【DontDestroyOnLoad】

//DontDestroyOnLoad(gameObject);
在这里插入图片描述

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

        PlayAnimationByChangeCloth(part);
    }
    /// <summary>更新数组<para /></summary>
    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");
    }
    ......

在这里插入图片描述

猜你喜欢

转载自blog.csdn.net/weixin_39538253/article/details/118657537
今日推荐