Unity3D角色换装及换装编辑器

Unity换装系统 第一篇

Darren原创.欢迎转载,转载请注明出处.

这几天在研究换装系统,现在将研究过程中的思路记录下来。
换装系统本人分两步来实现:
1本地换装以及换装编辑器
2换装资源规范制定,以及通过读取网络资源实现换装

今天先记录本地换装,换装的步骤为:

1.创建主骨架。
主骨架有两个方法获取到:
第一个方法是美术直接输出不带蒙皮的骨骼信息
第二个方法是美术输出带蒙皮的模型,将模型所有带SkinnedMeshRenderer的gameobject删掉,剩下的就是主骨架

 _go = Instantiate(prefab) as GameObject;

foreach (SkinnedMeshRenderer item in _go.GetComponentsInChildren())
{
      Object.DestroyImmediate(item.gameObject);
}

2.记录需要换装的部件中的所有 SkinnedMeshRenderer中的蒙皮信息,具体需要记录SkinnedMeshRenderer.bones,SkinnedMeshRenderer.sharedMaterials以及SkinnedMeshRenderer.sharedMesh

 public struct AvtarInfo
 {
     public Dictionary<string, SkinMeshInfo> skmr;
 }

private void _GetSkinMeshInfo(GameObject prefab, AvtarInfo avtarinfo)
{
        foreach (SkinnedMeshRenderer item in prefab.GetComponentsInChildren(true))
        {
            List<string> bonesName = new List<string>();
            for (int i = 0; i < item.bones.Length; i++)
            {
                bonesName.Add(item.bones[i].name);
            }

            SkinMeshInfo info = new SkinMeshInfo();
            info.mesh = item.sharedMesh;
            info.materials = new List();
            info.bonesName = bonesName;
            for (int i = 0; i < item.sharedMaterials.Length; i++)
            {
                info.materials.Add(Instantiate(item.sharedMaterials[i]) as Material);
            }
            avtarinfo.skmr.Add(item.name, info);
        }
}

3。将所有换装的Mesh合并成一个Mesh,将所有骨骼合并成一个Transform数组,将所有部件中的materials
合并层一个合计。并将这些数据赋予主骨架物体上的SkinnedMeshRenderer上对应的成员变量

 public struct SkinMeshInfo
 {
    public List materials;
    public Mesh mesh;
    public List<string> bonesName;
 }

 public GameObject Generate(GameObject root, Dictionary<string, SkinMeshInfo> _skmr)
 {
        float startTime = Time.realtimeSinceStartup;

        List combineInstances = new List();
        List materials = new List();
        List bones = new List();
        Transform[] transforms = root.GetComponentsInChildren();

        foreach (var item in _skmr)
        {
            for (int sub = 0; sub < item.Value.mesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = item.Value.mesh;
                ci.subMeshIndex = sub;
                combineInstances.Add(ci);
            }

            foreach (string bone in item.Value.bonesName)
            {
                foreach (Transform transform in transforms)
                {
                    if (transform.name != bone) continue;
                    bones.Add(transform);
                    break;
                }
            }
            materials.AddRange(item.Value.materials);
        }

        SkinnedMeshRenderer r = root.GetComponent();
        r.sharedMesh = new Mesh();
        r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
        r.bones = bones.ToArray();
        r.materials = materials.ToArray();
        return root;
 }

整体的流程为:加载主骨架->读取要替换的资源中的所有蒙皮信息并存储->生成新的Mesh,将Mesh与子物体中所有的bones,materials赋予父物体的SkinnedMeshRenderer中对应的成员。

通过这种方法实现换装需要将资源拆分,在编辑器中没办法直接看到合并之后的效果,所以我做了一个编辑器,可以在非运行状态直接看最终合成之后的效果,下面贴上代码:

using UnityEngine;
using System.Collections;
using System.Collections.Generic;

using UnityEditor;

public class AvtarEditorWindow : EditorWindow
{
    private List _avtarObjects = new List();
    private GameObject _skeleton;
    private GameObject _body;
    private GameObject _wing;
    private GameObject _equip;
    private int _equipBoneCount = 0;
    public Dictionary<int, string> _equipBlindBoneNames = new Dictionary<int, string>();
    private int _avtarCount = 0;
    private const string AVTAR_SKELETON = "AVTAR_SKELETON";
    private const string AVTAR_BODY = "AVTAR_BODY";
    private const string AVTAR_WING = "AVTAR_WING";
    private const string AVTAR_EQUIP = "AVTAR_EQUIP";

    public struct AvtarInstanceInfo
    {
        public GameObject go;
        public AvtarInfo info;
    }

    public struct AvtarInfo
    {
        public Dictionary<string, SkinMeshInfo> skmr;
    }

    public struct SkinMeshInfo
    {
        public List materials;
        public Mesh mesh;
        public List<string> bonesName;
    }

    [MenuItem("Tool/Open Avtar EditWindow")]
    public static void OpenAvtarWindow()
    {
        AvtarEditorWindow window = (AvtarEditorWindow)EditorWindow.GetWindow(typeof(AvtarEditorWindow));
        window.Show();
    }

    void OnEnable()
    {
        _avtarObjects = new List();
        try
        {
            Debug.Log(EditorPrefs.GetString(AVTAR_SKELETON).Replace(Application.dataPath, "Assets"));
            _skeleton = AssetDatabase.LoadAssetAtPath(EditorPrefs.GetString(AVTAR_SKELETON).Replace(Application.dataPath, "Assets"), typeof(GameObject)) as GameObject;
            _body = AssetDatabase.LoadAssetAtPath(EditorPrefs.GetString(AVTAR_BODY).Replace(Application.dataPath, "Assets"), typeof(GameObject)) as GameObject;
            _wing = AssetDatabase.LoadAssetAtPath(EditorPrefs.GetString(AVTAR_WING).Replace(Application.dataPath, "Assets"), typeof(GameObject)) as GameObject;
            _equip = AssetDatabase.LoadAssetAtPath(EditorPrefs.GetString(AVTAR_EQUIP).Replace(Application.dataPath, "Assets"), typeof(GameObject)) as GameObject;
        }
        catch
        {
            Debug.Log("获取资源出错");
        }
    }

    void OnDestroy()
    {
        _avtarCount = 0;
        if (_avtarObjects.Count > 0)
        {
            for (int i = 0; i < _avtarObjects.Count; i++)
            {
                if (_avtarObjects[i].go)
                {
                    DestroyImmediate(_avtarObjects[i].go);
                }
            }

            _avtarObjects = null;
        }

        if (_skeleton)
            EditorPrefs.SetString(AVTAR_SKELETON, AssetDatabase.GetAssetPath(_skeleton.GetInstanceID()));
        if (_body)
            EditorPrefs.SetString(AVTAR_BODY, AssetDatabase.GetAssetPath(_body.GetInstanceID()));
        if (_wing)
            EditorPrefs.SetString(AVTAR_WING, AssetDatabase.GetAssetPath(_wing.GetInstanceID()));
        if (_equip)
            EditorPrefs.SetString(AVTAR_EQUIP, AssetDatabase.GetAssetPath(_equip.GetInstanceID()));
    }

    void OnGUI()
    {
        _skeleton = EditorGUILayout.ObjectField(new GUIContent("主骨架"), _skeleton, typeof(GameObject)) as GameObject;
        _body = EditorGUILayout.ObjectField(new GUIContent("身体蒙皮"), _body, typeof(GameObject)) as GameObject;
        _wing = EditorGUILayout.ObjectField(new GUIContent("翅膀蒙皮"), _wing, typeof(GameObject)) as GameObject;
        _equip = EditorGUILayout.ObjectField(new GUIContent("武器"), _equip, typeof(GameObject)) as GameObject;
        _equipBoneCount = EditorGUILayout.IntField("武器骨骼绑点数量:", _equipBoneCount);

        if (_equipBoneCount > 0)
        {
            for (int i = 0; i < _equipBoneCount; i++)
            {
                if (!_equipBlindBoneNames.ContainsKey(i))
                {
                    _equipBlindBoneNames.Add(i, "");
                }

                _equipBlindBoneNames[i] = EditorGUILayout.TextField(string.Format("骨骼名称{0}:", i.ToString()), _equipBlindBoneNames[i]);
            }
        }

        if (GUILayout.Button("Create"))
        {
            _CreateAvtar();
        }

        if (_avtarObjects.Count > 0)
        {
            for (int i = 0; i < _avtarObjects.Count; i++)
            {
                if (_avtarObjects[i].go != null && GUILayout.Button(_avtarObjects[i].go.name))
                {
                    DestroyImmediate(_avtarObjects[i].go);
                    _avtarObjects.RemoveAt(i);
                    break;
                }
            }
        }
    }

    private void _CreateAvtar()
    {
        if (_skeleton == null)
        {
            Debug.Log("主骨架为空,无法生成模型");
            return;
        }
        _avtarCount++;
        AvtarInstanceInfo avtarInfo = new AvtarInstanceInfo();
        avtarInfo.info = new AvtarInfo();
        avtarInfo.info.skmr = new Dictionary<string, SkinMeshInfo>();
        GameObject temp = Instantiate(_skeleton, Vector3.zero, new Quaternion(0, 180, 0, 1)) as GameObject;
        temp.AddComponent();
        if (_body) _GetSkinMeshInfo(_body, avtarInfo.info);
        if (_wing) _GetSkinMeshInfo(_wing, avtarInfo.info);
        avtarInfo.go = Generate(temp, avtarInfo.info.skmr);
        avtarInfo.go.name += "_" + _avtarCount;
        MountEquip(avtarInfo.go);
        _avtarObjects.Add(avtarInfo);
    }

    private void _GetSkinMeshInfo(GameObject prefab, AvtarInfo avtarinfo)
    {
        foreach (SkinnedMeshRenderer item in prefab.GetComponentsInChildren(true))
        {
            List<string> bonesName = new List<string>();
            for (int i = 0; i < item.bones.Length; i++)
            {
                bonesName.Add(item.bones[i].name);
            }

            SkinMeshInfo info = new SkinMeshInfo();
            info.mesh = item.sharedMesh;
            info.materials = new List();
            info.bonesName = bonesName;
            for (int i = 0; i < item.sharedMaterials.Length; i++)
            {
                info.materials.Add(Instantiate(item.sharedMaterials[i]) as Material);
            }
            avtarinfo.skmr.Add(item.name, info);
        }
    }

    public GameObject Generate(GameObject root, Dictionary<string, SkinMeshInfo> _skmr)
    {
        float startTime = Time.realtimeSinceStartup;

        List combineInstances = new List();
        List materials = new List();
        List bones = new List();
        Transform[] transforms = root.GetComponentsInChildren();

        foreach (var item in _skmr)
        {
            for (int sub = 0; sub < item.Value.mesh.subMeshCount; sub++)
            {
                CombineInstance ci = new CombineInstance();
                ci.mesh = item.Value.mesh;
                ci.subMeshIndex = sub;
                combineInstances.Add(ci);
            }

            foreach (string bone in item.Value.bonesName)
            {
                foreach (Transform transform in transforms)
                {
                    if (transform.name != bone) continue;
                    bones.Add(transform);
                    break;
                }
            }
            materials.AddRange(item.Value.materials);
        }

        SkinnedMeshRenderer r = root.GetComponent();
        r.sharedMesh = new Mesh();
        r.sharedMesh.CombineMeshes(combineInstances.ToArray(), false, false);
        r.bones = bones.ToArray();
        r.materials = materials.ToArray();
        return root;
    }

    public void MountEquip(GameObject root)
    {
        Transform[] transforms = root.GetComponentsInChildren(true);
        for (int i = 0; i < _equipBoneCount; i++)
        {
            for (int j = 0; j < transforms.Length; j++)
            {
                if (transforms[j].name == _equipBlindBoneNames[i])
                {
                    GameObject equip = Instantiate(_equip) as GameObject;
                    equip.transform.parent = transforms[j];
                    equip.transform.localPosition = Vector3.zero;
                    equip.transform.localRotation = Quaternion.identity;
                    equip.transform.localScale = Vector3.one;
                    break;
                }

                if (j == transforms.Length - 1)
                {
                    Debug.Log("找不到骨骼" + _equipBlindBoneNames[i]);
                }
            }
        }
    }



}

代码中使用了两种换装方式,一般有带蒙皮信息的部位换装需要合并网格,骨骼,材质球。
不带蒙皮信息的如代码中的武器,就直接将武器挂到挂点上并且初始化位置即可。

感觉整篇写下来表达的还是不够清晰,如果有意见的欢迎找我交流~~

你可能感兴趣的:(Unity3d)