有关Unity的杂项记录(2022)

/ Editor 与 Runtime 的EditorUtility.NaturalCompare类似的排序

在对列表里的字符串进行排序时,当存在有自然数时,想要让之以自然数顺序进行排序,然而默认的排序是不支持自然数排序的,需要自己实现。

排序方式对比如下:

Alphabetic sort    Natural numeric sort
DOS (CMD prompt) style    Windows Explorer Style
1.txt    1.txt
10.txt    3.txt
3.txt    10.txt
a10b1.txt    a1b1.txt
a1b1.txt    a2b1.txt
a2b1.txt    a2b2.txt
a2b11.txt    a2b11.txt
a2b2.txt    a10b1.txt
b1.txt    b1.txt
b10.txt    b2.txt
b2.txt    b10.txt
实现
.NET平台
在.NET平台下,可以使用《Numeric String Sort in C#》文章里提供的方法。

Unity平台
在编辑器下,可以使用EditorUtility.NaturalCompare接口,运行时的话,只能使用.NET平台的方法。

测试
测试代码如下:

private void Test()
{
    List<string> names = new List<string>();
    names.Add("1.txt");
    names.Add("a10b1.txt");
    names.Add("a1b1.txt");
    names.Add("10.txt");
    names.Add("3.txt");

    names.Sort();

    foreach (var n in names)
    {
        Debug.Log(n);
    }

    names.Sort(EditorUtility.NaturalCompare);

    foreach (var n in names)
    {
        Debug.Log(n);
    }
}
运行结果前后对比如下:

运行时也需要进行自然数排序。

解决

新建IEnumerableEx.cs文件,代码如下:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text.RegularExpressions;

public static class IEnumerableEx
{
    public static IOrderedEnumerable<T> OrderByAlphaNumeric<T>(this IEnumerable<T> source, Func<T, string> selector)
    {
        int max = source
                      .SelectMany(i => Regex.Matches(selector(i), @"\d+").Cast<Match>().Select(m => (int?)m.Value.Length))
                      .Max() ?? 0;

        return source.OrderBy(i => Regex.Replace(selector(i), @"\d+", m => m.Value.PadLeft(max, '0')));
    }
}
调用代码如下:

List<string> names = new List<string>();
            names.Add("1.txt");
            names.Add("a10b1.txt");
            names.Add("a1b1.txt");
            names.Add("10.txt");
            names.Add("3.txt");

            names.Sort();

            foreach (var n in names)
            {
                Debug.Log(n);
            }

            //names.Sort(EditorUtility.NaturalCompare);

           var val = names.OrderByAlphaNumeric(s => s).ToList();
            names.Clear();
            names.AddRange(val);

            foreach (var n in names)
            {
                Debug.Log(n);
            }
运行结果如下:

///

今天在 Windows 上打 PC 包时遇到了这样的报错:

Console 输出的详细错误日志:

IOException: Failed to Move File / Directory from 'Temp/StagingArea\bunny rummy_Data\StreamingAssets\lua\logic\ui\pnl_activity\first_add_cash\first_add_cash_activity_preselected_item\ui_first_add_cash_activity_preselected_item_logic.lua' to 'C:\TS\rummy_itc\Assets\..\z_package\debug\bunny_rummy_8-3-3-4_v1.0.1.20_20200427_153115\bunny rummy_Data\StreamingAssets\lua\logic\ui\pnl_activity\first_add_cash\first_add_cash_activity_preselected_item\ui_first_add_cash_activity_preselected_item_logic.lua'.
UnityEditor.FileUtil.MoveFileOrDirectory (System.String source, System.String dest) (at C:/buildslave/unity/build/Editor/Mono/FileUtil.bindings.cs:77)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:428)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyFilesToDestination (System.String source, System.String target, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:432)
DesktopStandalonePostProcessor.CopyStagingAreaIntoDestination (UnityEditor.Modules.BuildPostProcessArgs args, System.Collections.Generic.HashSet`1[T] filesToNotOverwrite) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:402)
DesktopStandalonePostProcessor.PostProcess (UnityEditor.Modules.BuildPostProcessArgs args) (at C:/buildslave/unity/build/Editor/Mono/BuildPipeline/DesktopStandalonePostProcessor.cs:51)
UnityEditor.BuildPipeline:BuildPlayer(String[], String, BuildTarget, BuildOptions)
PackAppPatch:GenPackage() (at Assets/Code/Editor/Package/PackAppPatch.cs:342)
PackAppPatch:DoPackApp(CPackArg) (at Assets/Code/Editor/Package/PackAppPatch.cs:203)
PackGUI:OnClickPackage() (at Assets/Code/Editor/Package/PackGUI.cs:54)
PackGUI:BranchConfirm(Action) (at Assets/Code/Editor/Package/PackGUI.cs:311)
<>c__DisplayClass9_0:<OnGUI>b__1() (at Assets/Code/Editor/Package/PackGUI.cs:112)
GuiUtil:RegSceneBtn(String, String, Action) (at Assets/Code/Editor/GuiUtil.cs:30)
PackGUI:OnGUI() (at Assets/Code/Editor/Package/PackGUI.cs:110)
PackGUI:Draw() (at Assets/Code/Editor/Package/PackGUI.cs:23)
UtilTemplate`2:OnGUI() (at Assets/Code/Editor/UtilBase/UtilTemplate.cs:55)
UnityEngine.GUIUtility:ProcessEvent(Int32, IntPtr)
看起来貌似是拷贝文件时拷贝失败了,核心的报错内容是这部分:

IOException: Failed to Move File / Directory from 'Temp/StagingArea\bunny rummy_Data\StreamingAssets\lua\logic\ui\pnl_activity\first_add_cash\first_add_cash_activity_preselected_item\ui_first_add_cash_activity_preselected_item_logic.lua' to 'C:\TS\rummy_itc\Assets\..\z_package\debug\bunny_rummy_8-3-3-4_v1.0.1.20_20200427_153115\bunny rummy_Data\StreamingAssets\lua\logic\ui\pnl_activity\first_add_cash\first_add_cash_activity_preselected_item\ui_first_add_cash_activity_preselected_item_logic.lua'.
UnityEditor.FileUtil.MoveFileOrDirectory (System.String source, System.String dest) (at C:/buildslave/unity/build/Editor/Mono/FileUtil.bindings.cs:77)
即调用 UnityEditor.FileUtil.MoveFileOrDirectory API 去复制文件的时候出错了

错误分析
其实之前出现过,使用相同内容的工程打包,在 C 盘下的工程可以打包,在 E 盘下不能打包,区别就是路径不同,如下:

C 盘工程路径:C:\TS\rummy_itc

D 盘工程路径:E:\U3DProjects\rummy_itc_copy

显然路径长度不同,只是 C 盘可以打包成功了就暂时没去理会。

这次 C 盘的工程也出现打包问题了,而且可以看到报错的路径长度很长,特别是复制目标文件的路径 'C:\TS\rummy_itc\Assets\..\z_package\debug\bunny_rummy_8-3-3-4_v1.0.1.20_20200427_153115\bunny rummy_Data\StreamingAssets\lua\logic\ui\pnl_activity\first_add_cash\first_add_cash_activity_preselected_item\ui_first_add_cash_activity_preselected_item_logic.lua' ,使用在线计算工具统计的结果:

字符长度为 256,初步猜测是路径超过 Windows 路径的长度限制。

Windows 限制
在 Windows API 中,通过 MAX_PATH 来限制路径得最大长度,MAX_PATH 被定义为 260

一般路径得结构:

|盘符|冒号|反斜杠|被分斜杠分割的具体路径|NUL('\0')|
例如:D:\<dir>NUL

最后的 NUL 是终止符,也可以用 '\0' 表示 ,因此,去除盘符的目录的相对路径 dir 长度不能超过 256,这里对于目录和文件的限制有所区别:

目录:248

文件:256

Windows API 中会将 '/' 转为 '\'

测试
将上面报错的目标路径修改进行测试,测试代码如下:

String src = "Temp/StagingArea/bunny rummy_Data/StreamingAssets/lua/logic/ui/pnl_activity/first_add_cash/first_add_cash_activity_preselected_item/ui_first_add_cash_activity_preselected_item_logic.lua";
String des = "C:/TS/rummy_itc/Assets/../z_package/debug/bunny_rummy_8-3-3-4_v1.0.1.20_20200427_105542/bunny rummy_Data/StreamingAssets/lua/logic/ui/pnl_activity/first_add_cash/first_add_cash_activity_preselected_item/ui_first_add_cash_activity_preselected_item_logic.lua";
FileUtil.CopyFileOrDirectory(src, des);
测试结果是将文件名删除一部分,改为 ui_first_add_cash_activity_preselected_item_ ,最终总路径长度为 247 才能复制成功。

总结
像这样的问题只能在开发时,通过一些规范来规避,主要需要注意几点:

Unity 工程位置不要放得太深,尽量放在磁盘根路径下或二级路径,如 :D:\U3DProjs

工程内文件名不要太长,像 ui_first_add_cash_activity_preselected_item_logic.lua 这样的取名方式有点逆天了,尽量用缩写

工程内目录不要创建太多级,且目录名不要太长

其他
MSDN 提到,可以通过 "\\?\" 前缀加上至多 255 长度的字符串来表示长达 32000 个字符的最长路径

File name too long cannot copy

Path limit is 248 characters.
Path+Filename limit is 260 characters.

Exceeding either will result in the error mentioned.
Simplify your folders and titles.

File name too long cannot copy

win7环境通常模式下:
path+filename: 260个长度是作为文件目录长度+文件名的长度限制之和(带结束符‘\0’)。

path: 248个长度是作为文件目录的长度限制(带结束符‘\0’);

filename: 256个长度通常作为文件名称的长度限制(带结束符‘\0’),不算结束符的话为255个字符;

1. 例如-建立文件:path+filename触发限额260

所在文件夹的长度为50,则文件名的长度被限制在210以内,超出的话会被进行截断。

2. 例如-建立文件夹:path触发限额248

在c:\下建立文件夹(目录前缀"c:\"占据3个长度),新建文件夹名称限制在245以内,超出的话会被进行截断。

3. 例如-建立文件:filename触发限额256

在c:\下建立文件(目录前缀"c:\"占据3个长度),新建文件夹名称限制在256以内,超出的话会被进行截断。

如果在程序中的话,超出长度的话MoveFile/CopyFile会返回错误,处理失败。

例子1:建立文件
1.  我手动在C:\建立一个文件,使用280个长度进行重命名,实际生成的文件名长度却是256个长度(带字符串结束符’\0’)

新建时使用280长度(带‘\0'结束符):1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklm.txt

实际生成文件名,共计256个长度(带字符串结束符’\0’):

256长度(带‘\0'结束符):1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz123

2. 把这个文件往”C:\temp”目录copy的时候

报错: 文件名对目标文件夹可能过长,您可以缩短文件名并重试,或者尝试路径较短的位置。

意味着,

  a. 要么我们把文件名改短一些,

  b. 要么copy 的目录长度短一些,例如向”D:\”复制就没会有问题。

例子2:建立文件夹
1. 我手动在C:\建立一个文件夹,使用280个长度进行重命名,实际生成的文件夹名称却是带字符串结束符’\0’共245个长度

新建时使用280长度(带‘\0'结束符):1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopq

实际生成目录名称: 共计245个长度(带字符串结束符’\0’)

245长度(带‘\0'结束符):1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqr

和父目录“c:\"连接在一起形成的目录长度正好为248长度(带‘\0'结束符)

c:\1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqrstuvwxyz1234567890abcdefghijklmnopqr

2. 在该目录下,再尝试建立目录时,失败-报错建立路径过长

意味着,

   a. 总目录长度已经达到了最大限制,不能再往下建立目录

3. 另外,在该目录下新建文件,利用同样字符串,可以建立一个12长度的文件(带‘\0'结束符),超出会被截断

   a. 建立的文件名(带'\0'长度12):1234567890a

   b. 文件路径前缀(带‘\’长度248) + 文件名长度12 = 260 达到限额

/

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值