Doris232 发表于 2022-4-29 18:31

Unity换装系统

Unity换装系统

效果演示: https://www.bilibili.com/video/BV1cV411x71T
引言

之前的项目需要实现人物的换装系统,网上目前的教程大部分都是骨骼点替换+Mesh合并的方案,代码也相对老旧。这里记录一下自己写的仅基于骨骼点替换的换装系统。
这里先简单讲一下两个换装系统用到的技术(仅为个人理解)

[*]骨骼点(Skeleton): 骨骼点就是附在模型上的Transform point,通过改动骨骼点的position,rotation或者scale可以对其对应的蒙皮进行位移,旋转或者缩放
[*]蒙皮(Skinned Mesh): 和骨骼点绑定的mesh。
所以换装系统需要一个具有骨骼点绑定模型,并且可以更换的服装也和主体模型有一样的骨骼点。接着进行骨骼点替换,把服装的骨骼点替换程主体模型的骨骼点。这样Unity在播放主体模型的动画时(实际就是对主体模型的骨骼点进行transform),服装也会跟着一起动,这就完成了简单的换装。
实现

->完整项目<-
SkinnedMeshHelper.cs

这个class主要就是按照我在引言中提到的思路进行骨骼点查找,然后返回一个新的骨骼点array供替换。
using System.Linq;
using UnityEngine;

public static class SkinnedMeshHelper
{
    public static Transform[] GetNewBones(SkinnedMeshRenderer root, SkinnedMeshRenderer source)
    {
      return root.bones
            .Where(x => source.bones.Select(s => s.name).Contains(x.name)).ToArray();
    }
}Outfit.cs

这个class放在服装的prefab上,设定好OutfitType(Hair,Cloth,Pant,Shoes)后放在Resources/Outfit/{OutfitType}/下。文件名和Id保持一致。
using UnityEngine;

public class Outfit : MonoBehaviour
{
    private OutfitType outfitType;
    private SkinnedMeshRenderer skinnedMeshRenderer;

    public OutfitType OutfitType { get => outfitType; set => outfitType = value; }
    public int Id { get => int.Parse(this.name); }
    public SkinnedMeshRenderer SkinnedMeshRenderer
    {
      get
      {
            if (skinnedMeshRenderer == null)
            {
                skinnedMeshRenderer = this.GetComponentInChildren<SkinnedMeshRenderer>();
            }
            return skinnedMeshRenderer;
      }
    }
}

public enum OutfitType
{
    Hair,
    Cloth,
    Pant,
    Shoes
}EquipmentManager.cs

这个类用于换装前端逻辑,使用前先在人物身上放4个Slot,分别对应头,身,腿和脚。
using UnityEngine;

public class EquipmentManager : MonoBehaviour
{
    private Transform hairSlot;
    private Transform clothSlot;
    private Transform pantSlot;
    private Transform shoesSlot;
    private SkinnedMeshRenderer avatarSkinnedMesh;

    public Transform HairSlot { get => hairSlot; }
    public Transform ClothSlot { get => clothSlot; }
    public Transform PantSlot { get => pantSlot; }
    public Transform ShoesSlot { get => shoesSlot; }

    public int HairId { get; set; }
    public int ClothId { get; set; }
    public int PantId { get; set; }
    public int ShoesId { get; set; }

    public void LoadEquipment()
    {
      ChangeOutfit(OutfitType.Hair, 1);
      ChangeOutfit(OutfitType.Cloth, 1);
      ChangeOutfit(OutfitType.Pant, 1);
      ChangeOutfit(OutfitType.Shoes, 1);
    }

    public void ChangeOutfit(OutfitType outfitType, int outfitId)
    {
      GameObject outfit = null;
      Transform target = null;
      switch (outfitType)
      {
            case OutfitType.Hair:
                outfit = Resources.Load<GameObject>($"Outfit/Hair/{outfitId}");
                target = hairSlot;
                if (hairSlot.childCount > 0)
                {
                  Destroy(hairSlot.GetChild(0).gameObject);
                }
                HairId = outfitId;
                break;
            case OutfitType.Cloth:
                outfit = Resources.Load<GameObject>($"Outfit/Clothes/{outfitId}");
                target = clothSlot;
                if (clothSlot.childCount > 0)
                {
                  Destroy(clothSlot.GetChild(0).gameObject);
                }
                ClothId = outfitId;
                break;
            case OutfitType.Pant:
                outfit = Resources.Load<GameObject>($"Outfit/Pants/{outfitId}");
                target = pantSlot;
                if (pantSlot.childCount > 0)
                {
                  Destroy(pantSlot.GetChild(0).gameObject);
                }
                PantId = outfitId;
                break;
            case OutfitType.Shoes:
                outfit = Resources.Load<GameObject>($"Outfit/Shoes/{outfitId}");
                target = shoesSlot;
                if (shoesSlot.childCount > 0)
                {
                  Destroy(shoesSlot.GetChild(0).gameObject);
                }
                ShoesId = outfitId;
                break;
      }
      var outfitObj = Instantiate(outfit, target);
      var smr = outfitObj.GetComponent<Outfit>().SkinnedMeshRenderer;
      var bones = SkinnedMeshHelper.GetNewBones(avatarSkinnedMesh, smr);
      smr.bones = bones;
    }
}ChangeOutfitController.cs

换装演示场景的UI控制逻辑。
using System.Collections;
using System.Collections.Generic;
using UnityEngine;
using UnityEngine.UI;

public class ChangeOutfitController : MonoBehaviour
{
    private Button prevHair;
    private Button nextHair;
    private Button prevCloth;
    private Button nextCloth;
    private Button prevPant;
    private Button nextPant;
    private Button prevShoes;
    private Button nextShoes;

    private EquipmentManager equipmentMgr;

    private int currClothIndex = 1;
    private int currHairIndex = 1;
    private int currPantIndex = 1;
    private int currShoesIndex = 1;

    // Start is called before the first frame update
    void Start()
    {
      equipmentMgr.LoadEquipment();

      prevHair.onClick.AddListener(() => { ChangeOutfit(OutfitType.Hair, false); });
      nextHair.onClick.AddListener(() => { ChangeOutfit(OutfitType.Hair, true); });
      prevCloth.onClick.AddListener(() => { ChangeOutfit(OutfitType.Cloth, false); });
      nextCloth.onClick.AddListener(() => { ChangeOutfit(OutfitType.Cloth, true); });
      prevPant.onClick.AddListener(() => { ChangeOutfit(OutfitType.Pant, false); });
      nextPant.onClick.AddListener(() => { ChangeOutfit(OutfitType.Pant, true); });
      prevShoes.onClick.AddListener(() => { ChangeOutfit(OutfitType.Shoes, false); });
      nextShoes.onClick.AddListener(() => { ChangeOutfit(OutfitType.Shoes, true); });
    }

    private void ChangeOutfit(OutfitType outfitType, bool isNext)
    {
      switch (outfitType)
      {
            case OutfitType.Hair:
                if (isNext)
                {
                  currHairIndex = currHairIndex < 5 ? ++currHairIndex : 1;
                }
                else
                {
                  currHairIndex = currHairIndex > 1 ? --currHairIndex : 5;
                }
                equipmentMgr.ChangeOutfit(outfitType, currHairIndex);
                break;
            case OutfitType.Cloth:
                if (isNext)
                {
                  currClothIndex = currClothIndex < 5 ? ++currClothIndex : 1;
                }
                else
                {
                  currClothIndex = currClothIndex > 1 ? --currClothIndex : 5;
                }
                equipmentMgr.ChangeOutfit(outfitType, currClothIndex);
                break;
            case OutfitType.Pant:
                if (isNext)
                {
                  currPantIndex = currPantIndex < 5 ? ++currPantIndex : 1;
                }
                else
                {
                  currPantIndex = currPantIndex > 1 ? --currPantIndex : 5;
                }
                equipmentMgr.ChangeOutfit(outfitType, currPantIndex);
                break;
            case OutfitType.Shoes:
                if (isNext)
                {
                  currShoesIndex = currShoesIndex < 5 ? ++currShoesIndex : 1;
                }
                else
                {
                  currShoesIndex = currShoesIndex > 1 ? --currShoesIndex : 5;
                }
                equipmentMgr.ChangeOutfit(outfitType, currShoesIndex);
                break;
      }
    }
}许可声明

请勿使用美术素材在任何形式的作品中,谢谢。

欢迎关注我的技术博客和B站:
B站:https://space.bilibili.com/4814333
技术博客:https://moecia.github.io/
页: [1]
查看完整版本: Unity换装系统