Unity3D角色换装及换装编辑器
2016-01-12 19:49
681 查看
Unity换装系统 第一篇
Darren原创.欢迎转载,转载请注明出处.这几天在研究换装系统,现在将研究过程中的思路记录下来。
换装系统本人分两步来实现:
1本地换装以及换装编辑器
2换装资源规范制定,以及通过读取网络资源实现换装
今天先记录本地换装,换装的步骤为:
1.创建主骨架。
主骨架有两个方法获取到:
第一个方法是美术直接输出不带蒙皮的骨骼信息
第二个方法是美术输出带蒙皮的模型,将模型所有带SkinnedMeshRenderer的gameobject删掉,剩下的就是主骨架
_go = Instantiate(prefab) as GameObject; foreach (SkinnedMeshRenderer item in _go.GetComponentsInChildren<SkinnedMeshRenderer>()) { 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<SkinnedMeshRenderer>(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<Material>(); 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<Material> materials; public Mesh mesh; public List<string> bonesName; } public GameObject Generate(GameObject root, Dictionary<string, SkinMeshInfo> _skmr) { float startTime = Time.realtimeSinceStartup; List<CombineInstance> combineInstances = new List<CombineInstance>(); List<Material> materials = new List<Material>(); List<Transform> bones = new List<Transform>(); Transform[] transforms = root.GetComponentsInChildren<Transform>(); 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<SkinnedMeshRenderer>(); 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<AvtarInstanceInfo> _avtarObjects = new List<AvtarInstanceInfo>(); 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<Material> 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<AvtarInstanceInfo>(); 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<SkinnedMeshRenderer>(); 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<SkinnedMeshRenderer>(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<Material>(); 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<CombineInstance> combineInstances = new List<CombineInstance>(); List<Material> materials = new List<Material>(); List<Transform> bones = new List<Transform>(); Transform[] transforms = root.GetComponentsInChildren<Transform>(); 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<SkinnedMeshRenderer>(); 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<Transform>(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 异步读取CSV文件
- 利用Microsoft.Practices.Unity的拦截技术,实现.NET中的AOP
- Unity3D游戏开发之如何发布Android游戏
- Unity3D游戏开发软件破解版安装
- Unity3D游戏开发软件破解版安装
- Unity3D游戏开发之如何发布Android游戏
- Unity3D中js与C#之间相互调用的解决办法
- 1.unity3d中的坐标系
- unity3d 5.x networking
- unity3d中的欧拉角
- Unity资源管理汇总
- Unity3D中的Coroutine详解
- unity3d热更新插件uLua学习整理
- Unity3d 开发(六) 5.x AssetBundle使用
- Unity3D入门
- OpenCV Configuration on Visual Studio 2015 Community
- (转)续上文,Unity3D面试ABC
- Unity3D安卓打包参数配置与兼容性的关系分析
- 解决Animation 添加AnimationClip 无效的问题
- unity3d 技能编辑器