先修复一下,错误的场景 删除不必要的钓场资产 修复into场景 update:更新meta文件,修复报错 update:修复资源 修复第一章节建造 修复建造第三章 update:删除多余内容 update:更新README.md update:还原图标 update:更新README update:更新配置
326 lines
12 KiB
C#
326 lines
12 KiB
C#
using UnityEditor;
|
||
using UnityEngine;
|
||
using System.IO;
|
||
using System.Text.RegularExpressions;
|
||
using System.Collections.Generic;
|
||
using System.Linq;
|
||
|
||
/// <summary>
|
||
/// 音频GUID转移工具 - 将MP3/WAV文件的GUID转移到OGG文件
|
||
///
|
||
/// 此脚本解决了Unity Timeline中音频资源格式转换的问题:
|
||
/// 当需要将Timeline中的WAV/MP3音频替换为OGG格式时,直接替换文件会导致引用丢失。
|
||
/// 本工具通过转移GUID,确保Timeline中的引用保持不变。
|
||
/// </summary>
|
||
public static class AudioGUIDTransfer
|
||
{
|
||
// 定义我们关心的原始音频扩展名
|
||
private static readonly HashSet<string> OriginalAudioExtensions = new HashSet<string>
|
||
{
|
||
".mp3",
|
||
".wav"
|
||
};
|
||
|
||
// 定义目标音频扩展名
|
||
private const string TargetAudioExtension = ".ogg";
|
||
|
||
// 在Unity菜单栏的 "Assets" 下添加一个菜单项
|
||
[MenuItem("Assets/AudioFormatTransfer")]
|
||
public static void TransferGUIDsInSelectedFolder()
|
||
{
|
||
// 获取当前在Project窗口中选中的对象
|
||
Object selectedObject = Selection.activeObject;
|
||
|
||
// --- 输入验证 ---
|
||
if (selectedObject == null)
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "请在Project窗口中选择一个包含音频文件的文件夹。", "确定");
|
||
return;
|
||
}
|
||
|
||
string selectedPath = AssetDatabase.GetAssetPath(selectedObject);
|
||
|
||
if (!AssetDatabase.IsValidFolder(selectedPath))
|
||
{
|
||
EditorUtility.DisplayDialog("错误", "请选择一个有效的文件夹。", "确定");
|
||
return;
|
||
}
|
||
|
||
// --- 确认对话框(对于破坏性操作至关重要) ---
|
||
if (!EditorUtility.DisplayDialog("确认操作",
|
||
$"您确定要将文件夹 '{selectedPath}' 中所有MP3/WAV文件的GUID转移到对应的同名OGG文件,并删除原始文件吗?\n\n此操作不可逆,请务必提前备份您的项目!",
|
||
"是", "否"))
|
||
{
|
||
return; // 用户取消了操作
|
||
}
|
||
|
||
// --- 核心处理逻辑 ---
|
||
ProcessFolder(selectedPath);
|
||
|
||
// --- 完成消息 ---
|
||
EditorUtility.DisplayDialog("完成", "MP3/WAV GUID转移到OGG并删除原始文件的操作已完成。请检查Console窗口获取详细日志。", "确定");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 处理给定文件夹,将MP3/WAV文件的GUID转移到对应的OGG文件,并删除原始文件。
|
||
/// </summary>
|
||
/// <param name="folderPath">要处理的文件夹的完整路径。</param>
|
||
private static void ProcessFolder(string folderPath)
|
||
{
|
||
// 获取选中文件夹中所有文件(仅限顶层目录,不包括子文件夹)
|
||
string[] allFilesInFolder = Directory.GetFiles(folderPath, "*.*", SearchOption.TopDirectoryOnly);
|
||
|
||
// 按基础名称(不含扩展名)对文件进行分组
|
||
Dictionary<string, List<string>> fileGroups = new Dictionary<string, List<string>>();
|
||
|
||
foreach (string filePath in allFilesInFolder)
|
||
{
|
||
// 跳过.meta文件,因为我们只关心实际资源文件
|
||
if (filePath.EndsWith(".meta")) continue;
|
||
|
||
string fileName = Path.GetFileName(filePath);
|
||
string baseName = GetBaseNameWithoutExtensions(fileName);
|
||
|
||
if (!fileGroups.ContainsKey(baseName))
|
||
{
|
||
fileGroups.Add(baseName, new List<string>());
|
||
}
|
||
fileGroups[baseName].Add(filePath);
|
||
}
|
||
|
||
int processedCount = 0;
|
||
int skippedCount = 0;
|
||
|
||
// 遍历每个文件组
|
||
foreach (var group in fileGroups)
|
||
{
|
||
string baseName = group.Key;
|
||
List<string> filesInGroup = group.Value;
|
||
|
||
// 尝试找到一个原始文件(MP3/WAV)和一个OGG文件
|
||
string originalFilePath = null;
|
||
string oggFilePath = null;
|
||
|
||
foreach (string filePath in filesInGroup)
|
||
{
|
||
string ext = Path.GetExtension(filePath).ToLower();
|
||
|
||
if (ext == TargetAudioExtension)
|
||
{
|
||
if (oggFilePath != null)
|
||
{
|
||
Debug.LogWarning($"在 '{folderPath}' 文件夹中发现多个同名OGG文件 '{baseName}{TargetAudioExtension}'。跳过此组文件。");
|
||
oggFilePath = null;
|
||
break;
|
||
}
|
||
oggFilePath = filePath;
|
||
}
|
||
else if (OriginalAudioExtensions.Contains(ext))
|
||
{
|
||
if (originalFilePath != null)
|
||
{
|
||
Debug.LogWarning($"在 '{folderPath}' 文件夹中发现多个同名原始音频文件(MP3/WAV)'{baseName}'。跳过此组文件。");
|
||
originalFilePath = null;
|
||
break;
|
||
}
|
||
originalFilePath = filePath;
|
||
}
|
||
}
|
||
|
||
// 如果找到了一个有效的配对(一个原始文件,一个OGG文件)
|
||
if (originalFilePath != null && oggFilePath != null)
|
||
{
|
||
// 转换为Unity资产路径格式
|
||
string assetOriginalPath = originalFilePath.Replace(Application.dataPath, "Assets").Replace('\\', '/');
|
||
string assetOggPath = oggFilePath.Replace(Application.dataPath, "Assets").Replace('\\', '/');
|
||
|
||
// 从原始文件的.meta文件中获取GUID
|
||
string originalMetaPath = originalFilePath + ".meta";
|
||
string originalGuid = GetGuidFromMetaFile(originalMetaPath);
|
||
|
||
if (string.IsNullOrEmpty(originalGuid))
|
||
{
|
||
Debug.LogError($"无法从原始文件 '{originalFilePath}' 的.meta文件中读取GUID。跳过此文件。");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
|
||
Debug.Log($"原始文件 '{assetOriginalPath}' 的GUID为: {originalGuid}");
|
||
|
||
// 关键步骤1:删除OGG文件的.meta文件(如果存在)
|
||
string oggMetaPath = oggFilePath + ".meta";
|
||
if (File.Exists(oggMetaPath))
|
||
{
|
||
try
|
||
{
|
||
File.Delete(oggMetaPath);
|
||
Debug.Log($"已删除OGG文件的.meta文件: '{oggMetaPath}'");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"删除OGG文件的.meta文件时出错: {ex.Message}");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
}
|
||
|
||
// 关键步骤2:删除原始文件(MP3/WAV)
|
||
// 注意:我们不使用AssetDatabase.DeleteAsset,因为它可能不会完全释放GUID
|
||
try
|
||
{
|
||
// 先删除原始文件的.meta文件
|
||
if (File.Exists(originalMetaPath))
|
||
{
|
||
File.Delete(originalMetaPath);
|
||
Debug.Log($"已删除原始文件的.meta文件: '{originalMetaPath}'");
|
||
}
|
||
|
||
// 再删除原始文件本身
|
||
if (File.Exists(originalFilePath))
|
||
{
|
||
File.Delete(originalFilePath);
|
||
Debug.Log($"已删除原始文件: '{originalFilePath}'");
|
||
}
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"删除原始文件或其.meta文件时出错: {ex.Message}");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 关键步骤3:刷新资产数据库,确保Unity知道文件已被删除
|
||
AssetDatabase.Refresh();
|
||
|
||
// 关键步骤4:为OGG文件创建新的.meta文件,使用原始文件的GUID
|
||
string metaContent = GenerateMetaFileContent(originalGuid);
|
||
|
||
try
|
||
{
|
||
File.WriteAllText(oggMetaPath, metaContent);
|
||
Debug.Log($"已为OGG文件创建新的.meta文件,使用原始GUID: '{oggMetaPath}'");
|
||
}
|
||
catch (System.Exception ex)
|
||
{
|
||
Debug.LogError($"为OGG文件创建.meta文件时出错: {ex.Message}");
|
||
skippedCount++;
|
||
continue;
|
||
}
|
||
|
||
// 关键步骤5:再次刷新资产数据库,确保Unity导入新的.meta文件
|
||
AssetDatabase.Refresh();
|
||
|
||
// 关键步骤6:验证OGG文件的GUID是否正确
|
||
string newOggGuid = AssetDatabase.AssetPathToGUID(assetOggPath);
|
||
if (newOggGuid == originalGuid)
|
||
{
|
||
Debug.Log($"<color=green>成功</color>: 文件 '{assetOggPath}' 现在使用原始文件的GUID: {originalGuid}");
|
||
processedCount++;
|
||
}
|
||
else
|
||
{
|
||
Debug.LogError($"GUID转移失败! OGG文件 '{assetOggPath}' 的GUID为 {newOggGuid},而不是预期的 {originalGuid}");
|
||
skippedCount++;
|
||
}
|
||
}
|
||
else if (originalFilePath == null && oggFilePath != null)
|
||
{
|
||
// 只有OGG文件,没有对应的原始文件,这是正常的,不需要警告
|
||
continue;
|
||
}
|
||
else if (originalFilePath != null && oggFilePath == null)
|
||
{
|
||
Debug.LogWarning($"找到原始文件 '{originalFilePath}',但没有对应的OGG文件。跳过此文件。");
|
||
skippedCount++;
|
||
}
|
||
}
|
||
|
||
// 最终刷新,确保所有更改都已反映在Unity中
|
||
AssetDatabase.Refresh();
|
||
|
||
Debug.Log($"处理完成:成功处理 {processedCount} 对文件,跳过 {skippedCount} 对文件。");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从文件名中提取基础名称,移除所有已知的音频扩展名。
|
||
/// </summary>
|
||
private static string GetBaseNameWithoutExtensions(string fileName)
|
||
{
|
||
string baseName = Path.GetFileNameWithoutExtension(fileName);
|
||
|
||
// 检查是否还有其他音频扩展名
|
||
foreach (string ext in OriginalAudioExtensions.Concat(new[] { TargetAudioExtension }))
|
||
{
|
||
if (baseName.EndsWith(ext, System.StringComparison.OrdinalIgnoreCase))
|
||
{
|
||
baseName = baseName.Substring(0, baseName.Length - ext.Length);
|
||
}
|
||
}
|
||
|
||
return baseName;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 从Unity的.meta文件中提取GUID。
|
||
/// </summary>
|
||
private static string GetGuidFromMetaFile(string metaFilePath)
|
||
{
|
||
if (!File.Exists(metaFilePath))
|
||
{
|
||
Debug.LogError($"Meta文件不存在: {metaFilePath}");
|
||
return null;
|
||
}
|
||
|
||
string metaContent = File.ReadAllText(metaFilePath);
|
||
Match match = Regex.Match(metaContent, @"guid: ([0-9a-fA-F]{32})");
|
||
|
||
if (match.Success)
|
||
{
|
||
return match.Groups[1].Value;
|
||
}
|
||
|
||
return null;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 生成包含指定GUID的.meta文件内容。
|
||
/// </summary>
|
||
private static string GenerateMetaFileContent(string guid)
|
||
{
|
||
// 创建一个基本的AudioImporter .meta文件内容
|
||
return $@"fileFormatVersion: 2
|
||
guid: {guid}
|
||
AudioImporter:
|
||
externalObjects: {{}}
|
||
serializedVersion: 6
|
||
defaultSettings:
|
||
loadType: 0
|
||
sampleRateSetting: 0
|
||
sampleRateOverride: 44100
|
||
compressionFormat: 1
|
||
quality: 1
|
||
conversionMode: 0
|
||
platformSettingOverrides: {{}}
|
||
forceToMono: 0
|
||
normalize: 1
|
||
preloadAudioData: 1
|
||
loadInBackground: 0
|
||
ambisonic: 0
|
||
3D: 1
|
||
userData:
|
||
assetBundleName:
|
||
assetBundleVariant:
|
||
";
|
||
}
|
||
|
||
// 菜单项的验证方法
|
||
[MenuItem("Assets/Transfer MP3/WAV GUID to OGG in Selected Folder", true)]
|
||
public static bool ValidateTransferGUIDsInSelectedFolder()
|
||
{
|
||
// 仅当在Project窗口中选中单个文件夹时才启用菜单项
|
||
if (Selection.activeObject == null) return false;
|
||
string selectedPath = AssetDatabase.GetAssetPath(Selection.activeObject);
|
||
return AssetDatabase.IsValidFolder(selectedPath);
|
||
}
|
||
}
|