先修复一下,错误的场景 删除不必要的钓场资产 修复into场景 update:更新meta文件,修复报错 update:修复资源 修复第一章节建造 修复建造第三章 update:删除多余内容 update:更新README.md update:还原图标 update:更新README update:更新配置
786 lines
28 KiB
C#
786 lines
28 KiB
C#
using UnityEngine;
|
||
using UnityEditor;
|
||
using UnityEditor.Animations;
|
||
using System.IO;
|
||
using System.Collections.Generic;
|
||
using System.Reflection;
|
||
using System.Linq;
|
||
using System.Text;
|
||
|
||
public class ImprovedFishFbxReplacer : EditorWindow
|
||
{
|
||
private string sourceFolderPath = @"D:\鱼素材";
|
||
private string unityTargetFolder = "Assets/AssetsPackage/art/fishsetting/mesh";
|
||
private string animatorControllerFolder = "Assets/AssetsPackage/art/fishsetting/animations";
|
||
|
||
private string[] prefabSearchPaths = new string[]
|
||
{
|
||
"Assets/ABPackage/pkgDownloads/Fish",
|
||
"Assets/ABPackage/pkgFirstPackage/Fish"
|
||
};
|
||
|
||
// 需要设置Loop的动画名称
|
||
private string[] loopAnimations = new string[] { "Show01", "Swim01", "Idle01" };
|
||
|
||
// 字段名称映射规则
|
||
private Dictionary<string, string[]> fieldNameMappings = new Dictionary<string, string[]>
|
||
{
|
||
{"Head", new string[] {"Head", "Controller_Head", "head", "HEAD"}},
|
||
{"LeftEye", new string[] {"LeftEye", "Left_Eye", "L_Eye", "Eye_L", "left_eye"}},
|
||
{"RightEye", new string[] {"RightEye", "Right_Eye", "R_Eye", "Eye_R", "right_eye"}},
|
||
{"Fin", new string[] {"Fin", "Controller_fin", "fin", "FIN"}},
|
||
{"Tail", new string[] {"Tail", "tail", "TAIL"}},
|
||
{"Neck", new string[] {"Neck", "neck", "NECK"}},
|
||
{"MouthPos", new string[] {"MouthPos", "Mouth_Pos", "mouth", "Mouth"}},
|
||
{"mouthPos", new string[] {"mouthPos", "MouthPos", "Mouth_Pos", "mouth", "Mouth"}},
|
||
{"Root", new string[] {"Root", "root", "ROOT"}}
|
||
};
|
||
|
||
[MenuItem("Tools/FishTools/FishReplace")]
|
||
public static void ShowWindow()
|
||
{
|
||
GetWindow<ImprovedFishFbxReplacer>("改进版鱼FBX替换工具");
|
||
}
|
||
|
||
private void OnGUI()
|
||
{
|
||
EditorGUILayout.LabelField("🐟 改进版鱼 FBX 替换 + 挂点修复工具", EditorStyles.boldLabel);
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.LabelField("配置路径", EditorStyles.boldLabel);
|
||
sourceFolderPath = EditorGUILayout.TextField("外部模型路径:", sourceFolderPath);
|
||
unityTargetFolder = EditorGUILayout.TextField("Unity 模型路径:", unityTargetFolder);
|
||
animatorControllerFolder = EditorGUILayout.TextField("Animator Controller路径:", animatorControllerFolder);
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.LabelField("操作选项", EditorStyles.boldLabel);
|
||
|
||
if (GUILayout.Button("仅修复挂点(不替换模型)", GUILayout.Height(30)))
|
||
{
|
||
FixAllFishPrefabs();
|
||
}
|
||
|
||
if (GUILayout.Button("替换模型并修复挂点", GUILayout.Height(30)))
|
||
{
|
||
ReplaceFishModels();
|
||
}
|
||
|
||
if (GUILayout.Button("自动挂载动画,手动设置loop", GUILayout.Height(30)))
|
||
{
|
||
AutoMountAnimationsAndSetLoop();
|
||
}
|
||
|
||
EditorGUILayout.Space();
|
||
EditorGUILayout.HelpBox("新功能:\n• 自动检测FBX中的动画\n• 挂载到Animator Controller\n• 自动设置Loop属性\n• 只处理外部路径下的鱼", MessageType.Info);
|
||
}
|
||
|
||
private void AutoMountAnimationsAndSetLoop()
|
||
{
|
||
if (!Directory.Exists(sourceFolderPath))
|
||
{
|
||
Debug.LogError("❌ 外部路径不存在: " + sourceFolderPath);
|
||
return;
|
||
}
|
||
|
||
string[] sourceSubFolders = Directory.GetDirectories(sourceFolderPath);
|
||
Dictionary<string, AnimationMountResult> mountResults = new Dictionary<string, AnimationMountResult>();
|
||
|
||
foreach (string fishFolder in sourceSubFolders)
|
||
{
|
||
string folderName = Path.GetFileName(fishFolder);
|
||
Debug.Log($"🎬 开始处理动画: {folderName}");
|
||
|
||
AnimationMountResult result = ProcessFishAnimations(fishFolder, folderName);
|
||
if (result.TotalAnimations > 0)
|
||
{
|
||
mountResults[folderName] = result;
|
||
}
|
||
}
|
||
|
||
AssetDatabase.Refresh();
|
||
GenerateAnimationReport(mountResults, "AnimationMountReport.txt");
|
||
Debug.Log("🎉 动画挂载完成!");
|
||
}
|
||
|
||
private AnimationMountResult ProcessFishAnimations(string fishFolder, string fishName)
|
||
{
|
||
AnimationMountResult result = new AnimationMountResult();
|
||
|
||
// 1. 找到FBX文件
|
||
string[] fbxFiles = Directory.GetFiles(fishFolder, "*.fbx");
|
||
if (fbxFiles.Length == 0)
|
||
{
|
||
Debug.LogWarning($"⚠️ 未找到FBX文件: {fishName}");
|
||
return result;
|
||
}
|
||
|
||
// 2. 找到对应的Animator Controller
|
||
string controllerPath = GetAnimatorControllerPath(fishName);
|
||
if (!File.Exists(controllerPath))
|
||
{
|
||
Debug.LogWarning($"⚠️ 未找到Animator Controller: {controllerPath}");
|
||
return result;
|
||
}
|
||
|
||
// 3. 处理每个FBX文件
|
||
foreach (string fbxPath in fbxFiles)
|
||
{
|
||
string unityFbxPath = GetUnityFbxPath(fbxPath, fishName);
|
||
if (!string.IsNullOrEmpty(unityFbxPath))
|
||
{
|
||
ProcessSingleFbxAnimations(unityFbxPath, controllerPath, fishName, result);
|
||
}
|
||
}
|
||
|
||
return result;
|
||
}
|
||
|
||
private void ProcessSingleFbxAnimations(string fbxPath, string controllerPath, string fishName, AnimationMountResult result)
|
||
{
|
||
// 1. 获取FBX中的所有动画
|
||
List<AnimationClip> fbxAnimations = GetAnimationsFromFbx(fbxPath);
|
||
result.TotalAnimations += fbxAnimations.Count;
|
||
|
||
if (fbxAnimations.Count == 0)
|
||
{
|
||
Debug.Log($"📭 {fishName}: FBX中没有找到动画");
|
||
return;
|
||
}
|
||
|
||
Debug.Log($"🎬 {fishName}: 找到 {fbxAnimations.Count} 个动画");
|
||
|
||
// 2. 加载Animator Override Controller
|
||
AnimatorOverrideController overrideController = AssetDatabase.LoadAssetAtPath<AnimatorOverrideController>(controllerPath);
|
||
if (overrideController == null)
|
||
{
|
||
Debug.LogError($"❌ 无法加载Animator Override Controller: {controllerPath}");
|
||
return;
|
||
}
|
||
|
||
// 3. 获取当前的Override设置
|
||
var currentOverrides = new List<KeyValuePair<AnimationClip, AnimationClip>>();
|
||
overrideController.GetOverrides(currentOverrides);
|
||
|
||
// 4. 创建新的Override映射
|
||
var newOverrides = new Dictionary<AnimationClip, AnimationClip>();
|
||
foreach (var pair in currentOverrides)
|
||
{
|
||
newOverrides[pair.Key] = pair.Value; // 保持现有的映射
|
||
}
|
||
|
||
// 5. 挂载缺失的动画
|
||
// 5. 挂载缺失的动画
|
||
foreach (var fbxClip in fbxAnimations)
|
||
{
|
||
bool mounted = TryMountAnimation(fbxClip, newOverrides, result);
|
||
if (mounted)
|
||
{
|
||
Debug.Log($"✅ 挂载动画: {fbxClip.name}");
|
||
}
|
||
|
||
// 6. 记录需要设置Loop的动画,但不立即设置
|
||
if (loopAnimations.Contains(fbxClip.name))
|
||
{
|
||
result.LoopSetAnimations.Add(fbxClip.name);
|
||
Debug.Log($"📝 标记需要设置Loop: {fbxClip.name}");
|
||
}
|
||
}
|
||
|
||
// 7. 先应用Override设置
|
||
if (result.MountedAnimations.Count > 0)
|
||
{
|
||
overrideController.ApplyOverrides(newOverrides.ToList());
|
||
EditorUtility.SetDirty(overrideController);
|
||
AssetDatabase.SaveAssets();
|
||
Debug.Log($"💾 保存Animator Controller: {fishName}");
|
||
}
|
||
|
||
// 8. 最后统一设置Loop(在所有挂载完成后)
|
||
if (result.LoopSetAnimations.Count > 0)
|
||
{
|
||
SetFbxAnimationsLoop(fbxPath, result.LoopSetAnimations);
|
||
}
|
||
|
||
|
||
|
||
}
|
||
|
||
private bool TryMountAnimation(AnimationClip fbxClip, Dictionary<AnimationClip, AnimationClip> overrides, AnimationMountResult result)
|
||
{
|
||
foreach (var kvp in overrides.ToList())
|
||
{
|
||
// 查找名称匹配的槽位(无论是否为空)
|
||
if (kvp.Key.name == fbxClip.name)
|
||
{
|
||
if (kvp.Value == null)
|
||
{
|
||
Debug.Log($"🎯 挂载到空槽位: {fbxClip.name}");
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"🔄 替换现有动画: {kvp.Value.name} -> {fbxClip.name}");
|
||
}
|
||
|
||
overrides[kvp.Key] = fbxClip;
|
||
result.MountedAnimations.Add(fbxClip.name);
|
||
return true;
|
||
}
|
||
}
|
||
|
||
Debug.LogWarning($"⚠️ 未找到匹配的槽位: {fbxClip.name}");
|
||
return false;
|
||
}
|
||
|
||
|
||
private List<AnimationClip> GetAnimationsFromFbx(string fbxPath)
|
||
{
|
||
List<AnimationClip> animations = new List<AnimationClip>();
|
||
|
||
Object[] assets = AssetDatabase.LoadAllAssetsAtPath(fbxPath);
|
||
foreach (Object asset in assets)
|
||
{
|
||
if (asset is AnimationClip clip && !clip.name.Contains("__preview__"))
|
||
{
|
||
animations.Add(clip);
|
||
}
|
||
}
|
||
|
||
return animations;
|
||
}
|
||
|
||
private void SetAnimationClipLoop(AnimationClip clip, bool loop)
|
||
{
|
||
try
|
||
{
|
||
string assetPath = AssetDatabase.GetAssetPath(clip);
|
||
ModelImporter importer = AssetImporter.GetAtPath(assetPath) as ModelImporter;
|
||
|
||
if (importer != null)
|
||
{
|
||
Debug.Log($"🔍 处理FBX: {assetPath}");
|
||
|
||
// 获取现有的动画设置
|
||
ModelImporterClipAnimation[] clipAnimations = importer.clipAnimations;
|
||
Debug.Log($"🔍 当前clipAnimations数量: {clipAnimations?.Length ?? 0}");
|
||
|
||
// 如果没有clipAnimations,需要先创建
|
||
if (clipAnimations == null || clipAnimations.Length == 0)
|
||
{
|
||
Debug.Log("🔧 创建新的clipAnimations数组");
|
||
clipAnimations = new ModelImporterClipAnimation[1];
|
||
clipAnimations[0] = new ModelImporterClipAnimation();
|
||
clipAnimations[0].name = clip.name;
|
||
clipAnimations[0].takeName = clip.name;
|
||
clipAnimations[0].loopTime = loop;
|
||
}
|
||
else
|
||
{
|
||
// 查找对应的动画设置
|
||
bool found = false;
|
||
for (int i = 0; i < clipAnimations.Length; i++)
|
||
{
|
||
Debug.Log($"🔍 检查动画: {clipAnimations[i].name} vs {clip.name}");
|
||
if (clipAnimations[i].name == clip.name)
|
||
{
|
||
clipAnimations[i].loopTime = loop;
|
||
found = true;
|
||
Debug.Log($"✅ 找到并设置 {clip.name} Loop: {loop}");
|
||
break;
|
||
}
|
||
}
|
||
|
||
if (!found)
|
||
{
|
||
Debug.LogWarning($"⚠️ 未找到匹配的动画设置: {clip.name}");
|
||
}
|
||
}
|
||
|
||
// 应用设置
|
||
importer.clipAnimations = clipAnimations;
|
||
importer.SaveAndReimport();
|
||
|
||
Debug.Log($"💾 重新导入完成: {assetPath}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"❌ 无法获取ModelImporter: {AssetDatabase.GetAssetPath(clip)}");
|
||
}
|
||
}
|
||
catch (System.Exception e)
|
||
{
|
||
Debug.LogError($"❌ 设置Loop失败 {clip.name}: {e.Message}");
|
||
}
|
||
}
|
||
|
||
|
||
|
||
private string GetAnimatorControllerPath(string fishName)
|
||
{
|
||
// 如果fishName已经以F_开头,就不再添加F_前缀
|
||
string controllerName = fishName.StartsWith("F_") ? $"{fishName}.controller" : $"F_{fishName}.controller";
|
||
return Path.Combine(animatorControllerFolder, $"{fishName}.overrideController").Replace("\\", "/");
|
||
}
|
||
|
||
|
||
private string GetUnityFbxPath(string externalFbxPath, string fishName)
|
||
{
|
||
string fileName = Path.GetFileName(externalFbxPath);
|
||
string unityPath = Path.Combine(unityTargetFolder, fishName, fileName).Replace("\\", "/");
|
||
|
||
if (File.Exists(unityPath))
|
||
{
|
||
return unityPath;
|
||
}
|
||
|
||
Debug.LogWarning($"⚠️ Unity中未找到FBX文件: {unityPath}");
|
||
return null;
|
||
}
|
||
|
||
private void GenerateAnimationReport(Dictionary<string, AnimationMountResult> results, string fileName)
|
||
{
|
||
if (results.Count > 0)
|
||
{
|
||
string logPath = Application.dataPath + "/../" + fileName;
|
||
using (StreamWriter writer = new StreamWriter(logPath))
|
||
{
|
||
writer.WriteLine($"鱼动画挂载报告 - {System.DateTime.Now}");
|
||
writer.WriteLine("=" + new string('=', 50));
|
||
writer.WriteLine();
|
||
|
||
foreach (var kv in results)
|
||
{
|
||
writer.WriteLine($"【{kv.Key}】");
|
||
writer.WriteLine($"总动画数量: {kv.Value.TotalAnimations}");
|
||
writer.WriteLine($"挂载动画数量: {kv.Value.MountedAnimations.Count}");
|
||
if (kv.Value.MountedAnimations.Count > 0)
|
||
{
|
||
writer.WriteLine($"挂载的动画: {string.Join(", ", kv.Value.MountedAnimations)}");
|
||
}
|
||
if (kv.Value.LoopSetAnimations.Count > 0)
|
||
{
|
||
writer.WriteLine($"设置Loop的动画: {string.Join(", ", kv.Value.LoopSetAnimations)}");
|
||
}
|
||
writer.WriteLine();
|
||
}
|
||
|
||
writer.WriteLine($"总计处理了 {results.Count} 条鱼");
|
||
}
|
||
Debug.Log($"📄 动画挂载报告已生成: {logPath}");
|
||
}
|
||
}
|
||
|
||
// 原有的其他方法保持不变...
|
||
private void FixAllFishPrefabs()
|
||
{
|
||
List<string> allPrefabs = new List<string>();
|
||
|
||
foreach (string searchPath in prefabSearchPaths)
|
||
{
|
||
if (AssetDatabase.IsValidFolder(searchPath))
|
||
{
|
||
string[] prefabGuids = AssetDatabase.FindAssets("t:Prefab", new string[] { searchPath });
|
||
foreach (string guid in prefabGuids)
|
||
{
|
||
string path = AssetDatabase.GUIDToAssetPath(guid);
|
||
allPrefabs.Add(path);
|
||
}
|
||
}
|
||
}
|
||
|
||
Debug.Log($"🔍 找到 {allPrefabs.Count} 个Prefab,开始修复挂点...");
|
||
|
||
Dictionary<string, List<string>> fixResults = new Dictionary<string, List<string>>();
|
||
|
||
foreach (string prefabPath in allPrefabs)
|
||
{
|
||
string prefabName = Path.GetFileNameWithoutExtension(prefabPath);
|
||
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
||
|
||
if (prefabAsset != null)
|
||
{
|
||
GameObject instance = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject;
|
||
|
||
if (instance != null)
|
||
{
|
||
Fish fishComp = instance.GetComponent<Fish>();
|
||
if (fishComp != null)
|
||
{
|
||
List<string> fixedFields = FixFishComponentImproved(instance.transform, fishComp);
|
||
|
||
if (fixedFields.Count > 0)
|
||
{
|
||
fixResults[prefabName] = fixedFields;
|
||
Debug.Log($"🔧 修复挂点:{prefabName} -> {string.Join(", ", fixedFields)}");
|
||
|
||
// 强制标记为脏数据
|
||
EditorUtility.SetDirty(fishComp);
|
||
EditorUtility.SetDirty(instance);
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"✅ 所有挂点完整:{prefabName}");
|
||
}
|
||
|
||
|
||
PrefabUtility.SaveAsPrefabAsset(instance, prefabPath);
|
||
|
||
}
|
||
|
||
GameObject.DestroyImmediate(instance);
|
||
}
|
||
}
|
||
}
|
||
|
||
GenerateFixReport(fixResults, "FishPrefabFixReport.txt");
|
||
AssetDatabase.Refresh();
|
||
Debug.Log("🎉 批量修复完成!");
|
||
}
|
||
|
||
private void ReplaceFishModels()
|
||
{
|
||
if (!Directory.Exists(sourceFolderPath))
|
||
{
|
||
Debug.LogError("❌ 外部路径不存在: " + sourceFolderPath);
|
||
return;
|
||
}
|
||
|
||
string[] sourceSubFolders = Directory.GetDirectories(sourceFolderPath);
|
||
List<string> replacedFish = new List<string>();
|
||
List<string> notFoundFish = new List<string>();
|
||
Dictionary<string, List<string>> prefabMissingLog = new Dictionary<string, List<string>>();
|
||
|
||
foreach (string fishFolder in sourceSubFolders)
|
||
{
|
||
string folderName = Path.GetFileName(fishFolder);
|
||
string unityFolderPath = Path.Combine(unityTargetFolder, folderName).Replace("\\", "/");
|
||
|
||
if (!AssetDatabase.IsValidFolder(unityFolderPath))
|
||
{
|
||
Debug.LogWarning($"⚠️ 未找到 Unity 中对应目录: {unityFolderPath}");
|
||
notFoundFish.Add(folderName);
|
||
continue;
|
||
}
|
||
|
||
Debug.Log($"✅ 正在处理: {folderName}");
|
||
replacedFish.Add(folderName);
|
||
|
||
// 删除原模型资源
|
||
string fullUnityPath = Path.Combine(Application.dataPath, "../", unityFolderPath);
|
||
if (Directory.Exists(fullUnityPath))
|
||
{
|
||
DirectoryInfo dirInfo = new DirectoryInfo(fullUnityPath);
|
||
foreach (FileInfo file in dirInfo.GetFiles()) file.Delete();
|
||
foreach (DirectoryInfo subDir in dirInfo.GetDirectories()) subDir.Delete(true);
|
||
}
|
||
|
||
// 拷贝 .fbx 文件
|
||
string[] fbxFiles = Directory.GetFiles(fishFolder, "*.fbx");
|
||
foreach (string fbxPath in fbxFiles)
|
||
{
|
||
string fileName = Path.GetFileName(fbxPath);
|
||
string destPath = Path.Combine(fullUnityPath, fileName);
|
||
File.Copy(fbxPath, destPath, true);
|
||
Debug.Log($"📁 复制 {fileName} 到 {unityFolderPath}");
|
||
}
|
||
|
||
// 修复挂点
|
||
string prefabPath = FindPrefabByName(folderName);
|
||
if (!string.IsNullOrEmpty(prefabPath))
|
||
{
|
||
GameObject prefabAsset = AssetDatabase.LoadAssetAtPath<GameObject>(prefabPath);
|
||
GameObject instance = PrefabUtility.InstantiatePrefab(prefabAsset) as GameObject;
|
||
|
||
if (instance != null)
|
||
{
|
||
Fish fishComp = instance.GetComponent<Fish>();
|
||
if (fishComp != null)
|
||
{
|
||
List<string> fixedFields = FixFishComponentImproved(instance.transform, fishComp);
|
||
|
||
if (fixedFields.Count > 0)
|
||
{
|
||
if (prefabMissingLog == null)
|
||
prefabMissingLog = new Dictionary<string, List<string>>();
|
||
prefabMissingLog[folderName] = fixedFields;
|
||
|
||
Debug.Log($"🔧 修复挂点:{folderName} -> {string.Join(", ", fixedFields)}");
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"✅ 所有挂点完整:{folderName}");
|
||
}
|
||
|
||
PrefabUtility.SaveAsPrefabAsset(instance, prefabPath);
|
||
GameObject.DestroyImmediate(instance);
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"❗Prefab {folderName} 没有挂 Fish 脚本!");
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"❌ 未找到 prefab: {folderName}");
|
||
}
|
||
}
|
||
|
||
AssetDatabase.Refresh();
|
||
GenerateFixReport(prefabMissingLog, "FishReplacementReport.txt");
|
||
|
||
Debug.Log("🎉 替换完成!");
|
||
if (replacedFish.Count > 0)
|
||
Debug.Log($"🟢 已成功替换:{string.Join(", ", replacedFish)}");
|
||
if (notFoundFish.Count > 0)
|
||
Debug.LogWarning($"🔴 未找到目录:{string.Join(", ", notFoundFish)}");
|
||
}
|
||
|
||
private string FindPrefabByName(string folderName)
|
||
{
|
||
foreach (string path in prefabSearchPaths)
|
||
{
|
||
string potentialPath = Path.Combine(path, folderName + ".prefab").Replace("\\", "/");
|
||
if (File.Exists(potentialPath))
|
||
return potentialPath;
|
||
}
|
||
return null;
|
||
}
|
||
|
||
private Transform FindChildRecursiveImproved(Transform parent, string[] possibleNames)
|
||
{
|
||
foreach (string name in possibleNames)
|
||
{
|
||
Transform exactMatch = FindExactMatch(parent, name);
|
||
if (exactMatch != null)
|
||
return exactMatch;
|
||
}
|
||
|
||
foreach (string name in possibleNames)
|
||
{
|
||
Transform containsMatch = FindContainsMatch(parent, name);
|
||
if (containsMatch != null)
|
||
return containsMatch;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private Transform FindExactMatch(Transform parent, string name)
|
||
{
|
||
if (parent.name.Equals(name, System.StringComparison.OrdinalIgnoreCase))
|
||
return parent;
|
||
|
||
foreach (Transform child in parent)
|
||
{
|
||
Transform result = FindExactMatch(child, name);
|
||
if (result != null)
|
||
return result;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private Transform FindContainsMatch(Transform parent, string name)
|
||
{
|
||
if (parent.name.ToLower().Contains(name.ToLower()))
|
||
return parent;
|
||
|
||
foreach (Transform child in parent)
|
||
{
|
||
Transform result = FindContainsMatch(child, name);
|
||
if (result != null)
|
||
return result;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
private bool IsValidReference(Object obj)
|
||
{
|
||
return obj != null && obj;
|
||
}
|
||
|
||
private List<string> FixFishComponentImproved(Transform root, Fish fish)
|
||
{
|
||
List<string> fixedFields = new List<string>();
|
||
var type = typeof(Fish);
|
||
var fields = type.GetFields(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic);
|
||
|
||
string rootName = (root != null) ? root.name : "Unknown";
|
||
Debug.Log($"🔍 开始修复 {rootName} 的挂点...");
|
||
|
||
foreach (var field in fields)
|
||
{
|
||
if (field.FieldType == typeof(Transform))
|
||
{
|
||
var value = field.GetValue(fish);
|
||
Transform transformValue = value as Transform;
|
||
|
||
if (!IsValidReference(transformValue))
|
||
{
|
||
string fieldName = field.Name;
|
||
Debug.Log($"🔧 尝试修复字段: {fieldName} (Missing Reference)");
|
||
|
||
string[] possibleNames = GetPossibleNames(fieldName);
|
||
Transform found = FindChildRecursiveImproved(root, possibleNames);
|
||
|
||
if (found != null)
|
||
{
|
||
field.SetValue(fish, found);
|
||
fixedFields.Add(fieldName);
|
||
Debug.Log($"✅ 成功修复 {fieldName} -> {found.name}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"❌ 无法找到字段 {fieldName} 对应的节点,尝试的名称: {string.Join(", ", possibleNames)}");
|
||
LogAllChildNames(root, fieldName);
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"✅ 字段 {field.Name} 引用正常: {transformValue.name}");
|
||
}
|
||
}
|
||
else if (field.FieldType == typeof(Animator))
|
||
{
|
||
var value = field.GetValue(fish);
|
||
Animator animatorValue = value as Animator;
|
||
|
||
if (!IsValidReference(animatorValue))
|
||
{
|
||
string fieldName = field.Name;
|
||
Debug.Log($"🔧 尝试修复Animator字段: {fieldName} (Missing Reference)");
|
||
|
||
string[] possibleNames = GetPossibleNames(fieldName);
|
||
Transform found = FindChildRecursiveImproved(root, possibleNames);
|
||
|
||
if (found != null)
|
||
{
|
||
Animator animator = found.GetComponent<Animator>();
|
||
if (animator != null)
|
||
{
|
||
field.SetValue(fish, animator);
|
||
fixedFields.Add(fieldName);
|
||
Debug.Log($"✅ 成功修复Animator {fieldName} -> {found.name}");
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"❌ 找到节点 {found.name} 但没有Animator组件");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.LogWarning($"❌ 无法找到Animator字段 {fieldName} 对应的节点");
|
||
}
|
||
}
|
||
else
|
||
{
|
||
Debug.Log($"✅ Animator字段 {field.Name} 引用正常: {animatorValue.gameObject.name}");
|
||
}
|
||
}
|
||
}
|
||
|
||
return fixedFields;
|
||
|
||
}
|
||
|
||
private string[] GetPossibleNames(string fieldName)
|
||
{
|
||
if (fieldNameMappings.ContainsKey(fieldName))
|
||
{
|
||
return fieldNameMappings[fieldName];
|
||
}
|
||
|
||
List<string> variants = new List<string>();
|
||
variants.Add(fieldName);
|
||
variants.Add(fieldName.ToLower());
|
||
variants.Add(fieldName.ToUpper());
|
||
variants.Add("Controller_" + fieldName);
|
||
variants.Add("B_" + fieldName);
|
||
variants.Add(fieldName + "_Bone");
|
||
|
||
return variants.ToArray();
|
||
}
|
||
|
||
private void LogAllChildNames(Transform root, string searchingFor)
|
||
{
|
||
if (root == null)
|
||
{
|
||
Debug.LogWarning($"🔍 无法为字段 {searchingFor} 搜索节点:root为null");
|
||
return;
|
||
}
|
||
|
||
List<string> allNames = new List<string>();
|
||
CollectAllChildNames(root, allNames);
|
||
|
||
Debug.Log($"🔍 为字段 {searchingFor} 搜索时,发现以下所有子节点:");
|
||
foreach (string name in allNames.Take(20))
|
||
{
|
||
Debug.Log($" - {name}");
|
||
}
|
||
|
||
if (allNames.Count > 20)
|
||
{
|
||
Debug.Log($" ... 还有 {allNames.Count - 20} 个节点");
|
||
}
|
||
}
|
||
|
||
private void CollectAllChildNames(Transform parent, List<string> names)
|
||
{
|
||
if (parent == null) return;
|
||
|
||
names.Add(parent.name);
|
||
foreach (Transform child in parent)
|
||
{
|
||
CollectAllChildNames(child, names);
|
||
}
|
||
}
|
||
|
||
private void GenerateFixReport(Dictionary<string, List<string>> results, string fileName)
|
||
{
|
||
if (results.Count > 0)
|
||
{
|
||
string logPath = Application.dataPath + "/../" + fileName;
|
||
using (StreamWriter writer = new StreamWriter(logPath))
|
||
{
|
||
writer.WriteLine($"鱼Prefab挂点修复报告 - {System.DateTime.Now}");
|
||
writer.WriteLine("=" + new string('=', 50));
|
||
writer.WriteLine();
|
||
|
||
foreach (var kv in results)
|
||
{
|
||
writer.WriteLine($"【{kv.Key}】");
|
||
writer.WriteLine($"修复字段数量: {kv.Value.Count}");
|
||
writer.WriteLine($"修复字段: {string.Join(", ", kv.Value)}");
|
||
writer.WriteLine();
|
||
}
|
||
|
||
writer.WriteLine($"总计修复了 {results.Count} 个Prefab");
|
||
}
|
||
Debug.Log($"📄 修复报告已生成: {logPath}");
|
||
}
|
||
}
|
||
private void SetFbxAnimationsLoop(string fbxPath, List<string> animationNames)
|
||
{
|
||
Debug.Log($"📝 需要手动设置Loop的动画: {string.Join(", ", animationNames)}");
|
||
Debug.Log($"📁 FBX文件路径: {fbxPath}");
|
||
Debug.Log($"💡 请手动在FBX的Import Settings -> Animations中勾选这些动画的Loop Time");
|
||
|
||
// 不进行任何自动设置,避免破坏FBX
|
||
}
|
||
|
||
} // 这是类的结束括号
|
||
|
||
|
||
|
||
// 动画挂载结果数据结构
|
||
public class AnimationMountResult
|
||
{
|
||
public int TotalAnimations = 0;
|
||
public List<string> MountedAnimations = new List<string>();
|
||
public List<string> LoopSetAnimations = new List<string>();
|
||
}
|
||
|
||
|
||
|