CS文件编码批量转换为UTF-8 / 以UTF-8保存

问题

突然发现项目里CS文件的编码是GBK,文件里的中文字符在Inspector上也显示为乱码。

于是用C#写一个工具,编译后丢到Assets目录里,批量转换。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
using System.Text;

string path = Directory.GetCurrentDirectory();

string[] fileDir = Directory.GetFiles(path, "*.cs", SearchOption.AllDirectories);

foreach (var item in fileDir)
{
string content = File.ReadAllText(item, Encoding.Default);
File.WriteAllText(item, content, Encoding.UTF8);
}

Console.WriteLine("Done!");
Console.ReadKey();

然而从Unity创建CS文件,文件的编码格式仍然是GBK。那么就要修改设置,以UTF-8的编码保存。

解决方案

1. 修改保存选项

编辑 > 高级 > 设置编码 > 保存时使用编码

2. 设置默认编码

根据Visual Studio IDE 文档:

从 Visual Studio 2022 版本 17.13 预览版 1 开始,可以设置用于保存文件的默认编码。

若要设置默认值,请选择 工具 > 选项 > 环境,文档。 接下来,选择 “保存具有特定编码的文件”,然后选择所需的编码作为默认值。

3. 创建后以UTF-8保存

Unity有一个类叫做AssetModificationProcessor,根据官方说明,这个类可以hook到序列化资产和场景的保存里。

这个类有四个主要的函数:OnWillCreateAsset,OnWillDeleteAsset,OnWillMoveAsset,OnWillSaveAssets。

其中,当Unity要创建一个还没有导入的资产时,会调用OnWillCreateAsset函数。

1
2
3
4
5
6
7
public class CustomAssetModificationProcessor : UnityEditor.AssetModificationProcessor
{
static void OnWillCreateAsset(string assetName)
{
Debug.Log("OnWillCreateAsset is being called with the following asset: " + assetName + ".");
}
}

其中,assetName是资产的相对路径。

到这里思路就清晰了:创建CS文件后,用File类读取文件,再以UTF-8格式保存。

但是,如果在这个函数里使用File类,传入assetName,对创建的CS文件进行读取,会提示错误:FileNotFoundException: Could not find file "xxx.cs"

这是因为,这个函数是OnWillCreateAsset。

在Unity里,还有一个类叫做AssetPostprocessor,根据官方说明,这个类允许hook到导入pipeline,在导入资产之前或之后运行脚本。顾名思义,它是“资产的后处理”。

这个类里有一个函数OnPostprocessAllAssets,在导入任意数量的资产后(Asset进度条到达终点时),此函数被调用。

那么可以尝试在OnWillCreateAsset中标记刚刚创建的CS文件,在OnPostprocessAllAssets中读写文件。

代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public class ScriptModificationProcessor : AssetModificationProcessor
{
public static void OnWillCreateAsset(string assetName)
{
if (assetName.EndsWith(".cs"))
{
if (!ScriptPostprocessor.s_CreatedScripts.Contains(assetName))
{
ScriptPostprocessor.s_CreatedScripts.Add(assetName);
}
}
}
}

public class ScriptPostprocessor : AssetPostprocessor
{
public static List<string> s_CreatedScripts = new();

private static void OnPostprocessAllAssets(string[] importedAssets, string[] deletedAssets, string[] movedAssets, string[] movedFromAssetPaths, bool didDomainReload)
{
foreach (var importedAsset in importedAssets)
{
if (s_CreatedScripts.Contains(importedAsset))
{
string content = File.ReadAllText(importedAsset, Encoding.Default);
File.WriteAllText(importedAsset, content, Encoding.UTF8);
}
}
}
}

文档链接

使用编码保存和打开文件

AssetModificationProcessor.OnWillCreateAsset

AssetPostprocessor.OnPostprocessAllAssets