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]);
}
}
}
}
}
代码中使用了两种换装方式,一般有带蒙皮信息的部位换装需要合并网格,骨骼,材质球。
不带蒙皮信息的如代码中的武器,就直接将武器挂到挂点上并且初始化位置即可。
感觉整篇写下来表达的还是不够清晰,如果有意见的欢迎找我交流~~