using System.Collections.Generic; using System.Data; using UnityEngine; using UnityEditor; using System.IO; using System.Linq; using System.Text.RegularExpressions; using cfg; using SimpleJSON; using UnityEditor.Animations; using Path = System.IO.Path; using RootMotion.FinalIK; public class ArtActorPrefabModifyWindow : EditorWindow { public static string assetSrcFolderPath = "Assets/AssetsPackage/art/fishsetting/mesh"; public static string assetDstFolderPath = "Assets/ABPackage/pkgDownloads/Fish"; //"Assets/AssetsPackage/Prefabs/Fishing/Fish"; public static string assetRodDstFolderPath = "Assets/ABPackage/pkgDownloads/Rod"; public static string fishMaterialFolderPath = "Assets/AssetsPackage/art/fishsetting/materials/"; public static string reelMaterialFolderPath = "Assets/AssetsPackage/art/fishingrod/reel/materials/"; public static string rodMaterialFolderPath = "Assets/AssetsPackage/art/fishingrod/rod/materials/"; public static string baseFishOverrideControllerPath = "Assets/AssetsPackage/Animation/Fishing/Fish/"; public static string baseRodOverrideControllerPath = "Assets/AssetsPackage/Animation/Fishing/Rod/"; public static UnityEditor.Animations.AnimatorController animatorController; public static string animatorOverrideControllerPath = "Assets/AssetsPackage/art/fishsetting/animations/"; public static UnityEditor.Animations.AnimatorController animatorRodController; public static string animatorRodControllerPath = "Assets/AssetsPackage/art/fishingrod/rod/animations/"; private Object jsonFile; private string folderPath = "Assets/ABPackage/pkgDownloads/Fish"; private int startLine; private int endLine; static string crossbowPrefabPath = "Assets/ABPackage/pkgDownloads/Aquarium/AquariumCommon/Crossbow/"; public static string baseCrossbowOverrideControllerPath = "Assets/AssetsPackage/art/fishingcrossbow/animations/"; static UnityEditor.Animations.AnimatorController animatorCrossbowController; public static string animatorCrossbowControllerPath = "Assets/AssetsPackage/art/fishingcrossbow/animations/"; [MenuItem("Window/自动化构建工具", false, 1)] public static void ShowWindow() { EditorWindow thisWindow = EditorWindow.GetWindow(typeof(ArtActorPrefabModifyWindow)); thisWindow.titleContent = new GUIContent("创建Prefab"); thisWindow.position = new Rect(Screen.width / 2, Screen.height / 2, 600, 800); } private void OnEnable() { // 设置默认的json文件 string defaultJsonPath = "Assets/ABPackage/pkgData/Export/tbfishdata.json"; jsonFile = AssetDatabase.LoadAssetAtPath(defaultJsonPath, typeof(TextAsset)); } void OnGUI() { //分一个组 EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("批量创建鱼的Prefab", EditorStyles.boldLabel); EditorGUILayout.Space(10); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("选择源文件夹"); assetSrcFolderPath = EditorGUILayout.TextField(assetSrcFolderPath); if (GUILayout.Button("选择")) { assetSrcFolderPath = EditorUtility.OpenFolderPanel("选择文件夹", assetSrcFolderPath, ""); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.LabelField("选择保存文件夹"); assetDstFolderPath = EditorGUILayout.TextField(assetDstFolderPath); if (GUILayout.Button("选择")) { assetDstFolderPath = EditorUtility.OpenFolderPanel("选择文件夹", assetDstFolderPath, ""); } EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); EditorGUILayout.EndHorizontal(); EditorGUILayout.BeginHorizontal(); animatorController = (AnimatorController)EditorGUILayout.ObjectField("animatorController", animatorController, typeof(AnimatorController), false); EditorGUILayout.EndHorizontal(); if (GUILayout.Button("开始创建") && assetSrcFolderPath != null && assetDstFolderPath != null) { Seperate(GetAbsolutePath(assetSrcFolderPath)); } EditorGUILayout.Space(10); //分组结束 EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical("box"); EditorGUILayout.EndVertical(); EditorGUILayout.BeginVertical("box"); EditorGUILayout.LabelField("CheckFishFilesInFolder", EditorStyles.boldLabel); // 创建可以拖拽Excel表的字段 jsonFile = EditorGUILayout.ObjectField("Json File tbfishdata", jsonFile, typeof(TextAsset), false); // 获取拖进去的文件夹路径 if (Event.current.type == EventType.DragUpdated || Event.current.type == EventType.DragPerform) { DragAndDrop.visualMode = DragAndDropVisualMode.Copy; if (Event.current.type == EventType.DragPerform) { DragAndDrop.AcceptDrag(); foreach (string path in DragAndDrop.paths) { if (Directory.Exists(path)) { folderPath = path; break; } } } } // 显示文件夹路径 EditorGUILayout.LabelField("Folder Path: " + folderPath); // 输入起始行和结束行 startLine = EditorGUILayout.IntField("Start Line", startLine); endLine = EditorGUILayout.IntField("End Line", endLine); // 检查文件夹中是否存在指定文件 if (GUILayout.Button("Check Files")) { CheckFiles(); } //分组结束 EditorGUILayout.EndVertical(); } void CheckFiles() { // 读取json文件 string jsonString = (jsonFile as TextAsset).ToString(); var jsonData = new TbFishData(JSON.Parse(jsonString)); // 获取指定区间的数据 List fileNames = new List(); if (startLine < 1) { startLine = 1; } if (endLine > jsonData.DataList.Count) { endLine = jsonData.DataList.Count; } for (int i = startLine - 1; i < endLine; i++) { fileNames.Add(jsonData.DataList[i].Fbx); } // 检查文件夹中是否存在指定文件 List missingFiles = new List(); foreach (string fileName in fileNames) { string filePath = System.IO.Path.Combine(folderPath, fileName + ".prefab"); if (!File.Exists(filePath)) { missingFiles.Add(fileName); } } // 打印缺失文件名 if (missingFiles.Count > 0) { Debug.Log("The following files are missing:"); foreach (string missingFile in missingFiles) { Debug.Log(missingFile); } } else { Debug.Log("All files are present."); } } private static void Seperate(string folderPath) { //*.prefab或*.Fbx var folders = Directory.GetDirectories(folderPath, "F_*", SearchOption.TopDirectoryOnly); foreach (var folder in folders) { var files = Directory.GetFiles(folder, "*.*") .Where(file => file.EndsWith(".prefab") || file.EndsWith(".fbx") || file.EndsWith(".FBX")).ToArray(); string srcPath = GetRelativePath(files[0]); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; string fileName = Path.GetFileNameWithoutExtension(srcPath); string directory = Path.GetDirectoryName(srcPath); string newPath = Path.Combine(directory, fileName); AssetsFish(prefab, newPath); } } public static string GetRelativePath(string path) { string srcPath = path.Replace("\\", "/"); var retPath = Regex.Replace(srcPath, @"^(.*?/)Assets/", "Assets/"); return retPath; } public static string GetAbsolutePath(string path) { string srcPath = path.Replace("\\", "/"); //替换第一个Assets之前的路径 var retPath = Regex.Replace(srcPath, @"^(.*?/)Assets", Application.dataPath); return retPath; } #region 鱼相关工具 [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab查找节点")] static void MenuAssetsFinFishNodes() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; Fish fish = prefab.GetComponent(); if (fish == null) { Debug.LogError($"{prefab.name}不是可钓的鱼"); continue; } FindFishNode(fish, prefab.transform); LogNullNode(prefab.name, fish); EditorUtility.SetDirty(prefab); } AssetDatabase.SaveAssets(); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab查找节点", true)] private static bool MenuAssetsFinFishNodesValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查偏移")] static void MenuAssetsCheckFishPos() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; Fish fish = prefab.GetComponent(); if (fish == null) { Debug.LogError($"{prefab.name}不是可钓的鱼"); continue; } //FindFishNode(fish, prefab.transform); //LogNullNode(prefab.name, fish); //EditorUtility.SetDirty(prefab); if (fish.animator != null) { if (fish.animator.transform.localPosition != Vector3.zero) { Debug.LogError($"{prefab.name}的animator有偏移{fish.animator.transform.localPosition}"); } } else { Debug.LogError($"{prefab.name}缺少animator节点"); } } AssetDatabase.SaveAssets(); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查偏移", true)] private static bool MenuAssetsCheckFishPosValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab设置动画")] static void MenuAssetsFinFishAni() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; Fish fish = prefab.GetComponent(); if (fish == null) { Debug.LogError($"{prefab.name}不是可钓的鱼"); continue; } srcPath = $"{GetRelativePath(assetSrcFolderPath)}/{prefab.name}/{prefab.name}"; SetFishAni(fish, srcPath); } AssetDatabase.SaveAssets(); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab设置动画", true)] private static bool MenuAssetsFinFishAniValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查节点")] static void MenuAssetsCheckFishNodes() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; Fish fish = prefab.GetComponent(); if (fish == null) { Debug.LogError($"{prefab.name}不是可钓的鱼"); continue; } LogNullNode(prefab.name, fish); } } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查节点", true)] private static bool MenuAssetsCheckFishNodesValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查Layer")] static void MenuAssetsCheckFishLayer() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; LogRendererLayer(prefab); } } [MenuItem("Assets/FISH_Tools/遍历文件夹下的鱼prefab检查Layer", true)] private static bool MenuAssetsCheckFishLayerValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/FISH_Tools/文件夹下创建鱼Prefab", true)] private static bool MenuAssetsFishFValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return Directory.Exists(path); } [MenuItem("Assets/FISH_Tools/文件夹下创建鱼Prefab")] static void MenuAssetsFishF() { if (Selection.activeObject == null) { Debug.Log("Assets choose null"); } else { string path = AssetDatabase.GetAssetPath(Selection.activeObject); Seperate(path); } } [MenuItem("Assets/FISH_Tools/创建鱼Prefab", true)] private static bool MenuAssetsFishValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return path.EndsWith(".fbx") || path.EndsWith(".FBX") || path.EndsWith(".prefab"); } [MenuItem("Assets/FISH_Tools/创建鱼Prefab")] static void MenuAssetsFish() { if (Selection.activeObject == null) { Debug.Log("Assets choose null"); } else { Debug.Log("Assets name = " + Selection.activeObject.name); GameObject prefab = Selection.activeObject as GameObject; string path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string newPath = Path.Combine(directory, fileName); AssetsFish(prefab, newPath); } } static void AssetsFish(GameObject prefab, string srcPath) { string dstPath = GetRelativePath(assetDstFolderPath); GameObject dstObj = new GameObject(prefab.name, typeof(Fish), typeof(BoxCollider)); GameObject srcObj = Instantiate(prefab, dstObj.transform, false); srcObj.transform.localPosition = Vector3.zero; srcObj.transform.localEulerAngles = Vector3.up * 180; srcObj.transform.localScale = Vector3.one; srcObj.name = prefab.name; ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); PrefabUtility.ConvertToPrefabInstance(srcObj, prefab, settings, InteractionMode.AutomatedAction); Fish fish = dstObj.GetComponent(); Renderer skinnedMeshRenderer = srcObj.GetComponentInChildren(); //Add Material var fishMaterial = AssetDatabase.LoadAssetAtPath($"{fishMaterialFolderPath}/{prefab.name}.mat", typeof(Material)) as Material; if (skinnedMeshRenderer != null && fishMaterial != null) { skinnedMeshRenderer.material = fishMaterial; } else if (skinnedMeshRenderer == null) { Debug.LogError($"{prefab.name} :没有 Renderer"); } else { Debug.LogError($"{prefab.name} :没有名为 {prefab.name} 的 fishMaterial"); } FindFishNode(fish, srcObj.transform); fish.animator = srcObj.AddComponent(); SetFishAni(fish, srcPath); //dstObj下的layer都变成fish SetLayerRecursively(dstObj, LayerMask.NameToLayer("fish")); //设置脏数据 LogNullNode(srcObj.name, fish); EditorUtility.SetDirty(dstObj); PrefabUtility.SaveAsPrefabAsset(dstObj, dstPath + "/" + prefab.name + ".prefab"); Debug.Log($"创建{prefab.name}成功"); DestroyImmediate(dstObj); } static void SetFishAni(Fish fish, string srcPath) { //创建新的 Animator Override Controller并设置到fish.animator上 AnimatorOverrideController animatorOverrideController = new AnimatorOverrideController(); if (animatorController == null) { animatorController = AssetDatabase.LoadAssetAtPath(baseFishOverrideControllerPath + "fishGeneralAnimator.controller"); } animatorOverrideController.runtimeAnimatorController = animatorController; if (!string.IsNullOrEmpty(srcPath)) { srcPath = srcPath.Replace(@"\", "/"); AnimationClip clip; List> overrides = new List>(); animatorOverrideController.GetOverrides(overrides); string clipName; bool isHasClip = false; bool isHasDash01 = false; bool isHasShow01 = false; foreach (var pair in overrides) { clipName = pair.Key.name; string clipPath = $"{srcPath}@{clipName}.fbx"; clip = AssetDatabase.LoadAssetAtPath(clipPath); if (clip) { if (clipName.Equals("Dash01")) { isHasDash01 = true; } if (clipName.Equals("Show01")) { isHasShow01 = true; } isHasClip = true; if (clipName.Equals("Idle01") || clipName.Equals("Swim01") || clipName.Equals("Show01")) { AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip); clipSettings.loopTime = true; AnimationUtility.SetAnimationClipSettings(clip, clipSettings); //修改FBX的Animation的ModelImporter的循环属性 ModelImporter importer = AssetImporter.GetAtPath(clipPath) as ModelImporter; ModelImporterClipAnimation modelImporterClipAnimation = importer.defaultClipAnimations[0]; modelImporterClipAnimation.loopTime = true; importer.clipAnimations = new[] { modelImporterClipAnimation }; AssetDatabase.ImportAsset(clipPath); } animatorOverrideController[clipName] = clip; } } if (!isHasClip) { return; } if (!isHasDash01) { animatorOverrideController["Dash01"] = animatorOverrideController["Swim01"]; } if (!isHasShow01) { animatorOverrideController["Show01"] = animatorOverrideController["Swim01"]; } //保存animatorOverrideController到指定文件夹 AssetDatabase.CreateAsset(animatorOverrideController, animatorOverrideControllerPath + fish.name + ".overrideController"); AssetDatabase.SaveAssets(); fish.animator.runtimeAnimatorController = animatorOverrideController; } } static void FindFishNode(Fish fish, Transform root) { if (root.name.Equals("Mouth")) { fish.mouthPos = root; } else if (root.name.Equals("Head")) { fish.Head = root; } else if (root.name.Equals("Left_Eye")) { fish.LeftEye = root; } else if (root.name.Equals("Right_Eye")) { fish.RightEye = root; } else if (root.name.Equals("Fin")) { fish.Fin = root; } else if (root.name.Equals("Tail")) { fish.Tail = root; } else if (root.name.Equals("Neck")) { fish.Neck = root; } else if (root.name.Equals("Root")) { fish.Root = root; } foreach (Transform child in root) { FindFishNode(fish, child); } } static void LogNullNode(string objName, Fish fish) { string nullNode = $"{objName}: 缺少"; bool isLog = false; if (fish.mouthPos == null) { nullNode += "-|-Mouth"; isLog = true; } if (fish.Head == null) { nullNode += "-|-Head"; isLog = true; } if (fish.LeftEye == null) { nullNode += "-|-Left_Eye"; isLog = true; } if (fish.RightEye == null) { nullNode += "-|-Right_Eye"; isLog = true; } if (fish.Fin == null) { nullNode += "-|-Fin"; isLog = true; } if (fish.Tail == null) { nullNode += "-|-Tail"; isLog = true; } if (fish.Neck == null) { nullNode += "-|-Neck"; isLog = true; } if (isLog) { Debug.LogError(nullNode); } } static void LogRendererLayer(GameObject obj) { Renderer skinnedMeshRenderer = obj.GetComponentInChildren(); if (skinnedMeshRenderer == null) { Debug.LogError($"{obj.name}: 缺少SkinnedMeshRenderer"); } else { //skinnedMeshRenderer.renderingLayerMask 是包含 //fish = 1 << 0 //rod = 1 << 1 = 2 if ((skinnedMeshRenderer.renderingLayerMask & 1) == 0) { Debug.LogError($"{obj.name}: skinnedMeshRenderer.renderingLayerMask 不包含 fish"); } if (skinnedMeshRenderer.gameObject.layer != LayerMask.NameToLayer("fish")) { Debug.LogError($"{obj.name}: skinnedMeshRenderer.Layer 不是 fish"); } } } static void SetLayerRecursively(GameObject obj, int layer) { obj.layer = layer; foreach (Transform child in obj.transform) { SetLayerRecursively(child.gameObject, layer); } } [MenuItem("Assets/FISH_Tools/Fish_改名")] static void GenerateFish() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); string[] filePaths = Directory.GetFiles(path, "*.prefab", SearchOption.AllDirectories); foreach (string filePath in filePaths) { string fileName = System.IO.Path.GetFileNameWithoutExtension(filePath); if (!fileName.StartsWith("Fish_")) { string[] nameParts = Regex.Split(fileName, @"\s+"); nameParts = nameParts.Where(part => !part.Equals("Variant")).ToArray(); nameParts = nameParts.Select(part => char.ToUpper(part[0]) + part.Substring(1)).ToArray(); string fishName = "Fish_" + string.Join("", nameParts); // string newFilePath = Path.Combine(path, fishName + Path.GetExtension(filePath)); AssetDatabase.RenameAsset(filePath, fishName); // AssetDatabase.ImportAsset(newFilePath); Debug.Log(fishName); } } } [MenuItem("Assets/FISH_Tools/Fish_改名", true)] static bool GenerateFishValidation() { //选中的是文件夹 return AssetDatabase.IsValidFolder(AssetDatabase.GetAssetPath(Selection.activeObject)); } #endregion #region 鱼杆相关工具 [MenuItem("Assets/Rod_Tools/遍历文件夹下的杆prefab查找动画")] static void MenuAssetsFinRodAnimator() { var path = AssetDatabase.GetAssetPath(Selection.activeObject); var files = Directory.GetFiles(path, "*.prefab"); foreach (var file in files) { string srcPath = GetRelativePath(file); GameObject prefab = AssetDatabase.LoadAssetAtPath(srcPath, typeof(GameObject)) as GameObject; if (prefab == null) continue; string prefabName = prefab.name; var animator = prefab.transform.Find(prefabName).GetComponent(); List> overrides = new List>(); var animatorOverrideController = animator.runtimeAnimatorController as AnimatorOverrideController; animatorOverrideController.GetOverrides(overrides); string clipName; AnimationClip clip; foreach (var pair in overrides) { clipName = pair.Key.name; string clipPath = $"Assets/AssetsPackage/art/fishingrod/rod/mesh/{prefabName}/{prefabName}@{clipName}.fbx"; ModelImporter modelImporter = AssetImporter.GetAtPath(clipPath) as ModelImporter; if (modelImporter != null) { clip = AssetDatabase.LoadAssetAtPath(clipPath); if (clipName.Equals("Idle01") || clipName.Contains("Drawing") || clipName.Contains("Reeling") || clipName.Contains("Waiting01") || clipName.Contains("Waiting02") || clipName.Contains("MaxCombo01") || clipName.Contains("Show01") && !clipName.Equals("Drawing00")) { AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip); clipSettings.loopTime = true; AnimationUtility.SetAnimationClipSettings(clip, clipSettings); //修改FBX的Animation的ModelImporter的循环属性 ModelImporterClipAnimation modelImporterClipAnimation = modelImporter.defaultClipAnimations[0]; modelImporterClipAnimation.loopTime = true; modelImporter.clipAnimations = new[] { modelImporterClipAnimation }; AssetDatabase.ImportAsset(clipPath); } animatorOverrideController[clipName] = clip; } } } AssetDatabase.SaveAssets(); } [MenuItem("Assets/Rod_Tools/遍历文件夹下的杆prefab查找动画", true)] private static bool MenuAssetsFinRodAnimatorValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); return AssetDatabase.IsValidFolder(path); } [MenuItem("Assets/Rod_Tools/创建鱼竿Prefab")] static void MenuAssetsRod() { if (Selection.activeObject == null) { Debug.Log("Assets choose null"); } else { Debug.Log("Assets name = " + Selection.activeObject.name); GameObject prefab = Selection.activeObject as GameObject; string path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string newPath = Path.Combine(directory, fileName); if (fileName.StartsWith("Rod_")) { AssetsRod(prefab, newPath); } } } [MenuItem("Assets/Rod_Tools/创建鱼竿Prefab", true)] private static bool MenuAssetssRodValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); return fileName.StartsWith("Rod_") && (path.EndsWith(".FBX") || path.EndsWith(".fbx") || path.EndsWith(".prefab")); } static void AssetsRod(GameObject prefab, string srcPath = "") { string dstPath = GetRelativePath(assetRodDstFolderPath); GameObject dstObj = new GameObject(prefab.name, typeof(Rod), typeof(CCDIK)); GameObject srcObj = Instantiate(prefab, dstObj.transform, false); ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); PrefabUtility.ConvertToPrefabInstance(srcObj, prefab, settings, InteractionMode.AutomatedAction); Rod rod = dstObj.GetComponent(); rod.cCDIK = dstObj.GetComponent(); rod.rod = srcObj; rod.LineList = new List(); FindRodNode(rod, srcObj.transform); //rod.LineList.Reverse(); rod.LineList = rod.LineList.OrderByDescending(t => t.name).ToList(); //========================================Line===========================================// var FishingLinePrefab = AssetDatabase.LoadAssetAtPath("Assets/AssetsPackage/Prefabs/Fishing/Component/FishingLine.prefab"); var fishingLine = Instantiate(FishingLinePrefab, dstObj.transform, false); PrefabUtility.ConvertToPrefabInstance(fishingLine, FishingLinePrefab, settings, InteractionMode.AutomatedAction); rod.fishingLineRenderer = fishingLine.GetComponent(); //========================================================================================// SetRodAndReelMaterial(srcObj.transform); var animator = srcObj.AddComponent(); //创建新的 Animator Override Controller if (animatorRodController == null) { animatorRodController = AssetDatabase.LoadAssetAtPath(baseRodOverrideControllerPath + "RodAnimatorController.controller"); } AnimatorOverrideController animatorOverrideController = new AnimatorOverrideController(); animatorOverrideController.runtimeAnimatorController = animatorRodController; if (!string.IsNullOrEmpty(srcPath)) { string clipName; srcPath = srcPath.Replace(@"\", "/"); AnimationClip clip; List> overrides = new List>(); animatorOverrideController.GetOverrides(overrides); foreach (var pair in overrides) { clipName = pair.Key.name; string clipPath = $"{srcPath}@{clipName}.fbx"; ModelImporter modelImporter = AssetImporter.GetAtPath(clipPath) as ModelImporter; if (modelImporter != null) { modelImporter.resampleCurves = false; modelImporter.animationCompression = ModelImporterAnimationCompression.Optimal; clip = AssetDatabase.LoadAssetAtPath(clipPath); if (clipName.Equals("Idle01") || clipName.Contains("Drawing") || clipName.Contains("Reeling") || clipName.Contains("Waiting01") || clipName.Contains("Waiting02") || clipName.Contains("MaxCombo01") || clipName.Contains("Show01") && !clipName.Equals("Drawing00")) { AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip); clipSettings.loopTime = true; AnimationUtility.SetAnimationClipSettings(clip, clipSettings); //修改FBX的Animation的ModelImporter的循环属性 ModelImporter importer = AssetImporter.GetAtPath(clipPath) as ModelImporter; ModelImporterClipAnimation modelImporterClipAnimation = importer.defaultClipAnimations[0]; modelImporterClipAnimation.loopTime = true; importer.clipAnimations = new[] { modelImporterClipAnimation }; AssetDatabase.ImportAsset(clipPath); } animatorOverrideController[clipName] = clip; } } } //保存animatorOverrideController到指定文件夹 AssetDatabase.CreateAsset(animatorOverrideController, animatorRodControllerPath + prefab.name + ".overrideController"); animator.runtimeAnimatorController = animatorOverrideController; AssetDatabase.SaveAssets(); //设置脏数据 EditorUtility.SetDirty(dstObj); PrefabUtility.SaveAsPrefabAsset(dstObj, dstPath + "/" + prefab.name + ".prefab"); Debug.Log($"创建{prefab.name}成功"); DestroyImmediate(dstObj); } static void FindRodNode(Rod rod, Transform root) { if (root.name.Equals("Rod_Joint")) { rod.Rod_Joint = root; } else if (root.name.Equals("Bait")) { rod.Bait = root; } else if (root.name.Equals("TopLine")) { rod.TopLine = root; //TopLine; } else if (root.name.Equals("EndLine")) { rod.EndLine = root; //EndLine; } else if (Regex.IsMatch(root.name, @"^Line\d+$")) { rod.LineList.Add(root); } else if (root.name.Equals("LineHole")) { rod.LineHole = root; } else if (root.name.Equals("Bip001 L Hand")) { //========================================HandleLeft===========================================// // var HandleLeft = AssetDatabase.LoadAssetAtPath("Assets/ABPackage/pkgDownloads/Hand/HandleLeft.prefab"); // var handleLeft = Instantiate(HandleLeft, root.transform, false); // ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); // PrefabUtility.ConvertToPrefabInstance(handleLeft, HandleLeft, settings, InteractionMode.AutomatedAction); rod.LeftHandTargets = root; //========================================================================================// } else if (root.name.Equals("Bip001 R Hand")) { // //========================================HandleRight===========================================// // var HandleLeft = AssetDatabase.LoadAssetAtPath("Assets/ABPackage/pkgDownloads/Hand/HandleRight.prefab"); // var handleLeft = Instantiate(HandleLeft, root.transform, false); // ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); // PrefabUtility.ConvertToPrefabInstance(handleLeft, HandleLeft, settings, InteractionMode.AutomatedAction); rod.RightHandTargets = root; //========================================================================================// } else if (root.name.Equals("Bip001 Spine")) { rod.BodyTargets = root; //========================================================================================// } foreach (Transform child in root) { FindRodNode(rod, child); } } static Transform FindChildTransform(Transform root, string childName, bool isRoot) { if (root.name.Replace(" ", "").Equals(childName) && !isRoot) { return root; } for (int i = 0; i < root.childCount; i++) { var result = FindChildTransform(root.GetChild(i), childName, false); if (result != null) return result; } return null; } static Transform FindChildTransformStartWithName(Transform root, string pattern, bool isRoot) { if (root.name.StartsWith(pattern) && !isRoot) return root; for (int i = 0; i < root.childCount; i++) { var result = FindChildTransformStartWithName(root.GetChild(i), pattern, false); if (result != null) return result; } return null; } static void SetRodAndReelMaterial(Transform root) { // 检查root是否为空 if (root == null) { Debug.LogError("SetRodAndReelMaterial: root parameter is null!"); return; } // 检查root名称格式是否正确 if (!root.name.Contains("_")) { Debug.LogError($"SetRodAndReelMaterial: root name '{root.name}' does not contain '_' character!"); return; } var name = root.name.Split('_')[1]; var rodName = $"Rod_{name}"; var reelName = $"Reel_{name}"; var reellineName = $"Reelline_{name}"; var fishingLineName = "Fishing_line"; var rawRod = FindChildTransform(root, rodName, true); var rawReel = FindChildTransform(root, reelName, true); var rawReelline = FindChildTransform(root, reellineName, true); var rawLine = FindChildTransformStartWithName(root, fishingLineName, true); // 分别检查每个节点是否找到,并单独报错 if (rawRod == null) { Debug.LogError($"SetRodAndReelMaterial: Cannot find rod node '{rodName}' in root '{root.name}'"); } if (rawReel == null) { Debug.LogError($"SetRodAndReelMaterial: Cannot find reel node '{reelName}' in root '{root.name}'"); } if (rawReelline == null) { Debug.LogError($"SetRodAndReelMaterial: Cannot find reelline node '{reellineName}' in root '{root.name}'"); } if (rawLine == null) { Debug.LogWarning($"SetRodAndReelMaterial: Cannot find fishing line node '{fishingLineName}' in root '{root.name}' (this is optional)"); } // 如果关键节点缺失,直接返回 if (rawRod == null || rawReel == null || rawReelline == null) { Debug.LogError($"SetRodAndReelMaterial: Critical nodes missing in root '{root.name}'. Please check the prefab structure."); return; } var rodRender = rawRod.GetComponent(); var reelRender = rawReel.GetComponent(); var reellineRender = rawReelline.GetComponent(); // 检查Renderer组件 if (rodRender == null) { Debug.LogError($"SetRodAndReelMaterial: SkinnedMeshRenderer component not found on rod node '{rodName}'"); return; } if (reelRender == null) { Debug.LogError($"SetRodAndReelMaterial: SkinnedMeshRenderer component not found on reel node '{reelName}'"); return; } if (reellineRender == null) { Debug.LogError($"SetRodAndReelMaterial: SkinnedMeshRenderer component not found on reelline node '{reellineName}'"); return; } //Add Material var RodMaterial = AssetDatabase.LoadAssetAtPath($"{rodMaterialFolderPath}/{rodName}.mat", typeof(Material)) as Material; var ReelMaterial = AssetDatabase.LoadAssetAtPath($"{reelMaterialFolderPath}/{reelName}.mat", typeof(Material)) as Material; var ReellineMaterial = AssetDatabase.LoadAssetAtPath($"{reelMaterialFolderPath}/{reellineName}.mat", typeof(Material)) as Material; // 检查材质是否加载成功 if (RodMaterial == null) { Debug.LogError($"SetRodAndReelMaterial: Failed to load rod material from path '{rodMaterialFolderPath}/{rodName}.mat'"); } if (ReelMaterial == null) { Debug.LogError($"SetRodAndReelMaterial: Failed to load reel material from path '{reelMaterialFolderPath}/{reelName}.mat'"); } if (ReellineMaterial == null) { Debug.LogError($"SetRodAndReelMaterial: Failed to load reelline material from path '{reelMaterialFolderPath}/{reellineName}.mat'"); } Debug.Log($"SetRodAndReelMaterial: Loaded materials - Rod: {RodMaterial}, Reel: {ReelMaterial}, Reelline: {ReellineMaterial}"); // 应用材质 rodRender.material = RodMaterial; reelRender.material = ReelMaterial; reellineRender.material = ReellineMaterial; //Change MeshRender //layer int layerIndex = LayerMask.NameToLayer("rod"); rodRender.renderingLayerMask = (uint)(1 << 1); reelRender.renderingLayerMask = (uint)(1 << 1); reellineRender.renderingLayerMask = (uint)(1 << 1); if (rawLine != null) { var fishLineRender = rawLine.GetComponent(); if (fishLineRender != null) { fishLineRender.renderingLayerMask = (uint)(1 << 1); fishLineRender.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; rawLine.gameObject.layer = layerIndex; } else { Debug.LogWarning($"SetRodAndReelMaterial: SkinnedMeshRenderer component not found on fishing line node '{fishingLineName}'"); } } //Cast Shadows rodRender.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; reelRender.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; reellineRender.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; //Change Layer rawReel.gameObject.layer = layerIndex; rawRod.gameObject.layer = layerIndex; rawReelline.gameObject.layer = layerIndex; Debug.Log($"SetRodAndReelMaterial: Successfully processed rod, reel, and reelline for '{root.name}'"); } #endregion #region Crossbow相关工具 [MenuItem("Assets/Crossbow_Tools/创建弩Prefab")] private static void MenuAssetsCrossbow() { if (Selection.activeObject == null) { Debug.Log("Assets choose null"); } else { Debug.Log("Assets name = " + Selection.activeObject.name); GameObject prefab = Selection.activeObject as GameObject; string path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string newPath = Path.Combine(directory, fileName); if (fileName.Contains("prop_crossbow_")) { AssetsCrossbow(prefab, newPath); } } } [MenuItem("Assets/Crossbow_Tools/创建弩Prefab", true)] private static bool MenuAssetsCrossbowValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); return fileName.Contains("prop_crossbow_") && (path.EndsWith(".FBX") || path.EndsWith(".fbx")); } static void FindCrossbowNode(Crossbow crossbow, Transform root) { if (root.name.Equals("Root")) { crossbow.root = root.gameObject; } else if (root.name.Equals("crosshair_P")) { crossbow.crosshair_P = root; } else if (root.name.Equals("TopLine")) { crossbow.TopLine = root; //TopLine; } else if (root.name.Equals("EndLine")) { crossbow.EndLine = root; //EndLine; } else if (Regex.IsMatch(root.name, @"^Line\d+$")) { crossbow.LineList.Add(root); } else if (root.name.Equals("Bip001 L Hand")) { crossbow.LeftHandTargets = root; } else if (root.name.Equals("Bip001 R Hand")) { crossbow.RightHandTargets = root; } //LeftHandTargets foreach (Transform child in root) { FindCrossbowNode(crossbow, child); } } static void AssetsCrossbow(GameObject prefab, string srcPath = "") { string dstPath = GetRelativePath(crossbowPrefabPath); GameObject dstObj = new GameObject(prefab.name, typeof(Crossbow)); GameObject srcObj = Instantiate(prefab, dstObj.transform, false); ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); PrefabUtility.ConvertToPrefabInstance(srcObj, prefab, settings, InteractionMode.AutomatedAction); Crossbow crossbow = dstObj.GetComponent(); crossbow.animator = srcObj.AddComponent(); crossbow.LineList = new List(); FindCrossbowNode(crossbow, srcObj.transform); crossbow.LineList.Reverse(); //========================================Line===========================================// var FishingLinePrefab = AssetDatabase.LoadAssetAtPath("Assets/AssetsPackage/Prefabs/Fishing/Component/FishingLine.prefab"); var fishingLine = Instantiate(FishingLinePrefab, dstObj.transform, false); PrefabUtility.ConvertToPrefabInstance(fishingLine, FishingLinePrefab, settings, InteractionMode.AutomatedAction); crossbow.fishingLineRenderer = fishingLine.GetComponent(); //========================================================================================// //========================================C_Crosshair===========================================// var C_CrosshairPrefab = AssetDatabase.LoadAssetAtPath(crossbowPrefabPath + "C_Crosshair.prefab"); var crosshair = Instantiate(C_CrosshairPrefab, crossbow.crosshair_P, false); PrefabUtility.ConvertToPrefabInstance(crosshair, C_CrosshairPrefab, settings, InteractionMode.AutomatedAction); crossbow.icon_nml = crossbow.crosshair_P.Find("C_Crosshair/icon_nml").gameObject; crossbow.icon_notarget = crossbow.crosshair_P.Find("C_Crosshair/icon_notarget").gameObject; //========================================================================================// //创建新的 Animator Override Controller SetCrossbowAni(crossbow, srcPath); //dstObj下的layer都变成rod SetLayerRecursively(dstObj, LayerMask.NameToLayer("rod")); //设置脏数据 EditorUtility.SetDirty(dstObj); PrefabUtility.SaveAsPrefabAsset(dstObj, dstPath + "/" + prefab.name + ".prefab"); Debug.Log($"创建{prefab.name}成功"); DestroyImmediate(dstObj); } static void SetCrossbowAni(Crossbow crossbow, string srcPath) { //创建新的 Animator Override Controller并设置到fish.animator上 SetCrossbow(crossbow.animator, crossbow.name, srcPath, "CrossbowAnimatorController.controller"); } [MenuItem("Assets/Crossbow_Tools/创建弩箭Prefab")] private static void MenuAssetsCrossbowArrow() { if (Selection.activeObject == null) { Debug.Log("Assets choose null"); } else { Debug.Log("Assets name = " + Selection.activeObject.name); GameObject prefab = Selection.activeObject as GameObject; string path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); string directory = Path.GetDirectoryName(path); string newPath = Path.Combine(directory, fileName); if (fileName.Contains("prop_crossbowarrow_")) { AssetsCrossbowArrow(prefab, newPath); } } } [MenuItem("Assets/Crossbow_Tools/创建弩箭Prefab", true)] private static bool MenuAssetsCrossbowArrowValidation() { // 设置什么情况下消失/显现(the menu item will be disabled otherwise). var path = AssetDatabase.GetAssetPath(Selection.activeObject); string fileName = Path.GetFileNameWithoutExtension(path); return fileName.Contains("prop_crossbowarrow_") && (path.EndsWith(".FBX") || path.EndsWith(".fbx")); } static void FindCrossbowArrowNode(CrossbowArrow crossbowArrow, Transform root) { if (root.name.Equals("TopLine")) { crossbowArrow.TopLine = root; //TopLine; } else if (root.name.Equals("EndLine")) { crossbowArrow.EndLine = root; //EndLine; } else if (Regex.IsMatch(root.name, @"^Line\d+$")) { crossbowArrow.LineList.Add(root); } foreach (Transform child in root) { FindCrossbowArrowNode(crossbowArrow, child); } } static void AssetsCrossbowArrow(GameObject prefab, string srcPath = "") { string dstPath = GetRelativePath(crossbowPrefabPath); GameObject dstObj = new GameObject(prefab.name, typeof(CrossbowArrow)); GameObject srcObj = Instantiate(prefab, dstObj.transform, false); ConvertToPrefabInstanceSettings settings = new ConvertToPrefabInstanceSettings(); PrefabUtility.ConvertToPrefabInstance(srcObj, prefab, settings, InteractionMode.AutomatedAction); CrossbowArrow crossbowArrow = dstObj.GetComponent(); crossbowArrow.animator = srcObj.AddComponent(); crossbowArrow.crossbow = srcObj; crossbowArrow.LineList = new List(); FindCrossbowArrowNode(crossbowArrow, srcObj.transform); crossbowArrow.LineList.Reverse(); //创建新的 Animator Override Controller SetCrossbowArrowAni(crossbowArrow, srcPath); //dstObj下的layer都变成rod SetLayerRecursively(dstObj, LayerMask.NameToLayer("rod")); //设置脏数据 EditorUtility.SetDirty(dstObj); PrefabUtility.SaveAsPrefabAsset(dstObj, dstPath + "/" + prefab.name + ".prefab"); Debug.Log($"创建{prefab.name}成功"); DestroyImmediate(dstObj); } static void SetCrossbowArrowAni(CrossbowArrow crossbowArrow, string srcPath) { //创建新的 Animator Override Controller并设置到fish.animator上 SetCrossbow(crossbowArrow.animator, crossbowArrow.name, srcPath, "CrossbowArrowAnimatorController.controller"); } static void SetCrossbow(Animator animator, string nameStr, string srcPath, string animatorController) { //创建新的 Animator Override Controller并设置到fish.animator上 AnimatorOverrideController animatorOverrideController = new AnimatorOverrideController(); if (animatorCrossbowController == null) { animatorCrossbowController = AssetDatabase.LoadAssetAtPath(baseCrossbowOverrideControllerPath + animatorController); } animatorOverrideController.runtimeAnimatorController = animatorCrossbowController; if (!string.IsNullOrEmpty(srcPath)) { srcPath = srcPath.Replace(@"\", "/"); AnimationClip clip; List> overrides = new List>(); animatorOverrideController.GetOverrides(overrides); string clipName; bool isHasClip = false; foreach (var pair in overrides) { clipName = pair.Key.name; string clipPath = $"{srcPath}@{clipName}.fbx"; clip = AssetDatabase.LoadAssetAtPath(clipPath); if (clip) { isHasClip = true; ModelImporter importer = AssetImporter.GetAtPath(clipPath) as ModelImporter; importer.animationCompression = ModelImporterAnimationCompression.Optimal; importer.resampleCurves = false; if (clipName.Equals("Idle01")) { AnimationClipSettings clipSettings = AnimationUtility.GetAnimationClipSettings(clip); clipSettings.loopTime = true; AnimationUtility.SetAnimationClipSettings(clip, clipSettings); //修改FBX的Animation的ModelImporter的循环属性 ModelImporterClipAnimation modelImporterClipAnimation = importer.defaultClipAnimations[0]; modelImporterClipAnimation.loopTime = true; importer.clipAnimations = new[] { modelImporterClipAnimation }; } AssetDatabase.ImportAsset(clipPath); animatorOverrideController[clipName] = clip; } } if (!isHasClip) { return; } //保存animatorOverrideController到指定文件夹 AssetDatabase.CreateAsset(animatorOverrideController, animatorCrossbowControllerPath + nameStr + ".overrideController"); AssetDatabase.SaveAssets(); animator.runtimeAnimatorController = animatorOverrideController; } } #endregion }