#unity/白日梦/代码缓存 ```cs using System; using System.Collections.Generic; using UnityEngine; using UnityEditor; using System.IO; using System.Text; using System.Linq; using System.Text.RegularExpressions; using ResourcesModule.LMNA; namespace SkinToolModule.M.Editor { public struct LODDynamicColliderData { public Transform colTrans; public string name; public float radius; public string rootName; public LODDynamicColliderData(string n, float r = 0.3f, string rn = "", Transform c = null) { name = n; radius = r; rootName = rn; colTrans = c; } } public struct LODDynamicBoneParams { public Transform root; public float Damping; public float Elasticty; public float Stiffness; public float Inert; public float Friction; public float Radius; public List colliderDataList; public LODDynamicBoneParams(Transform t, float d = 0.02f, float e = 0.08f, float s = 0.06f, float i = 0.8f, float f = 0.6f, float r = 0.0f, List colliders = null) { root = t; Damping = d; Elasticty = e; Stiffness = s; Inert = i; Friction = f; Radius = r; colliderDataList = colliders; } } public class SkinToolEditor : EditorWindow { private enum MatType { Character, Transparent, Fakeskin, Fur, Error, } private static SkinToolEditor m_Window; public const string m_SuiteLevelPath = "Assets/Editor/ArtResources/Tool/Config/SuiteLevel.txt"; private const string m_DefaultMatPath = "Assets/Editor/ArtResources/Tool/Config/DefaultMaterial.mat"; private const string m_FbxDirIng = "/ResourcesRaw/CommonRegion/InGame/Skins/Characters/"; private const string m_FbxDirLob = "/ResourcesRaw/CommonRegion/OutsideGame/Skins/Characters/"; private const string m_PrefabDirIng = "Assets/BundleResources/CommonRegion/InGame/Skins/Characters/"; private const string m_PrefabDirLob = "Assets/BundleResources/CommonRegion/OutsideGame/Skins/Characters/"; private const string m_DynamicBoneParamDir = "Assets/Editor/ArtResources/Tool/LODDynamicBoneParams"; private string m_OldFBXPath = "Assets/ResourcesRaw/CommonRegion/Skins/Characters/Models/"; private bool m_IsLobby; private bool m_IsOld; // 是否是重导的减面资源 private GameObject m_OldPrefab; private FileInfo m_FbxFile; private FileInfo[] m_TgaFiles; private string m_FolderPath = ""; private string m_FbxPath = ""; private string m_PrefabPath = ""; private string LOD_m_PrefabPath = ""; private string m_DynamicBonePrefabPath; private Dictionary levelDic = new Dictionary { ["lv0"] = eAssetLodLevel.Lv0, ["lv1"] = eAssetLodLevel.Lv1, ["lv2"] = eAssetLodLevel.Lv2, ["lv3"] = eAssetLodLevel.Lv3, ["lv4"] = eAssetLodLevel.Lv4 }; private Dictionary sceneDic = new Dictionary { ["lob"] = eAssetSceneType.Lobby, ["ing"] = eAssetSceneType.InGame, }; private eAssetSceneType currentAssetSceneType = eAssetSceneType.Unknown; private eAssetLodLevel currentAssetLodLevel = eAssetLodLevel.None; private Dictionary mFbxFileList = new Dictionary(); private Dictionary mFolderPathList = new Dictionary(); private Dictionary mFbxPathList = new Dictionary(); private Dictionary mPrefabPathList = new Dictionary(); // 品质相关 public enum SuiteLevel { white = 0, blue = 1, purple = 2, orangee = 3, pink = 4, }; private SuiteLevel m_SuiteLevel = SuiteLevel.white; private GameObject m_FbxSenceObject; // Dynamic bone相关 private GameObject m_DynamicBoneSenceObject; Dictionary m_TaggedTransformDict = new Dictionary(); // 被标记的骨骼节点 - 对应父节点名字 Dictionary m_TagToDynamicBoneParamDict = new Dictionary(); // 被标记的骨骼节点名字 - 对应DynamicBone参数组 List m_SkinPartTransformList = new List(); // 部位 // 眨眼相关 string BlinkFaceTag = "head_fx_01"; Dictionary m_BlinkFaceDict = new Dictionary { { "Thief_08", new string[]{"Thief_08_head_OpenEyes_01_height", "Thief_08_head_CloseEyes_01_height"} }, { "Police_05", new string[]{ "Police_05_head_OpenEyes_01_height", "Police_05_head_CloseEyes_01_height" } }, }; // 界面相关 Color m_BlueColor = new Color(129 / 255f, 209 / 255f, 248 / 255f, 1); Color m_PinkColor = new Color(253 / 255f, 184 / 255f, 225 / 255f, 1); Color m_RedColor = new Color(233 / 255f, 74 / 255f, 110 / 255f, 1); Color m_PurpColor = new Color(125 / 255f, 128 / 255f, 227 / 255f, 1); float m_RowHeight = 20f; int m_RowFontSize = 12; GUIStyle m_MainHeaderStyle; GUIStyle m_SubHeaderStyle; GUIStyle m_ButtonStyle; GUIStyle m_TipsStyle; GUIStyle m_ErrorTipsStyle; GUIStyle m_LabelStyle; GUIStyle m_TagLabelStyle; GUIStyle m_ParamsLabelStyle; private string[] ModelName = new[] { "clothes", "hand", "head", "shoes", "trousers", }; private string[] DynamicBoneTag = new[] { "CLOTHES_", "HEAD_", "HAND_", "SHOES_", "TROUSERS_" }; [UnityEditor.MenuItem("美术工具/导入时装资源", false, 1)] static void ShowWindow() { if (m_Window == null) { m_Window = (SkinToolEditor)EditorWindow.GetWindow(typeof(SkinToolEditor), true, "SkinToolEditor"); m_Window.Show(); } else { m_Window.Close(); m_Window = null; } } #region GUI void OnGUI() { InitStyles(); EditorGUILayout.BeginVertical(); // Title EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("Bunny Editor", m_MainHeaderStyle, GUILayout.Height(30)); if (GUILayout.Button("清除", m_ButtonStyle, GUILayout.Width(150), GUILayout.Height(m_RowHeight))) { if (EditorUtility.DisplayDialog("警告", "确定要清除本次生成的所有资源么?", "删就完事儿了")) { OnClickClearButton(); } } // if (GUILayout.Button(m_IsOld?"当前:老资源合入": "当前:新资源合入", m_ButtonStyle, GUILayout.Width(250), GUILayout.Height(m_RowHeight))) // { // m_IsOld = !m_IsOld; // } EditorGUILayout.EndHorizontal(); EditorGUILayout.Space(); DrawSplitLine(25); // Load fbx if (GUILayout.Button("选fbx所在文件夹", m_ButtonStyle, GUILayout.Width(150), GUILayout.Height(m_RowHeight))) { OnClickCreateButton(OnClickLoadButton()); } EditorGUILayout.Space(); // Show FolderPath // if (!string.IsNullOrEmpty(m_FolderPath)) // { // if (!Directory.Exists(m_FolderPath)&&!m_IsOld) // { // EditorGUILayout.TextArea("地址已存在:\n" + m_FolderPath, m_TipsStyle); // } // else if(!Directory.Exists(m_FolderPath)&&m_IsOld) // { // EditorGUILayout.TextArea("导入完毕,资源地址为:\n" + m_OldFBXPath, m_TipsStyle); // } // else // { // EditorGUILayout.TextArea("导入完毕,资源地址为:\n" + m_FolderPath, m_TipsStyle); // } // } // else // { // EditorGUILayout.TextArea("无资源导入...", m_TipsStyle); // } EditorGUILayout.Space(); // Select Suite Level // m_SuiteLevel = (SuiteLevel)EditorGUILayout.EnumPopup("选择时装品质", m_SuiteLevel, GUILayout.Width(300)); // EditorGUILayout.Space(); // Create Prefab without Dynamic bone // if (GUILayout.Button("生成模型", m_ButtonStyle, GUILayout.Width(150), GUILayout.Height(m_RowHeight))) // { // OnClickCreateButton(); // } EditorGUILayout.Space(); // show prefab path if (!string.IsNullOrEmpty(LOD_m_PrefabPath)) { EditorGUILayout.TextArea("创建Prefab完毕,资源地址为:\n" + LOD_m_PrefabPath, m_TipsStyle); } else { EditorGUILayout.TextArea("无Prefab创建...", m_TipsStyle); } EditorGUILayout.Space(); // Put prefab in // EditorGUILayout.TextArea("部件模型路径", m_LabelStyle); // Rect modelRect = EditorGUILayout.GetControlRect(GUILayout.Width(500)); // m_DynamicBonePrefabPath = EditorGUI.TextField(modelRect, m_DynamicBonePrefabPath); // EditorGUILayout.Space(); // if ((Event.current.type == EventType.DragUpdated) // && modelRect.Contains(Event.current.mousePosition)) // { // DragAndDrop.visualMode = DragAndDropVisualMode.Generic; // } // // if ((Event.current.type == EventType.DragPerform) && modelRect.Contains(Event.current.mousePosition)) // { // if (DragAndDrop.paths != null && DragAndDrop.paths.Length > 0) // { // m_DynamicBonePrefabPath = DragAndDrop.paths[0]; // } // } // // Analysis Dynamic bone params // if (GUILayout.Button("生成DynamicBone参数", m_ButtonStyle, GUILayout.Width(150), // GUILayout.Height(m_RowHeight))) // { // } // OnClickCreateLODDynamicBoneParams(); // EditorGUILayout.Space(); // if (m_TagToDynamicBoneParamDict.Count > 0) // { // //在 foreach 中修改Dictionary中的值是不允许的,可以将key 先放在List中,foreach 这个list ,找到需要修改的项后,再修改原Dic中的内容 // OnDrawListView(); // EditorGUILayout.Space(); // EditorGUILayout.BeginHorizontal(); // // Create Prefab with Dynamic bone // if (GUILayout.Button("挂DynamicBone脚本", m_ButtonStyle, GUILayout.Width(200), // GUILayout.Height(m_RowHeight))) // { // OnClickDynamicBoneButton(); // } // // if (GUILayout.Button("导入DynamicBone文件", m_ButtonStyle, GUILayout.Width(200), // GUILayout.Height(m_RowHeight))) // { // OnClickImportDynamicBoneFile(); // } // // if (GUILayout.Button("导出DynamicBone文件", m_ButtonStyle, GUILayout.Width(200), // GUILayout.Height(m_RowHeight))) // { // OnClickExportDynamicBoneFile(); // } // // EditorGUILayout.EndHorizontal(); // // } } /// /// 初始化,清空数据 /// void ClearData() { mFbxFileList.Clear(); mFolderPathList.Clear(); mFbxFileList.Clear(); mPrefabPathList.Clear(); } void OnDestroy() { ClearScenePrefabs(); } void InitStyles() { if (m_MainHeaderStyle == null) { m_MainHeaderStyle = new GUIStyle(GUI.skin.label); m_MainHeaderStyle.fontSize = 20; m_MainHeaderStyle.fontStyle = FontStyle.Bold; m_MainHeaderStyle.normal.textColor = m_BlueColor; } if (m_SubHeaderStyle == null) { m_SubHeaderStyle = new GUIStyle(GUI.skin.label); m_SubHeaderStyle.fontSize = 14; m_SubHeaderStyle.fontStyle = FontStyle.Bold; m_SubHeaderStyle.normal.textColor = m_BlueColor; } if (m_ButtonStyle == null) { m_ButtonStyle = new GUIStyle(GUI.skin.button); m_ButtonStyle.fontSize = 14; } if (m_TipsStyle == null) { m_TipsStyle = new GUIStyle(GUI.skin.label); m_TipsStyle.normal.textColor = m_PinkColor; m_TipsStyle.fontSize = m_RowFontSize; } if (m_ErrorTipsStyle == null) { m_ErrorTipsStyle = new GUIStyle(GUI.skin.label); m_ErrorTipsStyle.normal.textColor = m_RedColor; m_ErrorTipsStyle.fontSize = m_RowFontSize; } if (m_LabelStyle == null) { m_LabelStyle = new GUIStyle(GUI.skin.label); m_LabelStyle.normal.textColor = m_BlueColor; m_LabelStyle.fontSize = 18; } if (m_TagLabelStyle == null) { m_TagLabelStyle = new GUIStyle(GUI.skin.label); m_TagLabelStyle.normal.textColor = m_PinkColor; m_TagLabelStyle.fontSize = 16; } if (m_ParamsLabelStyle == null) { m_ParamsLabelStyle = new GUIStyle(GUI.skin.label); m_ParamsLabelStyle.normal.textColor = m_PurpColor; m_ParamsLabelStyle.fontSize = 13; } } void DrawSplitLine(float height) { GUI.Box(new Rect(0, height, position.width, 1), string.Empty); } // 这里是scrollview Vector2 _scrollPos; void OnDrawListView() { _scrollPos = EditorGUILayout.BeginScrollView(_scrollPos, false, false, GUILayout.Width(500), GUILayout.Height(400)); List keyList = new List(); foreach (KeyValuePair pair in m_TagToDynamicBoneParamDict) { keyList.Add(pair.Key); } foreach (string key in keyList) { EditorGUILayout.TextArea(key, m_TagLabelStyle, GUILayout.Height(20)); LODDynamicBoneParams inputValue; float Damping = EditorGUILayout.FloatField("Damping", m_TagToDynamicBoneParamDict[key].Damping, m_ParamsLabelStyle); float Elasticty = EditorGUILayout.FloatField("Elasticty", m_TagToDynamicBoneParamDict[key].Elasticty, m_ParamsLabelStyle); float Stiffness = EditorGUILayout.FloatField("Stiffness", m_TagToDynamicBoneParamDict[key].Stiffness, m_ParamsLabelStyle); float Inert = EditorGUILayout.FloatField("Inert", m_TagToDynamicBoneParamDict[key].Inert, m_ParamsLabelStyle); float Friction = EditorGUILayout.FloatField("Friction", m_TagToDynamicBoneParamDict[key].Friction, m_ParamsLabelStyle); float Radius = EditorGUILayout.FloatField("Radius", m_TagToDynamicBoneParamDict[key].Friction, m_ParamsLabelStyle); EditorGUILayout.BeginHorizontal(); if (GUILayout.Button("+", m_ButtonStyle, GUILayout.Width(50), GUILayout.Height(m_RowHeight))) { OnClickAddCollider(key); } if (GUILayout.Button("-", m_ButtonStyle, GUILayout.Width(50), GUILayout.Height(m_RowHeight))) { OnClickRemoveCollider(key); } EditorGUILayout.EndHorizontal(); if (m_TagToDynamicBoneParamDict[key].colliderDataList != null) { for (int i = 0; i < m_TagToDynamicBoneParamDict[key].colliderDataList.Count; i++) { EditorGUILayout.BeginHorizontal(); LODDynamicColliderData inputColliderValue; // 列出这个标记位下绑到的所有collider信息 List colliderList = m_TagToDynamicBoneParamDict[key].colliderDataList; LODDynamicColliderData collider = colliderList[i]; EditorGUILayout.LabelField(collider.name, GUILayout.MaxWidth(80)); float collRadius = EditorGUILayout.FloatField("碰撞体半径:", collider.radius, m_ParamsLabelStyle); EditorGUILayout.LabelField("关联骨骼名字:", GUILayout.MaxWidth(80)); GUI.SetNextControlName("RootNameText"); string collAttachName = EditorGUILayout.TextField(collider.rootName, m_ParamsLabelStyle, GUILayout.MaxWidth(200), GUILayout.Height(m_RowHeight)); EditorGUILayout.EndHorizontal(); // 改碰撞体list if (collRadius != collider.radius || collAttachName != collider.rootName) { inputColliderValue = new LODDynamicColliderData(collider.name, collRadius, collAttachName, collider.colTrans); colliderList[i] = inputColliderValue; inputValue = new LODDynamicBoneParams(null, Damping, Elasticty, Stiffness, // TODO: 这些地方要把null替换掉! Inert, Friction, Radius, colliderList); m_TagToDynamicBoneParamDict[key] = inputValue; } } } // 只改Dynamicbone的数字参数,不涉及碰撞体list if (Damping != m_TagToDynamicBoneParamDict[key].Damping || Elasticty != m_TagToDynamicBoneParamDict[key].Elasticty || Stiffness != m_TagToDynamicBoneParamDict[key].Stiffness || Inert != m_TagToDynamicBoneParamDict[key].Inert || Friction != m_TagToDynamicBoneParamDict[key].Friction || Radius != m_TagToDynamicBoneParamDict[key].Radius) { List colliderList = m_TagToDynamicBoneParamDict[key].colliderDataList; inputValue = new LODDynamicBoneParams(null, Damping, Elasticty, Stiffness, Inert,// TODO: 这些地方要把null替换掉! Friction, Radius, colliderList); m_TagToDynamicBoneParamDict[key] = inputValue; } EditorGUILayout.Space(); } EditorGUILayout.EndScrollView(); // 组结束 } #endregion #region LoadFile bool OnClickLoadButton() { ClearData(); bool success = true; m_OldPrefab = null; string defaultFolder = PlayerPrefs.GetString("BunnyflopFilesDefalutFolder", ""); string folder = EditorUtility.OpenFolderPanel("Select Versions Folder", defaultFolder, ""); if (string.IsNullOrEmpty(folder)) { return false; } m_SuiteLevel = SuiteLevel.white; PlayerPrefs.SetString("BunnyflopFilesDefalutFolder", folder); DirectoryInfo direction = new DirectoryInfo(folder); FileInfo[] fbxFiles = direction.GetFiles("*.fbx", SearchOption.AllDirectories); if (fbxFiles.Length == 0) { NamingChecker.PopupErrorMessage("未检测到有fbx导入!"); return false; } for (int i = 0; i < fbxFiles.Length; i++) { m_FbxFile = fbxFiles[i];// 只允许载入一个 mFbxFileList.Add(i,m_FbxFile); // if(m_IsOld) // { // // 重新导入性能优化使用的低模不检查命名规范,但要找到老的fbx,不然就报错退出 // m_OldPrefab = FindOldPrefabByFbxFile(m_FbxFile.Name); // if(m_OldPrefab==null) // { // EditorUtility.DisplayDialog("警告", "项目内找不到名为"+m_FbxFile.Name+"的模型文件,请确认减面优化资源命名是否与老资源完全一致!", "我知道了"); // return; // } // } // else // { // bool isPassCheck = NamingChecker.PreCheck(m_FbxFile); // if (!isPassCheck) // { // return; // } // } m_IsLobby = !m_FbxFile.Name.Contains("Low") && !m_FbxFile.Name.Contains("low")&&!m_FbxFile.Name.Contains("&ing"); success = LoadFbxFile(i); if (success == false) { break; } } return success; } GameObject FindOldPrefabByFbxFile(string fileName) { string fbxName = fileName.Split('.')[0]; string[] allPath = AssetDatabase.FindAssets("t:Model", new string[] { "Assets/ResourcesRaw/CommonRegion/Skins/Characters/Models" }); foreach(string singlePath in allPath) { string assetPath = AssetDatabase.GUIDToAssetPath(singlePath); if(assetPath.Contains(fbxName+".FBX")) m_OldFBXPath = assetPath.Replace(fbxName+".FBX",""); if(assetPath.Contains(fbxName+".fbx")) m_OldFBXPath = assetPath.Replace(fbxName+".fbx",""); if(!GetFbxName(assetPath).Equals(fbxName)) { continue; } // 找到同名的fbx之后再找依赖fbx的prefab GameObject fbx = AssetDatabase.LoadAssetAtPath(assetPath, typeof(GameObject)) as GameObject; GameObject dependPrefab = GetPrefabDependByThisFbx(singlePath,fbxName); return dependPrefab; } return null; } GameObject GetPrefabDependByThisFbx(string fbxGuid,string fbxName) { List prefabExtension = new List() { ".prefab" }; string[] files = new string[10000]; if(fbxName.Contains("low")||fbxName.Contains("&ing")) files = Directory.GetFiles(Application.dataPath + "/BundleResources/CommonRegion/InGame/Skins/Characters/", "*.*", SearchOption.AllDirectories) .Where(s => prefabExtension.Contains(Path.GetExtension(s).ToLower())).ToArray(); if(fbxName.Contains("&lob")||fbxName.Contains("height")) files = Directory.GetFiles(Application.dataPath + "/BundleResources/CommonRegion/OutsideGame/Skins/Characters/", "*.*", SearchOption.AllDirectories) .Where(s => prefabExtension.Contains(Path.GetExtension(s).ToLower())).ToArray(); string prefabFileName = ""; foreach (string file in files) { if (Regex.IsMatch(File.ReadAllText(file), fbxGuid)) { prefabFileName = file; } } if (string.IsNullOrEmpty(prefabFileName)) { return null; } else { // 得到的filename是类似【G:/DMM_Art_B/Assets/BundleResources/Prefabs/Characters/SkinParts\Police_03_01_height_skin.prefab】这样的 string[] words_1 = prefabFileName.Split('/'); string[] words_2 = words_1[words_1.Length - 1].Split('\\'); ; // SkinParts\Police_03_01_height_skin.prefab string prefabName = words_2[words_2.Length-1]; string prefabPath = ""; string pathname = CombinePath(prefabName)+"/Prefabs/"; if(fbxName.Contains("low")||fbxName.Contains("&ing")) prefabPath = Application.dataPath + "/BundleResources/CommonRegion/InGame/Skins/Characters/" +pathname+ prefabName; if(fbxName.Contains("&lob")||fbxName.Contains("height")) prefabPath = Application.dataPath + "/BundleResources/CommonRegion/OutsideGame/Skins/Characters/" +pathname+ prefabName; return AssetDatabase.LoadAssetAtPath(NamingChecker.GetRelativePath(prefabPath), typeof(GameObject)) as GameObject; } } string RenameFBX(string fbxName) { string newName = ""; return newName; } bool LoadFbxFile(int index) { string fbxName = m_FbxFile.Name; // if (fbxName.Contains("height_") && !fbxName.Contains("lob")) // { // EditorUtility.DisplayDialog("错误", fbxName+"的模型文件命名不正确,缺少前缀lob或者多了height后缀!", "我知道了"); // return false; // // fbxName = fbxName.Replace("height_", "").Replace("&", "&lob"); // } // else if (fbxName.Contains("low_") && !fbxName.Contains("ing")) // { // EditorUtility.DisplayDialog("错误", fbxName+"的模型文件命名不正确,缺少前缀ing或者多了low后缀!", "我知道了"); // return false; // // fbxName = fbxName.Replace("low_", "").Replace("&", "&ing"); // } // if (!fbxName.Contains("mdl&")) // { // EditorUtility.DisplayDialog("警告", fbxName+"的模型文件命名不正确缺少前缀!", "我知道了"); // return; // } if(!m_IsOld) { m_FolderPath = CreateFolder(fbxName); m_FbxPath = m_FolderPath + fbxName; mFolderPathList.Add(index, m_FolderPath); mFbxPathList.Add(index,m_FbxPath); if (File.Exists(m_FbxPath)) { File.Delete(m_FbxPath); } m_FbxFile.CopyTo(m_FbxPath); } else { m_FolderPath = m_OldFBXPath; m_FbxPath = m_FolderPath+fbxName; if (File.Exists(m_FbxPath)) { File.Delete(m_FbxPath); } m_FbxFile.CopyTo(m_FbxPath); } /*if (!Directory.Exists(m_FolderPath)) { // NamingChecker.PopupErrorMessage("创建文件夹失败:" + m_FolderPath); }*/ AssetDatabase.Refresh(); // 如果是AnimPrefab则需要reimport一次,否则无法自动挂上Animation脚本(刚导入只能是Animator) if (fbxName.Contains(NamingChecker.AnimPattern)) { ModelImporter mi = AssetImporter.GetAtPath(NamingChecker.GetRelativePath(m_FbxPath)) as ModelImporter; mi.SaveAndReimport(); } // 这里就可以开始分析skinpart命名是否规范 // if(m_IsOld) // { // // } // else // { // if (!NamingChecker.SkinpartCheck(GetFbxGameobject())) // { // Clear(); // return; // } // } return true; } string LODCombinePath(string prefabName) { string realpath = String.Empty; string path = "SkinParts"; string feature = ""; string config = ""; string[] splitname = prefabName.Split('_'); if (prefabName.Contains("&ing") || prefabName.Contains("&lob")) { if (splitname.Length >= 4) { string[] nameList = splitname[0].Split('&'); feature = $"{nameList[1].Replace("lob","").Replace("ing","")}_{splitname[1]}"; config = splitname[2]; realpath = $"{feature}/{path}/{config}"; return realpath; } } else { //兼容原版fbx导入 if (splitname.Length > 4) { feature = $"{splitname[0].Replace("mdl&","")}_{splitname[1]}"; config = splitname[2]; } realpath = $"{feature}/{path}/{config}"; } return realpath; } string CombinePath(string prefabName) { string realpath = String.Empty; string path = "SkinParts"; string feature = ""; string config = ""; string[] splitname = prefabName.Split('_'); if (prefabName.Contains("&ing") || prefabName.Contains("&lob")) { if (splitname.Length >= 4) { // feature = $"{splitname[1]}_{splitname[2]}"; // config = splitname[3]; string[] nameList = splitname[0].Split('&'); string frontDelete = nameList[0] + "&"; feature = $"{splitname[0].Replace("mdl&", "").Replace("pfb&", "").Replace(frontDelete, "").Replace("lob", "").Replace("ing", "")}_{splitname[1]}"; config = splitname[2]; } realpath = $"{feature}/{path}/{config}"; } else { //兼容原版prefab导入 if (splitname.Length >= 4) { feature = $"{splitname[0].Replace("mdl&","").Replace("pfb&","")}_{splitname[1]}"; config = splitname[2]; } realpath = $"{feature}/{path}/{config}"; } return realpath; } void CreatePrefabFodler(string path) { string dirPath = Application.dataPath + path.Replace("Assets/", "/"); if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } if (!Directory.Exists(m_FolderPath)) { NamingChecker.PopupErrorMessage("创建文件夹失败:" + m_FolderPath); } } string CreateFolder(string fbxName) { string path = ""; if (fbxName.Contains("&ing")||fbxName.Contains("low")) { path = m_FbxDirIng + LODCombinePath(fbxName)+"/Models/"; }else if (fbxName.Contains("&lob")||fbxName.Contains("height")) { path = m_FbxDirLob + LODCombinePath(fbxName)+"/Models/"; }else Debug.LogError(fbxName+"FBX命名不规范,未包含前缀lob or ing!"); string dirPath = Application.dataPath + path; if (!Directory.Exists(dirPath)) { Directory.CreateDirectory(dirPath); } return dirPath; } string GetFbxDirectry(string fbxName) { string rst = ""; string[] wordList = fbxName.Split('_'); rst = wordList[0] + wordList[1] + "/SkinParts/" + wordList[2] + "/"; return rst; } string GetFbxName(string assetPath) { string[] wordList = assetPath.Split('/'); return wordList[wordList.Length - 1].Split('.')[0];//TODO } #endregion #region CreatePrefab void OnClickCreateButton(bool isSuccess = true) { if (isSuccess == false) { return; } m_PrefabPath = ""; if (m_FolderPath == "") { return; } for (int i = 0; i < mFolderPathList.Count; i++) { GameObject fbxObject = GetFbxGameobject(mFolderPathList[i],mFbxFileList[i]); // WriteSuiteLevel(); // 性能检查分析 // if (!DataAnalysisUtility.IsInIgnoreList(fbxObject.name) && !DataAnalysisUtility.ResourcesDataCheck(fbxObject)) // { // //此处先忽略 // Clear(); // return; // } CreatePrefab(ref fbxObject,i); } } void WriteSuiteLevel() { string fbxName = m_FbxFile.Name.Replace(".FBX",""); string writeLine = fbxName + "*" + (int)m_SuiteLevel; StreamReader sr = null; if (!File.Exists(m_SuiteLevelPath)) { File.Create(m_SuiteLevelPath).Dispose(); } sr = new StreamReader(m_SuiteLevelPath, Encoding.Default); string readStr = ""; List content = new List(); // 先缓存下所有除了本次处理fbx信息以外 while (sr != null && (readStr = sr.ReadLine()) != null) { if (!readStr.Contains(fbxName)) { content.Add(readStr); } } sr.Close(); // 重写文件内容 StreamWriter sw = new StreamWriter(m_SuiteLevelPath, false); foreach(string str in content) { sw.WriteLine(str); } sw.Close(); // 末尾补上新改的一段 FileInfo fi = new FileInfo(m_SuiteLevelPath); sw = fi.AppendText(); sw.WriteLine(writeLine); sw.Close(); sw.Dispose(); } GameObject GetFbxGameobject(string m_FolderPath,FileInfo m_FbxFile) { DirectoryInfo direction = new DirectoryInfo(m_FolderPath); FileInfo[] fbxFiles = direction.GetFiles("*.fbx", SearchOption.AllDirectories); string fbxFileChoosed = ""; foreach (FileInfo fbxfile in fbxFiles) { if (fbxfile.Name.Equals(m_FbxFile.Name)) { fbxFileChoosed = fbxfile.FullName; break; } } string relativePath = NamingChecker.GetRelativePath(fbxFileChoosed); Transform fbxTrans = AssetDatabase.LoadAssetAtPath(relativePath, typeof(Transform)) as Transform; if (m_FbxSenceObject != null) // 不要重复生成,关闭的时候清不掉 { DestroyImmediate(m_FbxSenceObject.gameObject); } m_FbxSenceObject = Instantiate(fbxTrans).gameObject; return m_FbxSenceObject; } void CreatePrefab(ref GameObject fbxGameObject,int index) { string prefabName = ""; string realPrefabName = ""; if (m_IsOld) { prefabName = m_OldPrefab.name; } else { prefabName = fbxGameObject.name.Split('(')[0]; realPrefabName = GetPrefabName(prefabName); prefabName = DropPostfix(prefabName); } string pathname = ""; string instPrefabPath = ""; pathname = CombinePath(prefabName)+"/Prefabs/"; if (prefabName.Contains("&ing")||prefabName.Contains("low")) { instPrefabPath = m_PrefabDirIng + pathname; CreatePrefabFodler(instPrefabPath); m_PrefabPath = instPrefabPath + realPrefabName + ".prefab"; LOD_m_PrefabPath = LOD_m_PrefabPath + instPrefabPath + realPrefabName + ".prefab\n"; mPrefabPathList.Add(index, m_PrefabPath); } else if(prefabName.Contains("&lob")||prefabName.Contains("height")) { instPrefabPath = m_PrefabDirLob + pathname; CreatePrefabFodler(instPrefabPath); m_PrefabPath = instPrefabPath+ realPrefabName + ".prefab"; LOD_m_PrefabPath = LOD_m_PrefabPath + instPrefabPath + realPrefabName + ".prefab\n"; mPrefabPathList.Add(index,m_PrefabPath); } else { Debug.LogError(prefabName+"资源命名缺少前缀ing or lob!"); } // 【注意】不能使用绝对路径!https://blog.csdn.net/MAOMAOXIAOHUO/article/details/51204230 Material defaultMat = AssetDatabase.LoadAssetAtPath(m_DefaultMatPath, typeof(Material)) as Material; // Dictionary dict = new Dictionary(); // if (m_IsOld) // { // // 拿到当前项目里已经存在的prefab中的材质与mesh映射关系 // dict = GetMaterialRelationFromOldPrefab(); // } // fbxGameObject.name = fbxGameObject.name.Replace("mdl&", "").Replace("(Clone)", ""); // if (m_IsOld) // { // // 赋予新生成的go材质 // foreach (Transform temp in fbxGameObject.transform) // { // if (temp.GetComponent() != null) // { // string matPath = ""; // if (dict.TryGetValue(temp.name, out matPath)) // { // temp.GetComponent().sharedMaterial = AssetDatabase.LoadAssetAtPath(matPath, typeof(Material)) as Material; // } // } // } // } // else // { DMMCharacterRevisionWindow.LODLoadPresetToGameObject(ref fbxGameObject,currentAssetLodLevel,currentAssetSceneType,realPrefabName); // } // 这一段是试图挂blink eye脚本 foreach (Transform temp in fbxGameObject.transform) { foreach (string m_name in ModelName) { if (temp.gameObject.name.Contains(m_name) && temp.GetComponent() != null) { if (m_IsLobby && temp.gameObject.name.Contains(BlinkFaceTag) && temp.gameObject.GetComponent() == null) { string characterTag = prefabName.Split('_')[0] + "_" + prefabName.Split('_')[1]; string[] faceNames; bool hasBlinkface = m_BlinkFaceDict.TryGetValue(characterTag, out faceNames); if (hasBlinkface) { // 挂脚本 temp.GetComponent().sharedMaterial = defaultMat; BlinkController ctrl = temp.gameObject.AddComponent(); ctrl.m_OpenEyesMatName = faceNames[0]; ctrl.mCloseEyesMatName = faceNames[1]; } } break; } } } string DynaMicPath = AddDynamicBoneData(Path.GetFileNameWithoutExtension(mPrefabPathList[index])); PrefabUtility.SaveAsPrefabAssetAndConnect(fbxGameObject, mPrefabPathList[index], InteractionMode.AutomatedAction); // GameObject m_prefabRoot = PrefabUtility.GetCorrespondingObjectFromOriginalSource(fbxGameObject) as GameObject; AssetDatabase.SaveAssets(); DestroyImmediate(fbxGameObject.gameObject); // 自动生成动态骨骼 // 读取prefab Transform model = AssetDatabase.LoadAssetAtPath(mPrefabPathList[index], typeof(Transform)) as Transform; if (m_DynamicBoneSenceObject != null) // 不要重复生成,关闭的时候清不掉 { DestroyImmediate(m_DynamicBoneSenceObject.gameObject); } m_DynamicBoneSenceObject = Instantiate(model).gameObject; // 找出标记父节点并初始化一系列成员变量 ProcessPrefabToInit(ref m_DynamicBoneSenceObject); //如果存在配置文件,就按照标准保存 if (DynaMicPath.Length > 0) { OnClickImportDynamicBoneFileByName(DynaMicPath); } // 根据m_TagToDynamicBoneParamDict动态生成可调节Dynamic Bone参数 RePaintDynamicBoneParamsPanel(); // 在对应部位挂HeadBoneAttach AddHeadBoneAttach(); // 保存prefab SaveDynamicBonePrefab(); } /// /// 缓存导入,已有的dynamic配置参数 /// /// string AddDynamicBoneData(string DynamicBoneName) { string objectName = DynamicBoneName.Split('(')[0]; string pathName; string DynamicPath = ""; pathName = CombinePath(objectName) + "/Prefabs/"; string assetPath = ""; if (objectName.Contains("&ing") || objectName.Contains("low")) { assetPath = m_PrefabDirIng + pathName + objectName + ".prefab"; } else if (objectName.Contains("&lob") || objectName.Contains("height")) { assetPath = m_PrefabDirLob + pathName + objectName + ".prefab"; } Transform model = AssetDatabase.LoadAssetAtPath(assetPath, typeof(Transform)) as Transform; if (model != null) { GameObject DynamicObj = Instantiate(model).gameObject; ProcessPrefabToInit(ref DynamicObj); DynamicPath = OnClickExportDynamicBoneFileByName(DynamicObj.name); DestroyImmediate(DynamicObj); } return DynamicPath; } void RePaintDynamicBoneParamsPanel() { this.Repaint(); } Dictionary GetMaterialRelationFromOldPrefab() { Dictionary rst = new Dictionary(); foreach (Transform temp in m_OldPrefab.transform) { if (temp.GetComponent() != null) { Material mat = temp.GetComponent().sharedMaterial; rst.Add(temp.name, AssetDatabase.GetAssetPath(mat)); } } return rst; } // 去掉_SRN后缀 string DropPostfix(string name) { return name.Replace("mdl", "pfb"); // string postfix = NamingChecker.GetPostfix(name); // bool isValid = NamingChecker.IsValidPostfix(postfix); // if (isValid) // { // return name.Replace("_" + postfix, "").Replace("mdl", "pfb"); // } // else // { // return name.Replace("mdl", "pfb"); // } } /// /// 获取fbx对应的prefab名字 /// /// /// string GetPrefabName(string name) { string[] nameList = name.Split('&'); eAssetLodLevel level = eAssetLodLevel.None; string finalName = String.Empty; foreach (KeyValuePair keyValuePair in levelDic) { if (nameList[0].Contains(keyValuePair.Key)) { level = keyValuePair.Value; currentAssetLodLevel = level; break; } } foreach (KeyValuePair keyValuePair in sceneDic) { if (nameList[1].Contains(keyValuePair.Key)) { currentAssetSceneType = keyValuePair.Value; } } if (nameList.Length == 2 ) { string[] logicNamelist = nameList[1].Split('_'); if (logicNamelist.Length >= 4) { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Clear(); for (int i = 0; i < 3; i++) { stringBuilder.Append(logicNamelist[i] + "_"); } stringBuilder.Append(logicNamelist[3]); string prefabName = stringBuilder.ToString(); //识别补充后缀 if (!prefabName.Contains("_skin")) { prefabName = prefabName + "_skin"; } stringBuilder.Clear(); finalName = LMNAResMapUtil.AssembleAssetName(prefabName, eAssetFunctionType.GameObject, level); } } return finalName; } // 处理需要蒙皮的特效部位 void ProcessEffectPart(ref GameObject prefab) { foreach(Transform temp in prefab.transform) { if (!temp.name.Contains(NamingChecker.AttachmentPatterns[2])) continue; // 新建一个子go GameObject go = new GameObject(); go.transform.SetParent(temp); // 把自己挂着的skinmeshrenderer剪切到子go上 SkinnedMeshRenderer smr = temp.GetComponent(); UnityEditorInternal.ComponentUtility.CopyComponent(smr); UnityEditorInternal.ComponentUtility.PasteComponentAsNew(go); // 挂上PlayerEffectController PlayerEffectController pec = temp.gameObject.AddComponent(); pec.m_Effect = go; } } #endregion #region Clear void OnClickClearButton() { ClearFbxAndTextures(() => { NamingChecker.PopupErrorMessage("成功清除" + m_FolderPath + "下新导入的资源!"); }); ClearPrefabFolder(() => { NamingChecker.PopupErrorMessage("成功清除" + m_PrefabPath + "!"); }); AssetDatabase.Refresh(); this.Repaint(); } void Clear(System.Action onFbxSucc = null, System.Action onPrefabSucc = null) { ClearFbxAndTextures(onFbxSucc); ClearPrefabFolder(onPrefabSucc); m_OldPrefab = null; ; AssetDatabase.Refresh(); this.Repaint(); } void ClearFbxAndTextures(System.Action onSucc = null) { if (!Directory.Exists(m_FolderPath)) return; // 删除fbx同目录下所有文件 string tempMatDir = m_FolderPath; DirectoryInfo tempDirection = new DirectoryInfo(tempMatDir); FileInfo[] files = tempDirection.GetFiles("*.*", SearchOption.TopDirectoryOnly); File.Delete(m_FbxPath); m_FbxPath = ""; m_FolderPath = ""; if (onSucc != null) onSucc(); } void ClearPrefabFolder(System.Action onSucc = null) { if (!File.Exists(m_PrefabPath)) return; File.Delete(m_PrefabPath); m_PrefabPath = ""; if (onSucc != null) onSucc(); } #endregion #region DynamicBone void OnClickCreateLODDynamicBoneParams() { if (string.IsNullOrEmpty(m_DynamicBonePrefabPath)) { return; } // 读取prefab Transform model = AssetDatabase.LoadAssetAtPath(m_DynamicBonePrefabPath, typeof(Transform)) as Transform; if (m_DynamicBoneSenceObject != null) // 不要重复生成,关闭的时候清不掉 { DestroyImmediate(m_DynamicBoneSenceObject.gameObject); } m_DynamicBoneSenceObject = Instantiate(model).gameObject; // 找出标记父节点并初始化一系列成员变量 ProcessPrefabToInit(ref m_DynamicBoneSenceObject); // 根据m_TagToDynamicBoneParamDict动态生成可调节Dynamic Bone参数 RePaintLODDynamicBoneParamsPanel(); } void OnClickDynamicBoneButton() { // 在对应部位挂HeadBoneAttach AddHeadBoneAttach(); // 保存prefab SaveDynamicBonePrefab(); } void OnClickAddCollider(string key) { int idx = 0; LODDynamicColliderData newItem; List tempList = new List(); LODDynamicBoneParams tempDictData = m_TagToDynamicBoneParamDict[key]; // 用于给字典赋值 // 添加碰撞体 if (m_TagToDynamicBoneParamDict[key].colliderDataList != null) { tempList = m_TagToDynamicBoneParamDict[key].colliderDataList; idx = tempList.Count; // 目前有多少个collider了 } // 点击添加键时就需要创建并挂上transform string name = "Collider_" + idx; GameObject collider = new GameObject(); collider.name = name; collider.transform.SetParent(GetTransformByName(key).GetChild(0)); DynamicBoneCollider dc = collider.AddComponent(); newItem = new LODDynamicColliderData(name, 0.3f, null, collider.transform); dc.m_Radius = newItem.radius; tempList.Add(newItem); tempDictData = new LODDynamicBoneParams(tempDictData.root, tempDictData.Damping, tempDictData.Elasticty, tempDictData.Stiffness, tempDictData.Inert, tempDictData.Friction, tempDictData.Radius, tempList); m_TagToDynamicBoneParamDict[key] = tempDictData; RePaintLODDynamicBoneParamsPanel(); } void OnClickRemoveCollider(string key) { if (m_TagToDynamicBoneParamDict[key].colliderDataList == null) return; int idx = m_TagToDynamicBoneParamDict[key].colliderDataList.Count; List tempList = m_TagToDynamicBoneParamDict[key].colliderDataList; LODDynamicBoneParams tempDictData = m_TagToDynamicBoneParamDict[key]; // 用于给字典赋值 DestroyImmediate(tempList[idx - 1].colTrans.gameObject); tempList.Remove(tempList[idx - 1]); tempDictData = new LODDynamicBoneParams(tempDictData.root, tempDictData.Damping, tempDictData.Elasticty, tempDictData.Stiffness, tempDictData.Inert, tempDictData.Friction, tempDictData.Radius, tempList); m_TagToDynamicBoneParamDict[key] = tempDictData; RePaintLODDynamicBoneParamsPanel(); } // 导入Dynamicbone配置文件 void OnClickImportDynamicBoneFile() { TryReadSBFile(); } void OnClickImportDynamicBoneFileByName(string path) { TryReadSBFileByName(path); } // 导出Dynamicbone配置文件 void OnClickExportDynamicBoneFile() { WriteSBInfo(); EditorUtility.DisplayDialog("成功", "导出完毕", "确定"); AssetDatabase.Refresh(); } string OnClickExportDynamicBoneFileByName(string name) { string path = WriteSBInfoByName(name); // EditorUtility.DisplayDialog("成功", "导出完毕", "确定"); AssetDatabase.Refresh(); return path; } void SaveDynamicBonePrefab() { string senceObjectName = m_DynamicBoneSenceObject.name.Split('(')[0]; string pathname = ""; pathname = CombinePath(senceObjectName)+"/Prefabs/"; // string dirPath = Application.dataPath + m_PrefabDirLob + pathname.Replace("Assets/", "/"); // if (!Directory.Exists(dirPath)) // { // Directory.CreateDirectory(dirPath); // } if (senceObjectName.Contains("&ing")||senceObjectName.Contains("low")) PrefabUtility.SaveAsPrefabAsset(m_DynamicBoneSenceObject, m_PrefabDirIng +pathname+ senceObjectName + ".prefab"); else if(senceObjectName.Contains("&lob")||senceObjectName.Contains("height")) PrefabUtility.SaveAsPrefabAsset(m_DynamicBoneSenceObject, m_PrefabDirLob +pathname+ senceObjectName + ".prefab"); AssetDatabase.SaveAssets(); } void ProcessPrefabToInit(ref GameObject senceObject) { m_SkinPartTransformList.Clear(); m_TaggedTransformDict.Clear(); m_TagToDynamicBoneParamDict.Clear(); // 初始化m_SkinPartTransformList for (int i = 0; i < senceObject.transform.childCount; i++) { foreach (string part in ModelName) { if (senceObject.transform.GetChild(i).name.Contains(part) && !senceObject.transform.GetChild(i).name.Contains(part + "_ex") && !senceObject.transform.GetChild(i).name.Contains(part + "_fx")) { m_SkinPartTransformList.Add(senceObject.transform.GetChild(i)); break; } } } // 初始化m_TaggedTransformDict和m_TagToDynamicBoneParamDict foreach (Transform child in senceObject.GetComponentsInChildren()) { foreach (string tag in DynamicBoneTag) { if (child.name.Contains(tag)) { m_TaggedTransformDict.Add(child, child.parent.name); LODDynamicBoneParams param = GetLODDynamicBoneParams(child); m_TagToDynamicBoneParamDict.Add(child.name, param); break; } } } } void RePaintLODDynamicBoneParamsPanel() { this.Repaint(); } void AddHeadBoneAttach() { foreach (KeyValuePair part in m_TaggedTransformDict) { Transform targetTrans = null; if (part.Key.name.Contains("CLOTHES_")) { targetTrans = m_SkinPartTransformList.Find(x => x.name.Contains("clothes")); } else if (part.Key.name.Contains("HEAD_")) { targetTrans = m_SkinPartTransformList.Find(x => x.name.Contains("head")); } else if (part.Key.name.Contains("HAND_")) { targetTrans = m_SkinPartTransformList.Find(x => x.name.Contains("hand")); } else if (part.Key.name.Contains("SHOES_")) { targetTrans = m_SkinPartTransformList.Find(x => x.name.Contains("shoes")); } else if (part.Key.name.Contains("TROUSERS_")) { targetTrans = m_SkinPartTransformList.Find(x => x.name.Contains("trousers")); } if (targetTrans == null) { Debug.LogError("在绑DynamicBone时未找到" + part.Key.name + "对应的部位Transform!"); } else { bool isShouldAdd = true; // 检查一下是否已经挂了一模一样的attachment脚本 HeadBoneAttach[] comps = targetTrans.gameObject.GetComponents(); HeadBoneAttach comp; Transform attachment = part.Key.GetChild(0); for (int i = 0; i < comps.Length; i++) { if (comps[i].m_Attachment == attachment && comps[i].m_HeadBoneName.Equals(part.Value)) { isShouldAdd = false; break; } } if (isShouldAdd) { comp = targetTrans.gameObject.AddComponent(); comp.m_Attachment = attachment; comp.m_HeadBoneName = part.Value; } part.Key.SetParent(targetTrans); // 骨骼串的跟节点挂DynamicBone脚本 SetLODDynamicBoneParams(ref attachment, m_TagToDynamicBoneParamDict[part.Key.name]); // collider的attachment // TODO:我忘了。。DB需不需要和SB一样都要对collider的transform也挂上HeadBoneAttach脚本?先注释掉 /* LODDynamicBoneParams dbParams = m_TagToDynamicBoneParamDict[part.Key.name]; List collParams = dbParams.colliderDataList; HeadBoneAttach collComp; if (collParams != null) { for (int i = 0; i < collParams.Count; i++) { bool isShouldAddCollAttach = !string.IsNullOrEmpty(collParams[i].rootName); for (int j = 0; j < comps.Length; j++) { // 检查一下是否已经挂了一模一样的attachment脚本 if (comps[j].m_Attachment == null || string.IsNullOrEmpty(comps[j].m_HeadBoneName)) continue; if (comps[j].m_Attachment.name.Equals(collParams[i].name) && comps[j].m_HeadBoneName.Equals(collParams[i].rootName)) { isShouldAddCollAttach = false; break; } } if (isShouldAddCollAttach) { collComp = targetTrans.gameObject.AddComponent(); collComp.m_Attachment = collParams[i].colTrans; collComp.m_HeadBoneName = collParams[i].rootName; } } } */ } } } Transform GetTransformByName(string name) { foreach (Transform VARIABLE in m_DynamicBoneSenceObject.transform.GetComponentsInChildren()) { if (VARIABLE.name.Equals(name)) return VARIABLE; } return null; } LODDynamicBoneParams GetLODDynamicBoneParams(Transform trans) { LODDynamicBoneParams rst = new LODDynamicBoneParams(trans.GetChild(0), 0.02f, 0.08f, 0.06f, 0.8f, 0.6f, 0.02f, null); // TODO: 这个经验数值应该也用不了 if (trans.childCount == 0 || trans.GetChild(0).childCount == 0) return rst; DynamicBone root = trans.GetChild(0).gameObject.GetComponent(); if (root == null) { root = trans.GetChild(0).gameObject.AddComponent(); } rst.root = trans.GetChild(0); rst.Damping = root.m_Damping; rst.Elasticty = root.m_Elasticity; rst.Inert = root.m_Inert; rst.Friction = root.m_Friction; rst.Stiffness = root.m_Stiffness; rst.Radius = root.m_Radius; rst.colliderDataList = new List(); if(root.m_Colliders!=null) { for (int i = 0; i < root.m_Colliders.Count; i++) { DynamicBoneCollider dc = root.m_Colliders[i] as DynamicBoneCollider; string rootName = GetAttachmentRootName(dc.transform); LODDynamicColliderData collData = new LODDynamicColliderData(dc.name, dc.m_Radius, rootName, dc.transform); rst.colliderDataList.Add(collData); } } return rst; } void SetLODDynamicBoneParams(ref Transform trans, LODDynamicBoneParams param) { if (trans == null || param.root == null) return; DynamicBone db = trans.GetComponent(); if (db == null) { db = trans.gameObject.AddComponent(); } db.m_Root = trans; db.m_Damping = param.Damping; db.m_Elasticity = param.Elasticty; db.m_Stiffness = param.Stiffness; db.m_Inert = param.Inert; db.m_Friction = param.Friction; db.m_Radius = param.Radius; } string GetAttachmentRootName(Transform collider) { string name = ""; HeadBoneAttach[] partAttaches = collider.GetComponentsInParent(); if (partAttaches != null) { for (int i = 0; i < partAttaches.Length; i++) { if (partAttaches[i].m_Attachment == collider) { name = partAttaches[i].m_HeadBoneName; } } } return name; } Transform GetCollider(Transform parent, string name) { for (int i = 0; i < parent.childCount; i++) { if (parent.GetChild(i).name.Equals(name)) return parent.GetChild(i); } return null; } void ClearScenePrefabs() { if (m_FbxSenceObject != null && m_FbxSenceObject.gameObject != null) { GameObject.DestroyImmediate(m_FbxSenceObject.gameObject); } if (m_DynamicBoneSenceObject != null && m_DynamicBoneSenceObject.gameObject != null) { GameObject.DestroyImmediate(m_DynamicBoneSenceObject.gameObject); } } #endregion #region Export/ImportDynamicBoneInfo string GetDynamicBoneInfoFilePath() { if (m_DynamicBoneSenceObject != null && m_DynamicBoneSenceObject.gameObject != null) { string fileName = m_DynamicBoneSenceObject.transform.name.Replace("(Clone)", ""); // 就是prefab同名 return m_DynamicBoneParamDir + fileName + ".txt"; } else { EditorUtility.DisplayDialog("警告", "先生成Dynamicbone参数才能正确取到文件名字!", "好吧"); return ""; } } /// /// 通过名字,拿到对应的动态骨骼配置文件 /// /// /// string GetDynamicBoneInfoFilePathByName(string name) { string fileName = name.Replace("(Clone)", ""); // 就是prefab同名 return m_DynamicBoneParamDir + fileName + ".txt"; } /// /// 通过名字写入动态骨骼配置文件 /// /// string WriteSBInfoByName(string name) { string path = GetDynamicBoneInfoFilePathByName(name); StreamWriter sw; if (!File.Exists(path)) { File.Create(path).Dispose(); } else { // 清空 File.WriteAllText(path, string.Empty); } FileInfo fi = new FileInfo(path); sw = fi.AppendText(); // 写入m_TagToDynamicBoneParamDict WriteSBParamInfo(sw); sw.Close(); sw.Dispose(); AssetDatabase.Refresh(); return path; } void WriteSBInfo() { string path = GetDynamicBoneInfoFilePath(); StreamWriter sw; if (!File.Exists(path)) { File.Create(path).Dispose(); } else { // 清空 File.WriteAllText(path, string.Empty); } FileInfo fi = new FileInfo(path); sw = fi.AppendText(); // 写入m_TagToDynamicBoneParamDict WriteSBParamInfo(sw); sw.Close(); sw.Dispose(); } void TryReadSBFile() { string path = GetDynamicBoneInfoFilePath(); if (!File.Exists(path)) { EditorUtility.DisplayDialog("警告", "找不到对应的DynamicBone参数文件!", "好吧"); } if (EditorUtility.DisplayDialog("警告", "确定要覆盖DynamicBone参数么?", "确定")) { LoadSBFile(); } } /// /// 加载动态骨骼配置文件(通过名字) /// /// void TryReadSBFileByName(string path) { // string path = GetDynamicBoneInfoFilePathByName(name); // if (!File.Exists(path)) // { // EditorUtility.DisplayDialog("警告", "找不到对应的DynamicBone参数文件!", "好吧"); // } // if (EditorUtility.DisplayDialog("警告", "确定要覆盖DynamicBone参数么?", "确定")) // { // LoadSBFileByName(name); // } if (File.Exists(path)) { LoadSBFileByName(path); } } void LoadSBFile() { string path = GetDynamicBoneInfoFilePath(); StreamReader sr = new StreamReader(path, Encoding.Default); string readStr = ""; string fileInfoString = ""; // 整个文件的内容都存入 while (sr != null && (readStr = sr.ReadLine()) != null) { fileInfoString += readStr; } sr.Close(); SetLODDynamicBoneParams(fileInfoString); Repaint(); EditorUtility.DisplayDialog("成功", "导入完毕", "确定"); } void LoadSBFileByName(string path) { // string path = GetDynamicBoneInfoFilePathByName(name); StreamReader sr = new StreamReader(path, Encoding.Default); string readStr = ""; string fileInfoString = ""; // 整个文件的内容都存入 while (sr != null && (readStr = sr.ReadLine()) != null) { fileInfoString += readStr; } sr.Close(); SetLODDynamicBoneParams(fileInfoString); Repaint(); // EditorUtility.DisplayDialog("成功", "导入完毕", "确定"); } void SetLODDynamicBoneParams(string fileInfoString) { string[] tags = fileInfoString.Split('!'); string currTagKey; LODDynamicBoneParams currDBParam; foreach (string tagStr in tags) { currTagKey = ""; currDBParam = new LODDynamicBoneParams(null, 0.02f, 0.08f, 0.06f, 0.8f, 0.6f, 0.02f, null); if (string.IsNullOrEmpty(tagStr)) continue; string[] pairStrs = tagStr.Split('%'); foreach (string pairStr in pairStrs) { if (string.IsNullOrEmpty(pairStr)) continue; string[] pair = pairStr.Split(':'); string key = ""; string value = ""; if (pair.Length != 2) { Debug.LogError("数据对" + pairStr + "解析失败!"); } if (!string.IsNullOrEmpty(pair[0])) key = pair[0]; if (!string.IsNullOrEmpty(pair[1])) value = pair[1]; // 简陋的字符匹配 if (key.Contains("TagName")) { currTagKey = value; if (m_TagToDynamicBoneParamDict.ContainsKey(currTagKey)) { currDBParam = m_TagToDynamicBoneParamDict[currTagKey]; // 应该是最先被赋值的,保证后面参数修改的都是拿到了有值的currDBParam } } else if (key.Contains("DB_Root")) { currDBParam.root = GetRootFromPath(value); } else if (key.Contains("DB_Damping")) { currDBParam.Damping = float.Parse(value); } else if (key.Contains("DB_Elasticty")) { currDBParam.Elasticty = float.Parse(value); } else if (key.Contains("DB_Stiffness")) { currDBParam.Stiffness = float.Parse(value); } else if (key.Contains("DB_Inert")) { currDBParam.Inert = float.Parse(value); } else if (key.Contains("DB_Friction")) { currDBParam.Friction = float.Parse(value); } else if (key.Contains("DB_Radius")) { currDBParam.Radius = float.Parse(value); } else if (key.Contains("SBC_List")) { SetColliders(currTagKey, ref currDBParam.colliderDataList, value); } else { Debug.LogError("读取文件发现异常内容:" + pairStr); } } m_TagToDynamicBoneParamDict[currTagKey] = currDBParam; } } void SetColliders(string tagName, ref List colliderDataList, string colliderInfoStr) { string[] collInfoStrs = colliderInfoStr.Split('$'); foreach (string infoStrs in collInfoStrs) { if (string.IsNullOrEmpty(infoStrs)) continue; string[] infoStrArray = infoStrs.Split('@'); string name = infoStrArray[1]; float radius = float.Parse(infoStrArray[2]); string rootName = infoStrArray[3]; string[] posArray = infoStrArray[4].Split('#'); float x, y, z; if (posArray.Length != 3) { Debug.LogError("读取" + name + "collider坐标失败!"); x = y = z = 0f; } else { x = float.Parse(posArray[0]); y = float.Parse(posArray[1]); z = float.Parse(posArray[2]); } // 查找colliderDataList是否存在同名Collider,存在则覆盖,不存在则新建 bool isNeedAdd = true; LODDynamicColliderData scData; for (int i = 0; i < colliderDataList.Count; i++) { scData = colliderDataList[i]; if (scData.name.Equals(name)) { isNeedAdd = false; // 存在,覆盖 scData.radius = radius; scData.rootName = rootName; scData.colTrans.localPosition = new Vector3(x, y, z); break; } } // 新建并赋值 if (isNeedAdd) { GameObject collider = new GameObject(); collider.name = name; collider.transform.SetParent(GetTransformByName(tagName).GetChild(0)); collider.transform.localPosition = new Vector3(x, y, z); DynamicBoneCollider dc = collider.AddComponent(); dc.m_Radius = radius; scData = new LODDynamicColliderData(name, radius, rootName, collider.transform); colliderDataList.Add(scData); } } } void WriteSBParamInfo(StreamWriter sw) { Dictionary TagInfo = new Dictionary(); foreach (KeyValuePair pair in m_TagToDynamicBoneParamDict) { LODDynamicBoneParams param = pair.Value; TagInfo = new Dictionary { {"TagName:", pair.Key}, {"DB_Root:", CreateDynamicBoneRoot(pair.Key, param.root==null?"":param.root.name)}, {"DB_Damping:", param.Damping.ToString()}, {"DB_Elasticty:", param.Elasticty.ToString()}, {"DB_Stiffness:", param.Stiffness.ToString()}, {"DB_Inert:", param.Inert.ToString()}, {"DB_Friction:", param.Friction.ToString()}, {"DB_Radius:", param.Radius.ToString()}, {"SBC_List:", GetColliderListString(pair.Key, param.colliderDataList)} }; sw.WriteLine('!'); foreach (KeyValuePair tag in TagInfo) { sw.WriteLine('%' + tag.Key + tag.Value); } } } string GetColliderListString(string tagName, List colliderDataList) { string rst = ""; string singleCollDataStr = ""; if (colliderDataList == null) return ""; for (int i = 0; i < colliderDataList.Count; i++) { singleCollDataStr = ""; // 普通的参数普通地获取 singleCollDataStr += "@" + colliderDataList[i].name; singleCollDataStr += "@" + colliderDataList[i].radius; singleCollDataStr += "@" + colliderDataList[i].rootName; // 厉害的参数遍历获取(?) foreach (Transform trans in m_DynamicBoneSenceObject.transform.GetComponentsInChildren()) { if (!trans.name.Equals(tagName)) continue; // 找到标记位下面同名的collider,拿到position foreach (DynamicBoneCollider collider in trans.GetComponentsInChildren()) { if (!collider.transform.name.Equals(colliderDataList[i].name)) continue; Vector3 pos = collider.transform.localPosition; singleCollDataStr += "@" + pos.x + "#" + pos.y + "#" + pos.z; // @-0.21#0#0 } } rst += singleCollDataStr + "$"; // collider单条信息以$结尾 } return rst; } string CreateDynamicBoneRoot(string parentName, string childName) { return parentName + "/" + childName; } Transform GetRootFromPath(string path) { DynamicBone[] transList = m_DynamicBoneSenceObject.transform.GetComponentsInChildren(); foreach(DynamicBone trans in transList) { if(path.Contains(trans.name) &&path.Contains(trans.transform.parent.name)) { return trans.transform; } } return null; } #endregion } } ```